I've an issue with trying to display a tooltip for listboxitems in WPF. So here's what I have for main control:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ItemDataTemplate">
<TextBlock Text="{Binding .}" />
</DataTemplate>
<local:ItemValueToTooltipConverter x:Key="MyItemValueToTooltipConverter" />
<local:ItemValueToTooltipVisibilityConverter x:Key="MyItemValueToTooltipVisibilityConverter" />
</Grid.Resources>
<ListBox
x:Name="ItemsListBox"
ItemTemplate="{DynamicResource ItemDataTemplate}"
ItemsSource="{Binding MyItems}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding ElementName=ItemsListBox, Path=DataContext}"></Setter>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget}">
<ToolTip.Visibility>
<MultiBinding Converter="{StaticResource MyItemValueToTooltipVisibilityConverter}">
<Binding Path="Content"></Binding>
<Binding Path="Tag.ItemValueTooltipProvider"></Binding>
</MultiBinding>
</ToolTip.Visibility>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyItemValueToTooltipConverter}">
<Binding Path="Content"></Binding>
<Binding Path="Tag.ItemValueTooltipProvider"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
There's the listbox bound with some items and a style for listboxitem, what I want is to display a specific text in tooltip based on the content of a listboxitem.
ItemValueToTooltipConverter: uses the value of listboxitem and Tag.ItemValueTooltipProvider to generate the text that I want to show in the tooltip
ItemValueToTooltipVisibilityConverter: if the text for tooltip is empty Visibility is set to Collapsed otherwise it's Visible
ItemValueTooltipProvider: object in DataContext used to convert listboxitem text to tooltip text
For this example project I've initialised listbox with these values:
MyItems.Add(new ItemValue("1"));
MyItems.Add(new ItemValue("2"));
MyItems.Add(new ItemValue("3"));
MyItems.Add(new ItemValue("4"));
and hardcoded the Tag.ItemValueTooltipProvider to give back this text for the tooltip:
if (input == "1")
{
return "";
}
if (input == "2")
{
return "some more text";
}
if (input == "3")
{
return "350";
}
return input;
Now what happens is that when I hover over item: 2, it shows tooltip with "some more text"
then when I hover over item: 1, it shows no tooltip which is good since I have Collapsed for empty text
but then when I hover over item: 2 again it shows empty tooltip
and when I hover over item: 3 it shows the correct tooltip
After this hovering over items 2,3,4 will show correct tooltip, until I hover over 1 and it starts failing again.
What I've tried is to put ListBoxItem and ToolTip styles to resources, make them x:Shared=False, and then use them as DynamicResource, but it failed with: Unable to cast object of type 'System.String' to type 'System.Windows.Style.
Then I've tried setting a ControlTemplate for the ToolTip and that kind of worked (the correct text was shown all the time), but then I've lost the style of ToolTip (in real use case tooltips are also using style from 3rd party library) and I'd have to model the Control of the tooltip.
So at this point I'm not sure if I have to take the ControlTemplate route, or there is something else more simple that would fix this.
CodePudding user response:
OK so after a long fight with WPF, I've finally got it to work.
At first I've tried creating a DataTemplate for ToolTip have it as x:Shared="False" and then using it as DynamicResource on ContentTemplate ToolTip's property. But this still had issues, since if the first hover happens over item 1 (with Collapsed ToolTip), I would get an exception:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Windows.DataTemplate'.'
Then I've thought, let's see what I could do, if I'd use a ContentTemplateSelector, and that did the trick.
Here's the solution:
first the xaml part:
<DataTemplate x:Key="ToolTipContTemplate" x:Shared="False">
<TextBlock>
<TextBlock.Text>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}}" Path="DataContext" Converter="{StaticResource MyItemValueToTooltipConverter}"></Binding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
<local:MyTemplateSelector x:Key="MyTemplateSelector" MyTemplate="{StaticResource ToolTipContTemplate}" x:Shared="False"></local:MyTemplateSelector>
...
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget}" ContentTemplateSelector="{DynamicResource MyTemplateSelector}">
<ToolTip.Visibility>
<MultiBinding Converter="{StaticResource MyItemValueToTooltipVisibilityConverter}">
<Binding Path="Content"></Binding>
<Binding Path="Tag.ItemValueTooltipProvider"></Binding>
</MultiBinding>
</ToolTip.Visibility>
</ToolTip>
</Setter.Value>
</Setter>
then a very simple template selector:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate MyTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return MyTemplate;
}
}
and a bit of property work on the converter side:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
return "";
}
var tooltipProvider = ((value as ListBoxItem)?.Tag as MainWindowVm)?.ItemValueTooltipProvider;
var itemValue = (value as ListBoxItem)?.DataContext as ItemValue;
return tooltipProvider?.GetTooltip(itemValue?.Value);
}
Also the x:Shared="False" for resources and using them as DynamicResource were a must.