Winform中PropertyGrid控件如何在运行时动态修改属性可浏览性
问题成因
你遇到browsableField为null的核心原因是不同.NET版本中BrowsableAttribute的内部实现不一致:
- 在.NET Framework 4.x及更早版本中,
BrowsableAttribute确实存在名为browsable的私有实例字段存储配置值 - 在.NET Core 3.0+、.NET 5+及后续版本中,
BrowsableAttribute改为使用自动属性实现,私有存储字段是编译器生成的<Browsable>k__BackingField,不再存在名为browsable的字段,因此反射获取返回null
另外这种直接修改特性私有字段的方案本身就不稳定,依赖框架内部实现,版本升级就容易失效,而且TypeDescriptor对特性有全局缓存,部分场景下修改后不会立即生效。
可行解决方案
方案1:快速兼容修复原反射代码
如果想最小改动兼容原逻辑,可以调整反射字段查找逻辑,修改后主动刷新PropertyGrid即可生效:
public static void ChangeBrowsability(object obj, string propertyName, bool isBrowsable, PropertyGrid grid) { PropertyDescriptor descriptor = TypeDescriptor.GetProperties(obj)[propertyName]; if (descriptor == null) return; BrowsableAttribute attrib = (BrowsableAttribute)descriptor.Attributes[typeof(BrowsableAttribute)]; if (attrib == null) return; // 兼容不同.NET版本的字段名 FieldInfo browsableField = attrib.GetType().GetField("browsable", BindingFlags.NonPublic | BindingFlags.Instance) ?? attrib.GetType().GetField("<Browsable>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance); if (browsableField != null) { browsableField.SetValue(attrib, isBrowsable); // 刷新PropertyGrid显示 grid.Refresh(); } }
注意:该方案仍依赖框架内部实现,仅适合临时快速修复,不推荐长期生产环境使用。
方案2:更稳定的自定义类型描述方案(推荐)
通过实现ICustomTypeDescriptor接口让业务类动态控制属性的可见性,完全基于.NET公开API实现,不依赖框架内部逻辑,兼容所有.NET版本:
- 业务类实现接口并添加可见性控制逻辑:
public class YourBusinessClass : ICustomTypeDescriptor { // 存储属性的可见性配置 private Dictionary<string, bool> _propertyBrowsableConfig = new Dictionary<string, bool>(); // 你的原有业务属性 public string FixedVisibleProp { get; set; } public string DynamicVisibleProp { get; set; } // 对外提供设置可见性的方法 public void SetPropertyBrowsable(string propertyName, bool isBrowsable) { _propertyBrowsableConfig[propertyName] = isBrowsable; } #region ICustomTypeDescriptor默认实现,仅需修改属性获取逻辑 public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true); public string GetClassName() => TypeDescriptor.GetClassName(this, true); public string GetComponentName() => TypeDescriptor.GetComponentName(this, true); public TypeConverter GetConverter() => TypeDescriptor.GetConverter(this, true); public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true); public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true); public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true); public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true); public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(this, attributes, true); public object GetPropertyOwner(PropertyDescriptor pd) => this; #endregion // 核心逻辑:过滤标记为不可见的属性 public PropertyDescriptorCollection GetProperties() => GetProperties(null); public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { var originalProps = TypeDescriptor.GetProperties(this, attributes, true); var filteredProps = new List<PropertyDescriptor>(); foreach (PropertyDescriptor prop in originalProps) { if (_propertyBrowsableConfig.TryGetValue(prop.Name, out var isBrowsable) && !isBrowsable) continue; filteredProps.Add(prop); } return new PropertyDescriptorCollection(filteredProps.ToArray()); } }
- 按钮点击切换可见性的调用逻辑:
private void TogglePropBtn_Click(object sender, EventArgs e) { var targetObj = propertyGrid.SelectedObject as YourBusinessClass; if (targetObj == null) return; // 切换指定属性的可见性 targetObj.SetPropertyBrowsable(nameof(YourBusinessClass.DynamicVisibleProp), !targetObj.GetProperties().Cast<PropertyDescriptor>().Any(p=>p.Name == nameof(YourBusinessClass.DynamicVisibleProp))); propertyGrid.Refresh(); }
内容的提问来源于stack exchange,提问作者Mayerstro
相关产品推荐
相关产品推荐

