【问题标题】:WPF Setting style based on datatype?基于数据类型的WPF设置样式?
【发布时间】:2020-12-04 23:59:58
【问题描述】:

这就是问题所在。我正在将 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')

当然,我们得到了很多次。所以我试图想出一种方法来根据它拥有的 DataType 切换 TreeViewItem 的样式。关于如何做到这一点的任何想法?

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

编辑:我找到了this answer,但它对我不起作用。

【问题讨论】:

    标签: wpf data-binding styles


    【解决方案1】:

    尝试将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}">
    

    【讨论】:

    • 我真的很喜欢这个主意...明天我会试一试。谢谢!
    【解决方案2】:

    更新

    <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的Item Template可以根据绑定的对象类型不同,定义不同的“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>
    

    【讨论】:

    • 这并不能真正回答问题 - 他想在样式之间切换
    • 好吧,让请求者决定他是否可以接受这个答案,当 HierarchicalDataTemplate 旨在做他正在寻找的东西时,它是一个更好和完美的替代方案,而不是使用 Selector,Selectors 很复杂要编写,因为您必须在代码隐藏中编写更多代码才能使其工作,这违反了完整的设计/代码分离。无论如何,这是一个替代方案,这不是一个错误的答案。
    • 是的,我有一个用于多种类型对象的 DataTemplate 或 HierarchicalDataTemplate,我在树中有 5 个不同的对象,1 个主要对象嵌套 2 个,那些 2 个嵌套其他 3 个,所以树是排序的的复杂。我仍然看不到您的答案如何解决 IsNodeExpanded 属性的问题。也许我错过了什么?
    • HierarchicalDataTemplate 具有名为“ItemContainerStyle”的属性,该属性仅适用于您的特定类型,因此只能在选择性 HierarchicalDataTemplate 上为选择性类型指定 TreeViewItem 样式。
    • 哦,我现在看到了。由于我一直忙于项目的其他部分,因此我无法测试其中的任何一个。一旦我得到我正在寻找的结果,我会尽快回复你们。非常感谢您的时间和帮助!
    【解决方案3】:

    在绑定上使用FallbackValue 对您有用吗?如果绑定失败,这将适用...

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

    【讨论】:

    • 感谢您的建议。但它没有用;它只是从“错误”变为“警告”:System.Windows.Data 警告:39 blah blah
    【解决方案4】:

    基于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();
        }
    }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多