Reputation: 1811
I have a TextBlock
inside a GridViewColumn
that requires a DisplayMemberBinding
. The TextBlock
is completely unaccounted for as DisplayMemberBinding
takes precedence over CellTemplate
. However, the Textblock
has a ToolTip
that I'd like to be displayed that's specific to the column itself. I was able to move most Style settings outside the CellTemplate
as they are generic for all columns, but the ToolTip
cannot be taken outside as it requires a binding and is unique to each column.
Here is one of the columns. Everything within the GridViewColumn.CellTemplate
tags is removable due to the DisplayMemberBinding
taking precedence.
<GridViewColumn Header="Templates"
Width="200"
DisplayMemberBinding="{Binding Path=Name}>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type request:ModelDocument}">
<TextBlock ToolTip="{Binding Name}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The way I've done other Style properties is as follows. This was done before the GridView
(the GridView
is a child of the ListView
):
<ListView.Resources>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="FontFamily" Value="Segue UI Light" />
<Setter Property="FontSize" Value="13" />
</Style>
</ListView.Resources>
How can I add a unique ToolTip
to each column without removing the DisplayMemberBinding
?
Upvotes: 2
Views: 1845
Reputation: 4943
Even though there are more appropriate solutions like using a DataGrid
instead of a ListView
, I'd like to show an answer that lets you do exactly what you want with a ListView
, DisplayMemberBinding
and a ToolTip
on your TextBlocks
.
In the end I have the following XAML:
<ListView ItemsSource="{Binding MyList}">
<ListView.Resources>
<local:GridRowPresenterToDataContextPropertiesConverter x:Key="GridRowPresenterToDataContextPropertiesConverter"></local:GridRowPresenterToDataContextPropertiesConverter>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="FontFamily" Value="Segue UI Light" />
<Setter Property="FontSize" Value="13" />
<!--THIS IS THE NEW PART-->
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource GridRowPresenterToDataContextPropertiesConverter}"
ConverterParameter="Name, Value">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=GridViewRowPresenter}"/>
<Binding RelativeSource="{RelativeSource Mode=Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<!--END OF THE NEW PART-->
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Templates" Width="200" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Templates" Width="200" DisplayMemberBinding="{Binding Value}"/>
</GridView>
</ListView.View>
</ListView>
I added a simple view model class and another GridViewColumn
for testing purposes:
public class MyViewModel {
public string Name { get; set; } = "Hello.";
public string Value { get; set; } = "world.";
}
This XAML will set the Name
property of the DataContext
as ToolTip
for all TextBlocks
in the first column, and the Value
property of the DataContext
as ToolTip
for all TextBlocks
in the second column. You can handle additional columns by adding to the ConverterParameter="Name, Value"
ordered list of properties.
The key is in the definition of GridRowPresenterToDataContextPropertiesConverter
(which is one of the least straightforward and most hacky IMultiValueConverter
I might have ever written):
public class GridRowPresenterToDataContextPropertiesConverter : IMultiValueConverter {
public static T FindVisualSelfOrChildren<T>(DependencyObject parent) where T : DependencyObject {
if (parent == null) return null;
if (parent is T) return parent as T;
IEnumerable<DependencyObject> children = Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(parent)).Select(i => VisualTreeHelper.GetChild(parent, i));
foreach (var child in children) {
T result = FindVisualSelfOrChildren<T>(child);
if (result != null) return result as T;
}
return null;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
var presenter = (GridViewRowPresenter)values[0];
var targetElement = values[1] as FrameworkElement;
var sourceProperties = ((string)parameter).Split(',');
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(presenter); i++) {
var child = VisualTreeHelper.GetChild(presenter, i);
if (FindVisualSelfOrChildren<TextBlock>(child) == targetElement) {
var dataContext = targetElement.DataContext;
return dataContext.GetType().GetProperty(sourceProperties[i].Trim()).GetValue(dataContext);
}
}
return "";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
This converter takes the target element (here, it's gonna be each TextBlock
), figures out in which column of the GridView
is lives and extracts the property at the correct index from the ConverterParameter
.
This answer is more written for fun than for use in production code though, and it will need some smarter code to handle re-ordering of columns which is absolutely not supported now.
Upvotes: 1
Reputation: 9944
As it was already suggested in the comments by @icebat, a DataGrid
is more suited for such scenarios, for the sake of clarification here a simple demo implementation:
<DataGrid ItemsSource="{Binding Collection}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="TextBlock" x:Key="TbStyle">
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="FontFamily" Value="Segue UI Light" />
<Setter Property="FontSize" Value="13" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Templates" SortMemberPath="Name"
Width="200" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<TextBlock Style="{StaticResource TbStyle}" ToolTip="{Binding Name}" Text="{Binding Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Upvotes: 3