VS2013中能否动态为属性设置[DesignerSerializationVisibility]特性?
嘿,这个需求完全可以实现!不过直接动态修改Attribute不是最稳妥的做法——毕竟Attribute本质是编译期嵌入的元数据,硬改不仅不稳定,设计器也不一定能正确识别。我们可以利用WinForms设计器的扩展机制来优雅达成目标,给你两个靠谱的方案:
GetProperties方法动态控制属性可见性 这是最直接的方式,通过重写控件的属性获取逻辑,根据UseCustomButtonIcon的值动态调整CustomButtonIcon的可见性:
public class YourControl : Control { public enum MoreButtonIcon { Default, Calendar, Clock } [DefaultValue(false)] public bool UseCustomButtonIcon { get; set; } public MoreButtonIcon CustomButtonIcon { get; set; } // 重写属性获取逻辑,动态调整CustomButtonIcon的可见性 public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { var originalProps = base.GetProperties(attributes); var customIconProp = originalProps.Find(nameof(CustomButtonIcon), false); if (customIconProp != null) { // 复制原有属性的特性数组,准备修改 var newAttributes = new Attribute[customIconProp.Attributes.Count]; customIconProp.Attributes.CopyTo(newAttributes, 0); // 替换或添加BrowsableAttribute,根据UseCustomButtonIcon的值决定可见性 bool foundBrowsable = false; for (int i = 0; i < newAttributes.Length; i++) { if (newAttributes[i] is BrowsableAttribute) { newAttributes[i] = new BrowsableAttribute(UseCustomButtonIcon); foundBrowsable = true; break; } } // 如果原有属性没有Browsable特性,直接添加 if (!foundBrowsable) { Array.Resize(ref newAttributes, newAttributes.Length + 1); newAttributes[newAttributes.Length - 1] = new BrowsableAttribute(UseCustomButtonIcon); } // 创建修改后的属性描述符 var modifiedProp = TypeDescriptor.CreateProperty( GetType(), customIconProp, newAttributes ); // 构建新的属性集合,替换原有的CustomButtonIcon属性 var newProps = new PropertyDescriptorCollection(null); foreach (PropertyDescriptor prop in originalProps) { newProps.Add(prop.Name == nameof(CustomButtonIcon) ? modifiedProp : prop); } return newProps; } return originalProps; } // 当UseCustomButtonIcon变化时,通知设计器刷新属性列表 protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.PropertyName == nameof(UseCustomButtonIcon)) { TypeDescriptor.Refresh(this); } } }
原理说明
当你在设计器中修改UseCustomButtonIcon的值时,OnPropertyChanged会触发TypeDescriptor.Refresh,告诉设计器重新读取控件的属性列表。GetProperties方法会根据当前UseCustomButtonIcon的状态,给CustomButtonIcon设置对应的Browsable特性——为true时显示,为false时隐藏。
TypeConverter控制序列化与可见性 如果你确实需要动态修改DesignerSerializationVisibility(比如不想让设计器序列化CustomButtonIcon的值),可以通过自定义TypeConverter来实现:
// 自定义TypeConverter,用于动态调整属性特性 public class CustomButtonIconConverter : TypeConverter { public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { var control = context.Instance as YourControl; var originalProps = base.GetProperties(context, value, attributes); if (control != null && !control.UseCustomButtonIcon) { // 找到CustomButtonIcon属性,修改其序列化特性 var customIconProp = originalProps.Find(nameof(YourControl.CustomButtonIcon), false); if (customIconProp != null) { // 替换原有DesignerSerializationVisibility特性为Hidden var modifiedAttributes = customIconProp.Attributes.Cast<Attribute>() .Select(attr => attr is DesignerSerializationVisibilityAttribute ? new DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden) : attr) .ToArray(); var modifiedProp = TypeDescriptor.CreateProperty( typeof(YourControl), customIconProp, modifiedAttributes ); // 构建新的属性集合,替换原属性 var filteredProps = originalProps.Cast<PropertyDescriptor>() .Where(p => p.Name != nameof(YourControl.CustomButtonIcon)) .Append(modifiedProp) .ToArray(); return new PropertyDescriptorCollection(filteredProps); } } return originalProps; } } // 在控件类上应用自定义TypeConverter [TypeConverter(typeof(CustomButtonIconConverter))] public class YourControl : Control { public enum MoreButtonIcon { Default, Calendar, Clock } [DefaultValue(false)] public bool UseCustomButtonIcon { get; set; } public MoreButtonIcon CustomButtonIcon { get; set; } protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.PropertyName == nameof(UseCustomButtonIcon)) { TypeDescriptor.Refresh(this); } } }
原理说明
这个方案通过TypeConverter拦截设计器的属性读取逻辑,当UseCustomButtonIcon为false时,把CustomButtonIcon的DesignerSerializationVisibility设置为Hidden——这样设计器不仅会隐藏该属性,还不会把它的值序列化到代码里。同样,OnPropertyChanged里的刷新逻辑会确保设计器实时响应状态变化。
- 这两个方案在VS2013中都能正常工作,无需额外依赖。
- 如果修改
UseCustomButtonIcon后设计器没立刻刷新,切换到其他控件再切回来即可(不过代码里的TypeDescriptor.Refresh已经做了自动刷新)。 - 不推荐用反射直接修改Attribute,因为设计器的元数据缓存可能不会实时更新,容易出现不一致的问题。
内容的提问来源于stack exchange,提问作者cmos

