基于数据类型设置WPF样式?

6

这是一个问题。我正在绑定一个TreeView,其中包含几种不同类型的对象。每个对象都是一个节点,有些对象具有名为IsNodeExpanded的属性,当然,有些对象没有。这是我的样式:

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}" />
</Style>

现在的问题是,当绑定没有此属性的项目时,我们会在输出中得到以下错误:
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')

当然我们会得到很多次。所以我正在尝试想出一种基于TreeviewItem所持有的数据类型来切换其样式的方法。有任何想法吗?

一些信息:我无法为每个项手动执行此操作,因为我不是在XAML中创建它们,它们是从数据源动态创建的。

编辑:我找到了this answer但对我没用。

4个回答

7
尝试使用 TreeView.ItemContainerStyleSelector 属性和自定义的 StyleSelector 类,根据绑定对象是否具有该属性来更改样式。
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;
    }
}

在XAML资源中:
<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}" />

在XAML中:
<TreeView ItemsSource="{Binding ...}"
          ItemContainerStyleSelector="{StaticResource TreeItemStyleSelector}">

我其实很喜欢这个想法...明天我会试一试。谢谢! - Carlo

1

更新

<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>

替代方案

与其切换样式,您应该使用HierarchicalDataTemplate和DataTemplate来为TreeViewItem设置样式,它们的工作方式类似,除非您想更改某些继承的框架属性。

您可以根据绑定到TreeView项模板的不同对象类型定义不同的“DataTemplate”和“HeirarchicalDataTemplate”。

这就是为什么这些模板被设计成完全分离您的UI逻辑和代码后端,使用Selector等任何此类编码,您将在代码后端中引入更多的UI依赖,而WPF并不打算这样做。

这里是链接, TreeView DataBinding

请看如何在资源中定义项目模板,

<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>

这并没有真正回答问题 - 他想要在样式之间进行切换。 - codekaizen
我们让请求者自己决定这个答案是否对他可接受。当HierarchicalDataTemplate被设计用来实现他所需要的功能时,它是一个更好、更完美的选择,而不是使用Selector。编写Selectors比较复杂,因为你必须在代码后台中编写更多的内容才能使其正常工作,这违反了完整的设计/代码分离原则。无论如何,这只是一种替代方案,而不是错误的答案。 - Akash Kava
是的,我有一个DataTemplate或HierarchicalDataTemplate适用于多种类型的对象,树中有5个不同的对象,其中1个主对象嵌套2个对象,这两个对象又嵌套其他3个对象,所以树比较复杂。我仍然不明白你的答案如何解决我的IsNodeExpanded属性问题。也许我漏掉了什么? - Carlo
HierarchicalDataTemplate有一个名为“ItemContainerStyle”的属性,它只适用于特定类型,因此TreeViewItem样式只能在选择性的HierarchicalDataTemplate中针对选择性类型进行指定。 - Akash Kava
哦,我现在明白了。由于我一直忙于项目的其他部分,所以还没有能够测试这些内容。一旦我得到我想要的结果,我会尽快回复你们。非常感谢你们的时间和帮助! - Carlo

0

你考虑过在绑定上使用FallbackValue吗?如果绑定失败,这将会起作用...

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay, FallbackValue=False}" />
</Style>

谢谢您的建议。不过它并没有起作用,只是从“错误”变成了“警告”:System.Windows.Data Warning: 39 blah blah - Carlo

0

基于DataTrigger的解决方案:

 <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>         

还有 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();
    }
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接