翼度科技»论坛 编程开发 .net 查看内容

WPF --- 如何以Binding方式隐藏DataGrid列

3

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
引言

如题,如何以Binding的方式动态隐藏DataGrid列?
预想方案

像这样:
先在ViewModel创建数据源 People 和控制列隐藏的 IsVisibility,这里直接以 MainWindow 为 DataContext
  1. public partial class MainWindow : Window, INotifyPropertyChanged
  2. {
  3.      public MainWindow()
  4.      {
  5.          InitializeComponent();
  6.          Persons = new ObservableCollection<Person>() { new Person() { Age = 11, Name = "Peter" }, new Person() { Age = 19, Name = "Jack" } };
  7.          DataContext = this;
  8.      }
  9.      public event PropertyChangedEventHandler? PropertyChanged;
  10.      public void OnPropertyChanged([CallerMemberName] string propertyName = null)
  11.      {
  12.          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  13.      }
  14.      private bool isVisibility;
  15.      
  16.      public bool IsVisibility
  17.      {
  18.          get => isVisibility;
  19.          set
  20.          {
  21.              isVisibility = value;
  22.              OnPropertyChanged(nameof(IsVisibility));
  23.          }
  24.      }
  25.      private ObservableCollection<Person> persons;
  26.      public ObservableCollection<Person> Persons
  27.      {
  28.          get { return persons; }
  29.          set { persons = value; OnPropertyChanged(); }
  30.      }
  31. }
复制代码
然后创建 VisibilityConverter,将布尔值转化为 Visibility。
  1. public class VisibilityConverter : IValueConverter
  2. {
  3.      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  4.      {
  5.          if (value is bool isVisible && isVisible)
  6.          {
  7.              return Visibility.Visible;
  8.          }
  9.          return Visibility.Collapsed;
  10.      }
  11.      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  12.      {
  13.          throw new NotImplementedException();
  14.      }
  15. }
复制代码
然后再界面绑定 IsVisibility,且使用转化器转化为Visibility,最后增加一个 CheckBox 控制是否隐藏列。
  1. <Grid>
  2.     <Grid>
  3.         <Grid.ColumnDefinitions>
  4.             <ColumnDefinition Width="1*" />
  5.             <ColumnDefinition Width="1*" />
  6.         </Grid.ColumnDefinitions>
  7.         <DataGrid
  8.             x:Name="dataGrid"
  9.             AutoGenerateColumns="False"
  10.             CanUserAddRows="False"
  11.             ItemsSource="{Binding Persons}"
  12.             SelectionMode="Single">
  13.             <DataGrid.Columns>
  14.                 <DataGridTextColumn
  15.                     Header="年龄"               
  16.                     Width="*"
  17.                     Binding="{Binding Age}"
  18.                     Visibility="{Binding DataContext.IsVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type Window}}, Converter={StaticResource VisibilityConverter}}" />
  19.                 <DataGridTextColumn Header="姓名" Width="*" Binding="{Binding Name}" />
  20.             </DataGrid.Columns>
  21.         </DataGrid>
  22.         <CheckBox
  23.             Grid.Column="1"
  24.             Content="是否显示年龄列"
  25.             IsChecked="{Binding IsVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
  26.     </Grid>
  27. </Grid>
复制代码
这样应该没问题,Visibility 是依赖属性,能直接通过 Binding 的方式赋值。
但实际测试时就会发现,勾选 CheckBox 能够改变 DataContext.IsVisibility 的值,但是无法触发转换器 VisibilityConverter,即使不用 RelativeSource 方式,更改为指定 ElementName获取元素的方式,也一样不生效。
这是为什么呢?
我疑惑了很久,直到看到了Visual Studio中的实时可视化树:

从图中可以看出,虽然我在 Xaml 中声明了两列 DataGridTextColumn,但他根本不在可视化树中。
获取 RelativeSource 和指定 ElementName 的方式,本质上还是在可视化树中寻找元素,所以上述方案无法生效。
那为什么 DataGridTextColumn 不在可视化树中呢?
可视化树(Visula Tree)

在上面那个问题之前,先看看什么是可视化树?
我们先从微软文档来看一下WPF中其他控件的继承树。
比如  Button

比如 DataGrid :

又比如 ListBox :

大家可以去看看其他的控件,几乎 WPF 中所有的控件都继承自 Visual(例如,Panel、Window、Button 等都是由 Visual 对象构建而成)。
Visual 是 WPF 中可视化对象模型的基础,而 Visual 对象通过形成可视化树(Visual Tree)来组织所有可视化模型。所以Visual Tree 是一个层次结构,包含了所有界面元素的视觉表示。所有继承自 Visual 或 UIElement(UI 元素的更高级别抽象)的对象都存在于可视化树中。
但是,DataGridColumn 是一个特例,它不继承 Visual,它直接继承 DependencyObject,如下:

所以,DataGridColumn的继承树就解答了他为什么不在可视化树中。
解决方案

所以,通过直接找 DataContext 的方式,是不可行的,那就曲线救国。
既然无法找到承载 DataContext.IsVisibility 的对象,那就创建一个能够承载的对象。首先该对象必须是 DependencyObject 类型或其子类,这样才能使用依赖属性在 Xaml 进行绑定,其次必须有属性变化通知功能,这样才能触发 VisibilityConverter,实现预期功能。
这时候就需要借助一个抽象类 System.Windows.Freezable。摘取部分官方解释如下:


从文档中可以看出 Freezable 非常符合我们想要的,第一它本身继承 DependencyObject 且 它在子属性值更改时能够提供变化通知。
所以我们可以创建一个自定义 Freezable 类,实现我们的功能,如下:
  1. public class CustomFreezable : Freezable
  2. {
  3.     public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(CustomFreezable));
  4.     public object Value
  5.     {
  6.         get => (object)GetValue(ValueProperty);
  7.         set => SetValue(ValueProperty, value);
  8.     }
  9.     protected override void OnChanged()
  10.     {
  11.         base.OnChanged();
  12.     }
  13.    
  14.     protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
  15.     {
  16.         base.OnPropertyChanged(e);
  17.     }
  18.     protected override Freezable CreateInstanceCore()
  19.     {
  20.         return new CustomFreezable();
  21.     }
  22. }
复制代码
然后在 Xaml 添加 customFreezable 资源,给 DataGridTextColumn 的 Visibility 绑定资源
  1. <Grid>
  2.     <Grid>
  3.         <Grid.ColumnDefinitions>
  4.             <ColumnDefinition Width="1*" />
  5.             <ColumnDefinition Width="1*" />
  6.         </Grid.ColumnDefinitions>
  7.         <DataGrid
  8.             x:Name="dataGrid"
  9.             AutoGenerateColumns="False"
  10.             CanUserAddRows="False"
  11.             ItemsSource="{Binding Persons}"
  12.             SelectionMode="Single">
  13.             <DataGrid.Columns>
  14.                 <DataGridTextColumn
  15.                     Header="年龄"               
  16.                     Width="*"
  17.                     Binding="{Binding Age}"
  18.                     Visibility="{Binding DataContext.IsVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type Window}}, Converter={StaticResource VisibilityConverter}}" />
  19.                 <DataGridTextColumn Header="姓名" Width="*" Binding="{Binding Name}" />
  20.             </DataGrid.Columns>
  21.         </DataGrid>
  22.         <CheckBox
  23.             Grid.Column="1"
  24.             Content="是否显示年龄列"
  25.             IsChecked="{Binding IsVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
  26.     </Grid>
  27. </Grid>        
复制代码
测试:
勾选后,显示年龄列:

取消勾选后,隐藏年龄列

小结

本篇文章中,首先探索了 DataGridTextColumn 为什么不在可视化树结构内,是因为所有继承自 Visual 或 UIElement(UI 元素的更高级别抽象)的对象才存在于可视化树中。,DataGridTextColumn是直接继承DependencyObject ,所以才不在可视化树结构内。
其次探索如何通过曲线救国,实现以 Binding 的方式实现隐藏DataGridTextColumn,我们借助了一个核心抽象类  System.Windows.Freezable。该抽象类是 DependencyObject 的子类,能使用依赖属性在 Xaml 进行绑定,且有属性变化通知功能,触发 VisibilityConverter转换器,实现了预期功能。
如果大家有更优雅的方案,欢迎留言讨论。
参考
stackoverflow - how to hide wpf datagrid columns depending on a propert?: https://stackoverflow.com/questions/6857780/how-to-hide-wpf-datagrid-columns-depending-on-a-property
Freezable Objects Overview: https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/freezable-objects-overview?view=netframeworkdesktop-4.8&wt.mc_id=MVP

来源:https://www.cnblogs.com/pandefu/archive/2023/11/21/17847807.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具