You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅针对单个类实例启用/禁用属性浏览,不影响其他实例?

问题描述

现有如下C#代码,当在Myxyz类中创建BaseFilter的多个实例(如Day和Week)时,启用Day的EnableFilter属性会导致Week的SetMinFrom和SetMinTo属性也被显示,需要修改代码让属性的显示/隐藏仅作用于当前操作的实例。

现有代码

BaseFilter类

public class BaseFilter
{
    private bool _enableFliter;
    private int _setMinTo;
    private int _setMinFrom;
    private string _name;

    [RefreshProperties(RefreshProperties.All)]
    public bool EnableFilter 
    {
        get => _enableFliter;
        set
        {
            if (_enableFliter != value)
            {
                SetBrowsableAttributeValue(nameof(SetMinFrom), value);
                SetBrowsableAttributeValue(nameof(SetMinTo), value);
            }
            _enableFliter = value;
        }
    }

    [Browsable(false)]
    [Display(Name = "min from")]
    [Range(0, 100)]
    public int SetMinFrom { get => _setMinFrom; set { _setMinFrom = value; OnPropertyChanged(); } }

    [Browsable(false)]
    [Display(Name = "min to")]
    [Range(0, 100)]
    public int SetMinTo { get => _setMinTo; set { _setMinTo = value; OnPropertyChanged(); } }

    public BaseFilter(string argname)
    {
        _enableFliter = false;
        _setMinTickFrom = 0; // 原代码字段名错误,应为_setMinFrom
        _setMinTickTo = 0;   // 原代码字段名错误,应为_setMinTo
        _name = argname;
    }

    private void SetBrowsableAttributeValue(string attribute, object value)
    {
        var descriptor = TypeDescriptor.GetProperties(GetType())[attribute];
        var attrib = (BrowsableAttribute)descriptor.Attributes[typeof(BrowsableAttribute)];
        var isBrow = attrib.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(a => a.Name.Contains("browsable", StringComparison.OrdinalIgnoreCase));
        isBrow.SetValue(attrib, value);
    }
}

Myxyz类

public class Myxyz
{
    public BaseFilter Day { get; set; } = new("Day");
    public BaseFilter Week { get; set; } = new("Week");
    // 其他实例...
}

问题根源

原代码中SetBrowsableAttributeValue方法通过TypeDescriptor.GetProperties(GetType())获取的是类级别的属性描述符,所有BaseFilter实例共享同一套属性描述符集合。修改这个集合里的BrowsableAttribute会影响所有实例,导致一个实例启用过滤时,其他实例的对应属性也会显示。

解决方案

要实现实例级的属性显示控制,需要为每个实例维护独立的可浏览状态,并通过自定义TypeDescriptionProvider动态返回属性的可浏览性。

修改后的代码

1. 改进BaseFilter类,添加实例级状态标记

using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

[TypeDescriptionProvider(typeof(BaseFilterTypeDescriptionProvider))]
public class BaseFilter : INotifyPropertyChanged
{
    private bool _enableFilter;
    private int _setMinTo;
    private int _setMinFrom;
    private string _name;

    // 维护当前实例的属性可浏览状态
    private readonly Dictionary<string, bool> _propertyBrowsableStates = new()
    {
        { nameof(SetMinFrom), false },
        { nameof(SetMinTo), false }
    };

    [RefreshProperties(RefreshProperties.All)]
    public bool EnableFilter 
    {
        get => _enableFilter;
        set
        {
            if (_enableFilter != value)
            {
                _propertyBrowsableStates[nameof(SetMinFrom)] = value;
                _propertyBrowsableStates[nameof(SetMinTo)] = value;
                _enableFilter = value;
                OnPropertyChanged();
                // 通知属性网格刷新显示
                TypeDescriptor.Refresh(this);
            }
        }
    }

    [Browsable(false)]
    [Display(Name = "min from")]
    [Range(0, 100)]
    public int SetMinFrom 
    { 
        get => _setMinFrom; 
        set 
        { 
            _setMinFrom = value; 
            OnPropertyChanged(); 
        } 
    }

    [Browsable(false)]
    [Display(Name = "min to")]
    [Range(0, 100)]
    public int SetMinTo 
    { 
        get => _setMinTo; 
        set 
        { 
            _setMinTo = value; 
            OnPropertyChanged(); 
        } 
    }

    public BaseFilter(string argname)
    {
        _enableFilter = false;
        _setMinFrom = 0;
        _setMinTo = 0;
        _name = argname;
    }

    // 供自定义描述器获取当前实例的属性可浏览状态
    internal bool IsPropertyBrowsable(string propertyName)
    {
        if (_propertyBrowsableStates.TryGetValue(propertyName, out var state))
        {
            return state;
        }
        // 默认返回属性原有Browsable标记
        var propInfo = GetType().GetProperty(propertyName);
        var attrib = propInfo?.GetCustomAttribute<BrowsableAttribute>();
        return attrib?.Browsable ?? true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. 自定义类型描述器相关类

public class BaseFilterTypeDescriptionProvider : TypeDescriptionProvider
{
    private static readonly TypeDescriptionProvider _defaultProvider = TypeDescriptor.GetProvider(typeof(BaseFilter));

    public BaseFilterTypeDescriptionProvider() : base(_defaultProvider) { }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        var defaultDescriptor = base.GetTypeDescriptor(objectType, instance);
        return instance is BaseFilter filter 
            ? new BaseFilterCustomTypeDescriptor(defaultDescriptor, filter) 
            : defaultDescriptor;
    }
}

public class BaseFilterCustomTypeDescriptor : CustomTypeDescriptor
{
    private readonly BaseFilter _filterInstance;

    public BaseFilterCustomTypeDescriptor(ICustomTypeDescriptor parent, BaseFilter filter) : base(parent)
    {
        _filterInstance = filter;
    }

    public override PropertyDescriptorCollection GetProperties()
    {
        return GetProperties(new Attribute[0]);
    }

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
            .Select(p => new BaseFilterPropertyDescriptor(p, _filterInstance))
            .ToArray();
        return new PropertyDescriptorCollection(properties);
    }
}

public class BaseFilterPropertyDescriptor : PropertyDescriptor
{
    private readonly PropertyDescriptor _originalDescriptor;
    private readonly BaseFilter _filterInstance;

    public BaseFilterPropertyDescriptor(PropertyDescriptor original, BaseFilter filter) 
        : base(original)
    {
        _originalDescriptor = original;
        _filterInstance = filter;
    }

    // 动态返回当前实例的属性可浏览状态
    public override bool IsBrowsable => _filterInstance.IsPropertyBrowsable(Name) && _originalDescriptor.IsBrowsable;

    // 转发其他属性描述器方法
    public override object GetValue(object component) => _originalDescriptor.GetValue(component);
    public override void SetValue(object component, object value) => _originalDescriptor.SetValue(component, value);
    public override bool CanResetValue(object component) => _originalDescriptor.CanResetValue(component);
    public override void ResetValue(object component) => _originalDescriptor.ResetValue(component);
    public override bool ShouldSerializeValue(object component) => _originalDescriptor.ShouldSerializeValue(component);
    public override Type PropertyType => _originalDescriptor.PropertyType;
    public override Type ComponentType => _originalDescriptor.ComponentType;
}

说明

  • 每个BaseFilter实例通过_propertyBrowsableStates字典维护自己的属性显示状态,互不干扰
  • 自定义的TypeDescriptionProvider会为每个实例生成专属的属性描述器,动态覆盖IsBrowsable值
  • 修改EnableFilter时调用TypeDescriptor.Refresh(this),触发属性网格刷新显示

内容的提问来源于stack exchange,提问作者chrisk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 21:47:15