WPF --- 如何以Binding方式隐藏DataGrid列
引言
如題,如何以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 = null)
{
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轉換器,實現了預期功能。
如果大家有更優雅的方案,歡迎留言討論。
參考
* - how to hide wpf datagrid columns depending on a propert?: https://*.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
總結
以上是生活随笔為你收集整理的WPF --- 如何以Binding方式隐藏DataGrid列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 盒豆腐怎么做好吃啊?
- 下一篇: .NET周刊【11月第3期 2023-1