龙空技术网

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

opendotnet 171

前言:

此刻兄弟们对“columns隐藏列”可能比较着重,同学们都需要知道一些“columns隐藏列”的相关知识。那么小编同时在网摘上收集了一些有关“columns隐藏列””的相关内容,希望同学们能喜欢,朋友们一起来了解一下吧!

引言

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

预想方案

像这样:

先在ViewModel创建数据源 People 和控制列隐藏的 IsVisibility,这里直接以 MainWindow 为 DataContext

 public partial class MainWindow : Window, INotifyPropertyChanged

{

public MainWindow()

{

InitializeComponent();

Persons = new ObservableCollection<Person>() { new Person() { Age = 11, Name = "Peter" }, new Person() { Age = 19, Name = "Jack" } };

DataContext = this;

}

public event PropertyChangedEventHandler? PropertyChanged;

public void OnPropertyChanged([CallerMemberName] string propertyName = )

{

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

}

private bool isVisibility;

public bool IsVisibility

{

get => isVisibility;

set

{

isVisibility = value;

OnPropertyChanged(nameof(IsVisibility));

}

}

private ObservableCollection<Person> persons;

public ObservableCollection<Person> Persons

{

get { return persons; }

set { persons = value; OnPropertyChanged(); }

}

}

然后创建 VisibilityConverter,将布尔值转化为 Visibility。

 public class VisibilityConverter : IValueConverter

{

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

{

if (value is bool isVisible && isVisible)

{

return Visibility.Visible;

}

return Visibility.Collapsed;

}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

{

throw new NotImplementedException();

}

}

然后再界面绑定 IsVisibility,且使用转化器转化为Visibility,最后增加一个 CheckBox 控制是否显示列。

<Grid>

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="1*" />

<ColumnDefinition Width="1*" />

</Grid.ColumnDefinitions>

<DataGrid

x:Name="dataGrid"

AutoGenerateColumns="False"

CanUserAddRows="False"

ItemsSource="{Binding Persons}"

SelectionMode="Single">

<DataGrid.Columns>

<DataGridTextColumn

Header="年龄"

Width="*"

Binding="{Binding Age}"

Visibility="{Binding DataContext.IsVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type Window}}, Converter={StaticResource VisibilityConverter}}" />

<DataGridTextColumn Header="姓名" Width="*" Binding="{Binding Name}" />

</DataGrid.Columns>

</DataGrid>

<CheckBox

Grid.Column="1"

Content="是否显示年龄列"

IsChecked="{Binding IsVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

</Grid>

</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 类,实现我们的功能,如下:

public class CustomFreezable : Freezable

{

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(CustomFreezable));

public object Value

{

get => (object)GetValue(ValueProperty);

set => SetValue(ValueProperty, value);

}

protected override void OnChanged()

{

base.OnChanged();

}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)

{

base.OnPropertyChanged(e);

}

protected override Freezable CreateInstanceCore()

{

return new CustomFreezable();

}

}

然后在 Xaml 添加 customFreezable 资源,给 DataGridTextColumn 的 Visibility 绑定资源

<Window.Resources>

<local:VisibilityConverter x:Key="VisibilityConverter" />

<local:CustomFreezable x:Key="customFreezable" Value="{Binding IsVisibility, Converter={StaticResource VisibilityConverter}}" />

</Window.Resources>

<Grid>

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="1*" />

<ColumnDefinition Width="1*" />

</Grid.ColumnDefinitions>

<DataGrid

x:Name="dataGrid"

AutoGenerateColumns="False"

CanUserAddRows="False"

ItemsSource="{Binding Persons}"

SelectionMode="Single">

<DataGrid.Columns>

<DataGridTextColumn

x:Name="personName"

Width="*"

Binding="{Binding Age}"

Header="年龄"

Visibility="{Binding Value, Source={StaticResource customFreezable}}" />

<DataGridTextColumn

Width="*"

Binding="{Binding Name}"

Header="姓名" />

</DataGrid.Columns>

</DataGrid>

<CheckBox

Grid.Column="1"

Content="是否显示年龄列"

IsChecked="{Binding IsVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

</Grid>

</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?:

Freezable Objects Overview:;wt.mc_id=MVP

标签: #columns隐藏列 #aspnetdatagrid数据绑定