ExpandoObject绑定PropertyGrid后PropertyChanged事件未触发问题
问题根因
ExpandoObject 原生实现的 INotifyPropertyChanged 机制本身没有问题,事件无法触发的核心原因是属性通知链被自定义类型描述层隔断了:
- 直接给 ExpandoObject 挂载事件时,只有直接操作 ExpandoObject 实例本身增删改属性,才会触发原生通知
- 当你通过自定义
ICustomTypeDescriptor、PropertyDescriptor封装实例再绑定给PropertyGrid时,PropertyGrid 对属性的所有读写操作都是通过你返回的 PropertyDescriptor 层完成的,不会直接和底层 ExpandoObject 交互,原生的属性变更事件自然不会触发 - 你尝试添加的
<RefreshProperties(RefreshProperties.All)>特性不生效的逻辑也很直接:这个特性的作用是告知 PropertyGrid “当前属性修改后需要刷新全表”,生效前提是 PropertyGrid 能捕获到当前属性的变更动作,现在变更通知都传不到 PropertyGrid,加特性自然没有效果。这个特性不需要在 PropertyDescriptor 里重写特殊逻辑,只要把它加到你自定义 PropertyDescriptor 的特性集合里就能被正常识别,核心问题始终是通知链断裂。
落地解决方案
- 方案一(最稳妥,推荐):放弃直接绑定原生 ExpandoObject,自己写一个动态包装类,同时实现
ICustomTypeDescriptor和INotifyPropertyChanged接口,内部用字典维护所有属性值、属性的可浏览/只读等状态。
在自定义 PropertyDescriptor 的SetValue方法里,写完属性值后主动触发包装类的 PropertyChanged 事件;如果修改的是需要联动其他属性状态的触发属性,直接调用 PropertyGrid 实例的Refresh()方法强制刷新,完全不需要依赖 ExpandoObject 自带的通知逻辑,可控性最高。
核心逻辑参考代码:Public Overrides Sub SetValue(component As Object, value As Object) ' 更新内部属性值存储 _internalValueStore(_currentPropertyName) = value ' 触发当前属性的变更通知 Dim wrapper = CType(component, DynamicWrapper) wrapper.RaisePropertyChanged(_currentPropertyName) ' 如果当前属性是联动触发属性,更新其他属性的Browsable/ReadOnly状态后刷新网格 If _isLinkageTrigger Then UpdateOtherPropertyStates() ' 自定义联动状态更新逻辑 _boundPropertyGrid.Refresh() End If End Sub - 方案二(保留原生ExpandoObject的适配写法):如果一定要用原生 ExpandoObject 作为底层存储,就不要在外部给 ExpandoObject 挂事件,把事件监听和触发逻辑放到自定义 PropertyDescriptor 里。
在重写的SetValue方法中,先把值写入 ExpandoObject 对应的属性,再主动抛出 PropertyChanged 通知,同时给需要触发全表刷新的 PropertyDescriptor 加上<RefreshProperties(RefreshProperties.All)>特性,PropertyGrid 识别到特性后,在属性修改完成后会自动重拉所有属性的状态,完成联动更新。
注意:不需要找 PropertyDescriptor 里和 Refresh 相关的可重写方法,RefreshProperties 是通过特性元数据识别的标记,没有对应的可重写逻辑,只要把特性正确加到 PropertyDescriptor 的特性列表里就完成了这部分配置。
内容的提问来源于stack exchange,提问作者Adrien Rochereau
相关产品推荐
相关产品推荐

