WPF修改类成员无法触发DependencyProperty的PropertyChangedCallback问题
我正在开发基于MVVM框架的WPF桌面应用,定义了如下枚举与类:
public enum eTypes { A, B }; public class MyClass { public eTypes MemberEnum { get; set; } }
在Model中:
public class Model : INotifyPropertyChanged { public MyClass Stiffness { get; } // 该属性必须为只读,因为它实际关联一个不允许修改的字典 private int _num; public int Num { get => _num; set { _num = value; Stiffness.MemberEnum = eTypes.A; // 修改成员 OnPropertyChanged(nameof(Stiffness)); } } public void OnPropertyChanged([CallerMemberName]string propertyName = null, bool includeEntirePropChanged = true) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; }
视图层有一个自定义UserControl:
public partial class PropertyGrid : UserControl { public static readonly DependencyProperty PropertyListProperty = DependencyProperty.Register( "PropertyList", typeof(MyClass), typeof(PropertyGrid), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnPropsListChanged))); private static void OnPropsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // 期望执行的操作 } public MyClass PropertyList { get { return (MyClass)GetValue(PropertyListProperty); } set { SetValue(PropertyListProperty, value); } } }
视图XAML代码(DataContext已正确绑定到Model实例):
<PropertyGrid PropertyList="{Binding Stiffness, UpdateSourceTrigger=PropertyChanged}" />
期望行为:当用户修改Model的Num属性时,Stiffness的MemberEnum被修改,触发OnPropertyChanged(nameof(Stiffness)),进而触发View中PropertyList绑定的OnPropsListChanged回调以更新视图。
实际问题:OnPropsListChanged从未被调用,原因是DependencyProperty的"有效值变更"检测认为Stiffness对象引用未变(即便其成员变量已修改)。目前临时解决方案是克隆Stiffness对象后再触发OnPropertyChanged,但该方式易出错。尝试为PropertyListProperty添加FrameworkPropertyMetadataOptions.AffectsRender和FrameworkPropertyMetadataOptions.BindsTwoWayByDefault标记,均无效。
想知道:是否有办法修改DependencyObject.SetValue的「有效值检查」逻辑?
首先明确:无法修改WPF内部的DependencyProperty有效值检查逻辑,它的核心判定依据是对象引用是否变更,这是WPF依赖属性系统的底层设计,无法自定义修改。针对你的场景,推荐以下几种更可靠的替代方案:
1. 让MyClass实现INotifyPropertyChanged接口
这是最符合MVVM规范的方案,当MyClass的内部属性变化时主动通知变更,控件可直接响应内部属性的变化:
public class MyClass : INotifyPropertyChanged { private eTypes _memberEnum; public eTypes MemberEnum { get => _memberEnum; set { if (_memberEnum != value) { _memberEnum = value; OnPropertyChanged(nameof(MemberEnum)); } } } public void OnPropertyChanged([CallerMemberName]string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; }
随后在自定义控件PropertyGrid中,订阅PropertyList对象的PropertyChanged事件,在成员属性变化时执行更新逻辑:
private static void OnPropsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = d as PropertyGrid; // 取消订阅旧对象的事件 if (e.OldValue is MyClass oldObj) { oldObj.PropertyChanged -= control.OnMyClassPropertyChanged; } // 订阅新对象的事件 if (e.NewValue is MyClass newObj) { newObj.PropertyChanged += control.OnMyClassPropertyChanged; // 初始加载时执行一次更新 control.UpdateView(newObj); } } private void OnMyClassPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(MyClass.MemberEnum)) { UpdateView(sender as MyClass); } } private void UpdateView(MyClass data) { // 这里执行你的视图更新逻辑 }
2. 使用MultiBinding传递触发信号
无需修改MyClass结构,通过MultiBinding将Stiffness和Num属性绑定在一起,当Num变化时强制触发DependencyProperty的变更回调:
<PropertyGrid> <PropertyGrid.PropertyList> <MultiBinding Converter="{StaticResource StiffnessConverter}"> <Binding Path="Stiffness" /> <Binding Path="Num" /> </MultiBinding> </PropertyGrid.PropertyList> </PropertyGrid>
转换器实现:
public class StiffnessConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { return values[0]; // 返回Stiffness对象 } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
当Num属性变化时,MultiBinding会重新计算并返回Stiffness对象,从而触发OnPropsListChanged回调。
内容的提问来源于stack exchange,提问作者Michael Nguyen

