WPF tooltip on item does not retrigger initial delay - c#

Consider the following simple code.
When I hover the mouse on any item, it waits for 1 second and then it shows the ToolTip as expected. However, if i move the mouse to another item without getting out of the list, the tooltip simply updates to the new item name without retriggering a show delay. Is this normal behavior?
I need for the tooltip to disappear when moving the mouse across the list whenever it enters a new item and retrigger a show delay. Any suggestions?
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate DataType="lcl:Item">
<TextBlock
Text="{Binding Name}"
HorizontalAlignment="Stretch"
ToolTipService.InitialShowDelay="1000"
ToolTipService.BetweenShowDelay="1000"
ToolTipService.HasDropShadow="True"
ToolTipService.HorizontalOffset="5"
ToolTipService.VerticalOffset="5">
<TextBlock.ToolTip>
<TextBlock Text="{Binding Name}" />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs
namespace WpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_Items.Add(new Item() { Name = "First" });
_Items.Add(new Item() { Name = "Second" });
_Items.Add(new Item() { Name = "Third" });
}
public Collection<Item> _Items = new Collection<Item>();
public Collection<Item> Items
{
get { return _Items; }
}
}
public class Item
{
public string Name
{
get;
set;
}
}
}

I am afraid that you misunderstood how the BetweenShowDelay property works. As you can read here:
In the [...] example, the InitialShowDelay property is set to one
second (1000 milliseconds) and the BetweenShowDelay is set to two
seconds (2000 milliseconds) for the tooltips of both Ellipse controls.
If you display the tooltip for one of the ellipses and then move the
mouse pointer to another ellipse within two seconds and pause on it,
the tooltip of the second ellipse displays immediately.
Take a look to the example in the link above for more details.
So - as you can see - the one that you describe is the normal behavior.

This seems to work as expected for me, i.e. don't set the ToolTipService.BetweenShowDelay property:
<TextBlock
Text="{Binding Name}"
HorizontalAlignment="Stretch"
ToolTipService.InitialShowDelay="5000"
ToolTipService.HasDropShadow="True"
ToolTipService.HorizontalOffset="5"
ToolTipService.VerticalOffset="5">
<TextBlock.ToolTip>
<TextBlock Text="{Binding Name}" />
</TextBlock.ToolTip>
</TextBlock>

I am also seeing the behavior that the OP was questioning. The entire reason I added the property ToolTipService.BetweenShowDelay was because I thought it would fix the problem, but as said here we are misunderstanding what it does.
Perhaps this is a bug in WPF, because even without setting BetweenShowDelay, the moment the mouse moves to another element, the tooltip just changes instead of the previous tooltip closing and then waiting to open the new one.
I am wondering if it has to do with using a Data Template?

Related

Dynamic text boxes binding

I am trying to make a form of sorts where the user will have the option to press a button to dynamically add more text boxes, with each text box to contain the path of a directory to exclude from a search. This is relatively trivial using code-behind, but my problem is doing it in proper MVVM.
The general markup for the structure is:
<ScrollViewer >
<StackPanel>
<DockPanel LastChildFill="False">
<TextBox DockPanel.Dock="Left"/>
<Button DockPanel.Dock="Right"
Content="+"/>
</DockPanel>
</StackPanel>
</ScrollViewer>
Clicking the button will add a new DockPanel with a TextBox and Button to the StackPanel. All buttons but the bottom-most will change to a minus sign. How can I somehow bind to the text of all the text boxes?
As an aside, once/if I get this working, would it be better to make it into its own component?
Quick pseudo-code to get you started:
cs (view model):
// INotifyPropertyChanged if you need, as well as full-properties with notification
public class Item
{
public string Text {get; set;}
}
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
void OnCommandAdd() => Items.Add(new Item());
xaml (view):
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The idea is to use control able to display list (e.g. ItemsControl) and collection of items in view model. Adding new element to view is done by adding item to that collection.
All TextBoxes will have Text bound to corresponding property of item.

WPF - Setting tab order on large number of controls

So I have a large amount of controls (textboxes) as you can see below, but there are around 30 rows of this. These are loaded using arrays, and each column represents an array. So when I hit tab in a textbox, instead of tabbing horizontally, it tabs vertically instead.
Is there a way to set the tab order so it will tab horizontally, aside from changing the way the controls are loaded?
Another quirk is that when leaving one textbox, instead of focusing the next, it just kind of highlights the textbox, and I have to tab a second time to get inside the next textbox.
EDIT:
Main view (lots of code has been omitted, I'm pretty sure nothing has been left out that needs to be here)
<ListBox ItemsSource="{Binding Items}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
ItemsView
<UserControl>
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:Item}">
<views:ItemView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<ContentControl Content="{Binding item <!-- about 30 different items here, omitted for readability -->}" />
</StackPanel>
</UserControl>
ItemView
<UserControl ... IsTabStop="False">
<TextBox Text="{Binding Value}" />
</UserControl>
The ItemView is nested in the ItemsView, which is nested in the MainView. Since the textboxes are generated based on the array values, I can't easily set the TabIndex property unless there is a way I don't know about (I am pretty new at WPF).
The TabIndex property provides a way to control the tab order independently of the order controls are loaded.
Usage example:
<Grid>
<TextBox TabIndex="2" /><!-- will receive focus second -->
<TextBox TabIndex="1" /><!-- will receive focus first-->
</Grid>
I would guess the unwanted focusing you are seeing is due to a parent UserControl that your TextBoxes are placed in.
If this is the case, you could prevent that by setting IsTabStop="false" on that parent control.
For example:
<UserControl .... IsTabStop="False">
<Grid>
<!-- other graphics -->
<TextBox TabIndex="1" />
</Grid>
</UserControl>
Using a view model to populate the data
public class CellViewModel
{
public double Value { get; set; }
public int TabIndex { get; set; }
}
public IEnumerable<IEnumerable<CellViewModel>> GetMatrix(
List<List<double>> matrixValues)
{
var columnCount = matrixValues.Count;
return matrixValues
.Select((x, i) => GetColumn(x, columnCount, i));
}
public IEnumerable<CellViewModel> GetColumn(
List<double> columnValues,
int columnCount,
int columnIndex)
{
return columnValues
.Select((x, i) =>
new CellViewModel { Value = x, TabIndex = columnIndex + columnCount * i });
}
Your ItemsSource for your ListBox (which you've now changed to ItemsControl) should be a new Matrix property, which you populate using GetMatrix().
In your ItemView, you would want something like this:
<UserControl ... IsTabStop="False">
<TextBox Text="{Binding Value}" TabIndex="{Binding TabIndex}" />
</UserControl>

Why does my dropdown feel so clunky?

I have a XAML UserControl embedded in a WinForms/WPF Interop ElementHost control. The control is pretty simple - it's just a dropdown with a button - here's the entire markup:
<UserControl x:Class="Rubberduck.UI.FindSymbol.FindSymbolControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Rubberduck.UI.FindSymbol"
mc:Ignorable="d"
d:DesignHeight="27" d:DesignWidth="270">
<UserControl.Resources>
<local:DeclarationImageConverter x:Key="DeclarationImageConverter" />
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="local:FindSymbolControl.GoCommand"
Executed="CommandBinding_OnExecuted"
CanExecute="CommandBinding_OnCanExecute"/>
</UserControl.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True"
ItemsSource="{Binding MatchResults}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="IdentifierName">
<ComboBox.ItemTemplate>
<DataTemplate DataType="local:SearchResult">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image Height="16" Width="16" Margin="2,0,2,0" Source="{Binding Declaration, Converter={StaticResource DeclarationImageConverter}}" />
<TextBlock Margin="2,0,2,0" Text="{Binding IdentifierName}" FontWeight="Bold" MinWidth="140" />
<TextBlock Margin="2,0,2,0" Text="{Binding Location}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="1"
Command="local:FindSymbolControl.GoCommand">
<Image Height="16" Source="pack://application:,,,/Rubberduck;component/Resources/arrow.png" />
</Button>
</Grid>
</UserControl>
The problem is that it doesn't work reliably, and far from instinctively.
If I type something in the box that actually matches an item, nothing happens until I manually select that item in the dropdown. Like here, I typed "sleepD", the box autocompleted to "sleepDelay", but the command is still disabled:
Once I've selected the item in the dropdown, the command button gets enabled as expected (although the image on the button doesn't show up grayed-out when the button is disabled, so it's not exactly as obvious as I intended it to be).
(the screenshot isn't really showing it, but there's only 1 match for that search)
If I click the button at that point, it works as expected. The problem is that if I make a new selection from the dropdown after that, the text box gets cleared instead of displaying the item I selected, and there's a weird delay during which the box is displaying what appears to be selected whitespace - this only seems to happen when the previous selection was made after selecting a value in the dropdown while the search text matches multiple entries, like "Sleep" above.
After the box got cleared, I can make a new selection from the dropdown and it will work as expected (except the VBE won't actually activate the CodePane I'm setting the selection to, but that's a separate issue).
The command implementation simply raises a Navigate event that passes a Declaration to the code that owns the VM instance.
The Search method, for which I need to add a .Take(50) after the .Select, to limit the number of returned results and perhaps reduce the lag a bit:
private void Search(string value)
{
var lower = value.ToLowerInvariant();
var results = _declarations.Where(
declaration => declaration.IdentifierName.ToLowerInvariant().Contains(lower))
.OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
.Select(declaration => new SearchResult(declaration));
MatchResults = new ObservableCollection<SearchResult>(results);
}
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
Search(value);
}
}
private SearchResult _selectedItem;
public SearchResult SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
private ObservableCollection<SearchResult> _matchResults;
public ObservableCollection<SearchResult> MatchResults
{
get { return _matchResults; }
set { _matchResults = value; OnPropertyChanged(); }
}
}
There's also an IValueConverter involved, that takes the Declaration in the SearchResult and switches on the declaration's DeclarationType enum to return a pack uri that points to the .png image to use in the dropdown list.
Aaah found it. It was all in the XAML.
Right here:
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
That line doesn't belong there; binding the TextSearch.Text property instead...
TextSearch.Text="{Binding SearchString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Makes it all work as intended. No glitch, no lag. Well there is a lag when I first drop the dropdown, but that's another issue.
Lesson learned: when TextSearch is enabled on an editable combobox, don't bind the Text property, unless you want weird behavior.

ListView ObservableCollection binding

I'm having problem with binding Collection do ListView.
public static ObservableCollection<ParagonViewClass> mparagonViewList = new ObservableCollection<ParagonViewClass>();
public ObservableCollection<ParagonViewClass> paragonViewList
{
get
{
return mparagonViewList;
}
}
In method, when user add new item, I'm adding it to list:
paragonViewList.Insert(0, newPar);
Also tried with mparagonViewList.Insert(0, newPar);
Itemssource in xaml file:
<ListView Grid.Row="1" Name="paragonListView1" ItemsSource="{Binding paragonViewList}" .../>
#EDIT: Listview have DataTemplate (Grid with labels - im prettu sure that binding is ok, becouse it works with just simply setting myListVIew.ItemsSource = myLis;)
It looks like when I click on product to add to listview it does insert to database, but I cannot see that product on listview. Probably there's little stupid problem, but I cant really find it ;)
Thanks for any answers!
Looking at the code you supplied, it is hard to figure out what you are doing wrong, if anything. So, I have thrown together a little sample application that works (from the WPF point of view anyway).
My model is called ItemModel, rather than ParagonViewClass, and is defined as follows
public class ItemModel
{
public ItemModel() { }
public string Text { get; set; }
}
My Xaml is
<Window x:Class="StackOverflow._20799346.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="clr-namespace:StackOverflow.Common;assembly=StackOverflow.Common"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Button Content="Add Item" Click="AddItem_OnClick" />
</StackPanel>
<ListView ItemsSource="{Binding Path=Items}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type common:ItemModel}">
<TextBlock Text="{Binding Path=Text}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DockPanel>
</Window>
Note the DataContext being bound to RelativeSource Self, allowing the code behind to be used as the ViewModel. I usually prefer to create a separate ViewModel class but this approach has its advantages as one can event directly from a control into the ViewModel, rather than mucking around with commands.
The code behind, now the view model, looks like
public partial class MainWindow : Window
{
private ObservableCollection<ItemModel> items;
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<ItemModel> Items { get { return items ?? (items = new ObservableCollection<ItemModel>()); } }
private void AddItem_OnClick(object sender, RoutedEventArgs e)
{
Items.Add(new ItemModel() { Text = Items.Count.ToString(CultureInfo.CurrentCulture) });
}
}
I have utilised a lazy load technique on the Items property. It will only be instantiated when it is accessed. For simplicity, when the Add Item button is clicked, I am adding a new item with its text set to the count of the Items collection.
You should be able to past this code into a new WPF application project, fix the namespacing in the xaml file and run it.
Now, as hinted at above by Rohit Vats, the Items property does not require a Setter. The ObservableCollection itself notifies the WPF binding subsystem when an item has been added or removed via both the INotifyPropertyChanged and INotifyCollectionChanged interfaces, both of which it implements.
I know this does not directly answer your question but with out further information (ie code) about the original problem, it is not possible to know what is going wrong.
Hopefully the example helps.
NOTE: I have removed exception management for brevity.

Storing properties of objects in C#/WPF

I'm allowing user to drag/drop some objects from a toolbox and of course each object has a unique id. As soon as object is used, let's say placed on a grid or canvas, I need to show its properties so I need an array of objects where each object can hold its own properties.
Can you give me some advice and direction on how to implement a class to handle multiple objects while each object can hold on to let's say 10 properties?
The best solution is to use a PropertyGrid control; your application looks similar to Visual Studio and your implementation will be similar to that.
Have a look at this SO question for available PropertyGrid options you have -
Is there a Property Dialog control that i can use in my WPF App?
Now you can define a class for each control and declare normal CLR properties for that control; properties you don't want to display in PropertyGrid can be marked with BrowsableAttribute and PropertyGrid will honor that.
In case you want more control over what properties are displayed, you can create your own custom attribute and modify PropertyGrid implementation to use that attribute and display properties marked with this attribute.
Can you give me some advice and direction on how to implement a class
to handle multiple objects while each object can hold on to let's say
10 properties?
There is no need for you to implement such a class. The way I would handle this problem would be to have a common base class for all the objects in the toolbox (ToolboxItem for example) which only exposes properties and functionality common to all items in the toolbox.
public abstract class ToolboxItem
{
public string Name { get; set; }
public Point Position { get; set; }
}
You can then derive your specific items from this class E.G. TextToolboxItem and RectangleToolboxItem (or whatever you want). The derived classes can then expose only the properties they require.
public class TextToolboxItem : ToolboxItem
{
public string Text { get; set; }
}
public class RectangleToolboxItem : ToolboxItem
{
public Rect Bounds { get; set; }
}
To store these you could just use a generic collection such as:
ObservableCollection<ToolboxItem> items = new ObservableCollection<ToolboxItems>();
As long as the items derive from ToolboxItem they can all be held within the single collection and the individual properties can all be bound to using WPF's data binding features.
You can then create and expose the data in the following way:
public partial class MainWindow : Window
{
private ObservableCollection<ToolboxItem> items;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
items = new ObservableCollection<ToolboxItem>
{
new TextToolboxItem { Name = "primaryText",
Text = "Hello world",
Position = new Point(40, 130) },
new TextToolboxItem { Name = "secondaryText",
Text = "Hello world (again)",
Position = new Point(200, 30) },
new RectangleToolboxItem { Position = new Point(50,300),
Name = "Rect1",
Bounds = new Rect(0, 0, 150, 85) },
};
}
public ObservableCollection<ToolboxItem> Items { get { return items; } }
}
To display this information in the user interface I would do the following:
Use a grid to split the view into two sections. The first is where the properties of the selected item will be displayed and the second displays the 'design surface'
Use a ContentPresenter to display the properties of the selected item.
Use a ListBox with a custom ItemsPanel and ItemContainerStyle to 'draw' your items onto the design surface.
Use a DataTemplate to tell WPF how to render each item in both the 'property grid' and the 'design surface' (This post describes how to use a different DataTemplate for different objects).
The xaml required to achieve this is shown below:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="7*" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding ElementName=listBox, Path=SelectedItem}"
Margin="5">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type this:TextToolboxItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Position}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type this:RectangleToolboxItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Position}"/>
<TextBlock Text="{Binding Bounds}"/>
</StackPanel>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
<ListBox x:Name="listBox" Grid.Column="1"
Margin="5" ItemsSource="{Binding Items}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type this:TextToolboxItem}">
<TextBox Text="{Binding Text}"
Margin="10"/>
</DataTemplate>
<DataTemplate DataType="{x:Type this:RectangleToolboxItem}">
<Rectangle Width="{Binding Bounds.Width}"
Height="{Binding Bounds.Height}"
Stroke="DarkRed" Fill="Pink"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
The end result looks like this:
Notice that the properties of the selected item are shown in the left hand section of the window.
Now this solution is currently very crude but does demonstrate a starting point for you to develop this further. Ideas for improvement include:
Re-factoring the code into a viewModel so that it is MVVM compliant.
Handling drag and drop of the items on the 'design surface'.
Changing the `ContentPresenter' for a property grid to give you much richer support for displaying and editing the properties of the selected object.

Categories

Resources