WPF Setting style based on datatype?
Here's the problem. I'm binding a TreeView with a few different types of objects. Each object is a node, and SOME objects have a property called IsNodeExpanded, and of course, some others don't. Here's my style:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}" />
</Style>
Now, the problem is when binding the items that DON'T have this property, we get this error in the output:
System.Windows.Data Error: 39 : BindingExpression path error: 'IsNodeExpanded' property not found on 'object' ''CompensationChannel' (HashCode=56992474)'. BindingExpression:Path=IsNodeExpanded; DataItem='CompensationChannel' (HashCode=56992474); target element is 'TreeViewItem' (Name=''); target property is 'IsExpanded' (type 'Boolean')
Of course we get it a ton of times. So I'm trying to come up with a way to switch the style of the TreeViewItem based on the DataType it holds. Any idea on how to do this?
Some info: I can't do it manually for each item because I'm not creating them in XAML, they are created dy开发者_高级运维namically from a data source.
EDIT: I found this answer but it didn't work for me.
Try using the TreeView.ItemContainerStyleSelector
property with a custom StyleSelector
class which changes the style depending if the bound object has that property or not.
public class TreeItemStyleSelector : StyleSelector
{
public Style HasExpandedItemStyle { get; set; }
public Style NoExpandedItemStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
// Choose your test
bool hasExpandedProperty = item.GetType().GetProperty("IsExpanded") != null;
return hasExpandedProperty
? HasExpandedItemStyle
: NoExpandedItemStyle;
}
}
In the XAML Resources:
<Style x:Key="IsExpandedStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}" />
</Style>
<Style x:Key="NoExpandedStyle" TargetType="{x:Type TreeViewItem}">
</Style>
<x:TreeViewItemStyleSelector x:Key="TreeViewItemStyleSelector"
HasExpandedItemStyle="{StaticResource IsExpandedStyle}"
NoExpandedItemStyle="{StaticResource NoExpandedStyle}" />
In the XAML:
<TreeView ItemsSource="{Binding ...}"
ItemContainerStyleSelector="{StaticResource TreeItemStyleSelector}">
UPDATE
<TreeView.Resources>
... following is only for one type of data
<HierarchicalDataTemplate
DataType="{x:Type local:RegionViewModel}"
ItemsSource="{Binding Children}"
>
... define your style
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}"
... following line is necessary
BasedOn="{StaticResource {x:Type TreeViewItem}}">
..... your binding stuff....
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
...
</TreeView.Resources>
ALTERNATIVE WAY
Instead of Switching Styles, you should use HierarchicalDataTemplate and DataTemplate in order to style your TreeViewItem, they will work similarly unless you want to change certain inherited framework properties.
You can define different "DataTemplate" and "HeirarchicalDataTemplate" based on different types of object that are bound to for Item Template of TreeView.
And that is why these templates are designed to completely seperate your UI logic and code behind, using Selector etc or any such coding, you will introduce UI dependency more on your code behind, which WPF is not intended for.
Here is the link, TreeView DataBinding
And see how you can define item templates in resources,
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:RegionViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:StateViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\State.png" />
<TextBlock Text="{Binding StateName}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:CityViewModel}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\City.png" />
<TextBlock Text="{Binding CityName}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
Would using a FallbackValue on the binding work for you? This would apply if the binding fails...
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay, FallbackValue=False}" />
</Style>
DataTrigger-based solution:
<UserControl.Resources>
<converters:DataTypeConverter x:Key="DataTypeConverter"/>
</UserControl.Resources>
<!-- .... -->
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource DataTypeConverter}}"
Value="{x:Type yourClasses:ClassWithIsNodeExpanded}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded}" />
</DataTrigger>
</Style>
And DataTypeConverter:
namespace Converters
{
/// <summary>
/// Implement an IValueConverter named DataTypeConverter, which accepts an object and returns its Type(as a
/// System.Type):
/// Usage:
/// Change your DataTrigger to use the Converter, and set the value to the Type:
/// <DataTrigger Binding="{Binding SelectedItem,
/// Converter={StaticResource DataTypeConverter}}"
/// Value="{x:Type local:MyType}">
/// ...
/// </DataTrigger>
/// Declare DataTypeConverter in the resources:
/// <UserControl.Resources>
/// <v:DataTypeConverter x:Key="DataTypeConverter"></v:DataTypeConverter>
/// </UserControl.Resources>
/// </summary>
[ValueConversion(typeof(object), typeof(Type))]
public class DataTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
精彩评论