.NETのプロパティグリッドに既存の型の任意のプロパティのみを表示する方法

ActiveReports for .NET(18.0J)のDesignerコントロールと.NETのグリッドコントロールを使用する際、任意のプロパティだけを表示する方法についての備忘録。

ActiveReportsに限らず、プロパティグリッドに表示するデータ型のソースを直接修正することができない状況で便利なテクニックです。


対応

方法1(NG)

最も一般的な方法で、カスタムPropertyGridとカスタムTypeDescriptionProvider、カスタムTypeDescriptorを使ってプロパティグリッドに表示したいプロパティのみを返す。

Designerコントロールからレイアウトファイルのロードを行う際に読み込みエラーが発生するようになる。また、アンドゥリドゥが正常に機能しなくなる(動きを見るとDesignerコントロール側で表示対象プロパティのみが処理の対象になっているような印象)。

方法2(NG)

カスタムPropertyGridとカスタムTypeDescriptionProvider、カスタムTypeDescriptor、カスタムPropertyDescriptor、リフレクションを使って、プロパティグリッドに表示したいプロパティ以外をBrowsableAttribute=falseに設定する。

Size/Location/Fontプロパティといった、階層型データのプロパティが表示されてしまう。

方法3(OK)

カスタムPropertyGridとカスタムTypeDescriptionProvider、カスタムTypeDescriptor、カスタムPropertyDescriptorを使って、プロパティグリッドに表示したいプロパティにカスタム属性を設定、プロパティグリッドのBrowsableAttributesにそのカスタム属性を設定する。

サンプルソースコード

今回あれこれ試して実現できたコードは以下のとおり。
本サンプルはデザイナ上のセクションレポートのLabelに対してカテゴリが「表示」または「レイアウト」、「データ」カテゴリの「Text」プロパティのみをプロパティグリッドで表示する制御を行っています。

あくまで検証実験コードですので、その点はご了承ください(At your own risk)。

カスタムAttribute:

// プロパティグリッドに表示するプロパティ識別用の属性
[AttributeUsage(AttributeTargets.All)]
public sealed class CustomBrowsableAttribute : Attribute
{
    public static readonly CustomBrowsableAttribute Yes = new CustomBrowsableAttribute(browsable: true);

    public static readonly CustomBrowsableAttribute No = new CustomBrowsableAttribute(browsable: false);

    public static CustomBrowsableAttribute Default => No;

    private bool browsable = true;

    public bool CustomBrowsable => browsable;

    public CustomBrowsableAttribute(bool browsable)
    {
        this.browsable = browsable;
    }

    public override bool Equals(object obj)
    {
        if (obj == this)
        {
            return true;
        }

        if (obj is CustomBrowsableAttribute browsableAttribute)
        {
            return browsableAttribute.CustomBrowsable == browsable;
        }

        return false;
    }

    public override int GetHashCode()
    {
        return browsable.GetHashCode();
    }

    public override bool IsDefaultAttribute()
    {
        return Equals(Default);
    }
}

カスタムPropertyDescriptor:

// 既存クラスに対して動的にカスタム属性を追加
public class DynamicAttributePropertyDescriptor : PropertyDescriptor
{
    private PropertyDescriptor _basePropertyDescriptor;

    private Attribute[] _newAttributes;

    public DynamicAttributePropertyDescriptor(PropertyDescriptor basePropertyDescriptor, params Attribute[] newAttributes)
        : base(basePropertyDescriptor)
    {
        _basePropertyDescriptor = basePropertyDescriptor;
        _newAttributes = newAttributes;
    }

    public override AttributeCollection Attributes
    {
        get
        {
            var newAttributes = new List<Attribute>(_newAttributes);
            newAttributes.AddRange(_basePropertyDescriptor.Attributes.Cast<Attribute>());
            return new AttributeCollection(newAttributes.ToArray());
        }
    }

    public override Type ComponentType => _basePropertyDescriptor.ComponentType;
    public override bool IsBrowsable => _basePropertyDescriptor.IsBrowsable;
    public override bool IsReadOnly => _basePropertyDescriptor.IsReadOnly;
    public override Type PropertyType => _basePropertyDescriptor.PropertyType;

    public override bool CanResetValue(object component)
    {
        return _basePropertyDescriptor.CanResetValue(component);
    }

    public override object GetValue(object component)
    {
        return _basePropertyDescriptor.GetValue(component);
    }

    public override void ResetValue(object component)
    {
        _basePropertyDescriptor.ResetValue(component);
    }

    public override void SetValue(object component, object value)
    {
        _basePropertyDescriptor.SetValue(component, value);
    }

    public override bool ShouldSerializeValue(object component)
    {
        return _basePropertyDescriptor.ShouldSerializeValue(component);
    }
}

カスタムTypeDescriptor:

// プロパティに対してカスタム属性を適用
public class FilteredTypeDescriptor : CustomTypeDescriptor
{
    private Regex _filterPattern;

    public FilteredTypeDescriptor(ICustomTypeDescriptor baseDescriptor, string filterPattern)
        : base(baseDescriptor)
    {
        _filterPattern = new Regex(filterPattern, RegexOptions.Compiled);
    }

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var originalProperties = base.GetProperties(attributes);
        var filteredProperties = new List<PropertyDescriptor>();

        foreach (PropertyDescriptor prop in originalProperties)
        {
            var categoryAndProperty = $"{prop.Category}.{prop.Name}";
            if (_filterPattern.IsMatch(categoryAndProperty))
            {
                filteredProperties.Add(new DynamicAttributePropertyDescriptor(prop, new CustomBrowsableAttribute(true)));
            }
            else
            {
                filteredProperties.Add(prop);
            }
        }

        return new PropertyDescriptorCollection(filteredProperties.ToArray());
    }

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

カスタムTypeDescriptionProvider:

// 型のメタデータ情報のカスタマイズ
public class FilteredTypeDescriptionProvider : TypeDescriptionProvider
{
    private TypeDescriptionProvider _defaultProvider = null;

    private string _filterPattern = "^$";

    public FilteredTypeDescriptionProvider(Type type)
    {
        _defaultProvider = TypeDescriptor.GetProvider(type);

        if (type == typeof(GrapeCity.ActiveReports.SectionReportModel.Label))
        {
            _filterPattern = @"^(表示\..+|レイアウト\..+|データ\.Text)$";
        }
    }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        return new FilteredTypeDescriptor(
            _defaultProvider.GetTypeDescriptor(objectType),
            _filterPattern);
    }
}

カスタムPropertyGrid:

// 表示するオブジェクトの型にメタ情報のカスタマイズを適用
public class CustomPropertyGrid : PropertyGrid
{
    private Dictionary<Type, FilteredTypeDescriptionProvider> _providers = new Dictionary<Type, FilteredTypeDescriptionProvider>();

    protected override void OnSelectedObjectsChanged(EventArgs e)
    {
        base.OnSelectedObjectsChanged(e);

        foreach (var selectedObject in SelectedObjects)
        {
            var objectType = selectedObject.GetType();
            if (_providers.ContainsKey(objectType) == false)
            {
                var newProvider = new FilteredTypeDescriptionProvider(objectType);
                _providers[objectType] = newProvider;
                TypeDescriptor.AddProvider(newProvider, objectType);
            }
        }

        Refresh();
    }
}

Form等のInitialize処理:

// プロパティグリッドに表示するカスタム属性を指定
arPropertyGrid.BrowsableAttributes = new AttributeCollection(new Attribute[]{new CustomBrowsableAttribute(true)});


以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする