DataGridColumnHeader模板中附加属性不更新的问题排查
WPF DataGridColumnHeader模板中附加属性变更通知失效问题排查与解决
问题背景
我需要给WPF DataGrid的DataGridColumnHeader制作自定义模板,模板需从附加属性读取数据(比如列筛选状态、标题旋转角度这类)。由于DataGrid列不继承DataContext,我使用BindingProxy类实现跨上下文的数据绑定。目前其他绑定均正常,但唯独模板内的附加属性绑定不响应属性变更——同一个属性用其他方式绑定能实时更新,只有模板里的没反应。
复现代码(.NET Framework 4.7.1)
XAML部分
<Window x:Class="DataGridColumnHeaderBindingIssue.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:DataGridColumnHeaderBindingIssue" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <local:BindingProxy x:Key="ViewModelProxy" Data="{Binding}" /> <Style x:Key="CustomColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type DataGridColumnHeader}"> <Border Background="LightGray" Padding="5"> <!-- 此绑定不响应附加属性变更 --> <TextBlock Text="{Binding Path=(local:ColumnProperties.FilterStatus), Source={StaticResource ViewModelProxy}}" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}" HeaderStyle="{StaticResource CustomColumnHeaderStyle}" /> <DataGridTextColumn Header="Value" Binding="{Binding Value}" /> </DataGrid.Columns> </DataGrid> <!-- 此绑定可正常响应变更 --> <TextBlock Text="{Binding Path=(local:ColumnProperties.FilterStatus), Source={StaticResource ViewModelProxy}}" Margin="10,10,0,0" VerticalAlignment="Top" /> <Button Content="Toggle Filter Status" Click="Button_Click" Margin="10,40,0,0" VerticalAlignment="Top" /> </Grid> </Window>
C#部分
using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Controls; namespace DataGridColumnHeaderBindingIssue { public class BindingProxy : Freezable { protected override Freezable CreateInstanceCore() { return new BindingProxy(); } public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy)); public object Data { get { return GetValue(DataProperty); } set { SetValue(DataProperty, value); } } } public static class ColumnProperties { public static readonly DependencyProperty FilterStatusProperty = DependencyProperty.RegisterAttached( "FilterStatus", typeof(string), typeof(ColumnProperties), new PropertyMetadata("Not Filtered", OnFilterStatusChanged)); public static string GetFilterStatus(DependencyObject obj) { return (string)obj.GetValue(FilterStatusProperty); } public static void SetFilterStatus(DependencyObject obj, string value) { obj.SetValue(FilterStatusProperty, value); } private static void OnFilterStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // 此处可触发,但模板绑定无响应 } } public class ViewModel : INotifyPropertyChanged { public List<Item> Items { get; set; } = new List<Item> { new Item { Name = "Item 1", Value = "100" }, new Item { Name = "Item 2", Value = "200" } }; private string _filterStatus = "Not Filtered"; public string FilterStatus { get => _filterStatus; set { _filterStatus = value; OnPropertyChanged(nameof(FilterStatus)); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); } } public class Item { public string Name { get; set; } public string Value { get; set; } } public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new ViewModel(); } private void Button_Click(object sender, RoutedEventArgs e) { var vm = DataContext as ViewModel; vm.FilterStatus = vm.FilterStatus == "Not Filtered" ? "Filtered" : "Not Filtered"; ColumnProperties.SetFilterStatus(vm, vm.FilterStatus); } } }
问题原因分析
- BindingProxy的局限性:默认的BindingProxy仅传递Data对象的引用,不会监听Data对象上附加属性的变更。当附加属性值改变时,Proxy本身不会触发任何变更通知,导致模板内的绑定无法感知变化。
- 模板绑定的上下文特性:
DataGridColumnHeader模板初始化时,绑定的是Proxy的Data引用,但附加属性的变更不会触发Data属性的变更通知,因此绑定不会更新。
解决方案
方案1:让BindingProxy实现INotifyPropertyChanged并监听附加属性变更
修改BindingProxy类,使其能监听Data对象上的附加属性变化,并主动触发PropertyChanged通知:
public class BindingProxy : Freezable, INotifyPropertyChanged { protected override Freezable CreateInstanceCore() { return new BindingProxy(); } public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null, OnDataChanged)); public object Data { get { return GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public event PropertyChangedEventHandler PropertyChanged; private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var proxy = d as BindingProxy; proxy?.OnPropertyChanged("Data"); // 移除旧对象的附加属性监听 if (e.OldValue != null) { DependencyPropertyDescriptor.FromProperty(ColumnProperties.FilterStatusProperty, typeof(ViewModel)) .RemoveValueChanged(e.OldValue, proxy.OnFilterStatusChanged); } // 为新对象添加附加属性监听 if (e.NewValue != null) { DependencyPropertyDescriptor.FromProperty(ColumnProperties.FilterStatusProperty, typeof(ViewModel)) .AddValueChanged(e.NewValue, proxy.OnFilterStatusChanged); } } private void OnFilterStatusChanged(object sender, EventArgs e) { // 触发Data属性的变更通知,让绑定更新 OnPropertyChanged("Data"); } private void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
方案2:调整模板中的绑定路径
在模板的绑定中,明确绑定到Proxy的Data对象上的附加属性,并指定Mode=OneWay确保单向绑定更新:
<TextBlock Text="{Binding Path=Data.(local:ColumnProperties.FilterStatus), Source={StaticResource ViewModelProxy}, Mode=OneWay}" />
验证效果
修改后点击按钮切换筛选状态,模板内的TextBlock会与页面上的TextBlock同步更新,附加属性的变更通知可正常触发到模板绑定。
内容的提问来源于stack exchange,提问作者Adam Mroczek
相关产品推荐
相关产品推荐

