PropertyGridでEnum型の値を任意の文字列で表示する

元ネタはぬるり。: EnumDisplayName


新しく自分で宣言する型の場合はこれでかなり楽になります。
ですが、既存の型については各値に属性を付けることができない(できるのかもしれませんがわからなかった)ので、これは自前でTypeConverterを作るしかないかと思います。
それを最大限楽にできるように、元ネタのEnumDisplayNameConverterをさっくりと拡張してみました。


少しいじりつつ書いたので元コードとは部分的に書式が違います。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;

public class EnumDisplayNameConverter : EnumConverter {
    private Dictionary<object, string> table = new Dictionary<object, string>();

    public EnumDisplayNameConverter(Type type)
        : base(type) {
        this.BuildTable(this.table);
    }

    protected virtual void BuildTable(System.Collections.Generic.Dictionary<object, string> table) {
        return;
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
        string str = value as string;
        if (str == null) {
            return base.ConvertFrom(context, culture, value);
        }

        Dictionary<object, string>.Enumerator enumerator = this.table.GetEnumerator();
        while (enumerator.MoveNext()) {
            if (enumerator.Current.Value == str) {
                return enumerator.Current.Key;
            }
        }

        foreach (FieldInfo field in base.EnumType.GetFields()) {
            string name = this.GetDisplayName(field, culture);
            if (name == str) {
                return field.GetValue(null);
            }
        }

        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
        if (destinationType != typeof(string)) {
            return base.ConvertTo(context, culture, value, destinationType);
        }
        
        string ret;
        if (this.table.TryGetValue(value, out ret)) {
            return ret;
        }
    
        string valueName = Enum.GetName(base.EnumType, value);
        if (valueName != null) {
            FieldInfo field = base.EnumType.GetField(valueName);
            string name = this.GetDisplayName(field, culture);
            if (name != null) {
                return name;
            }
        }
    
        return base.ConvertTo(context, culture, value, destinationType);
    }

    private string GetDisplayName(FieldInfo field, CultureInfo culture) {
        if (field == null) {
            return null;
        }
        Type type = typeof(EnumDisplayNameAttribute);
        Attribute attr = Attribute.GetCustomAttribute(field, type);
        EnumDisplayNameAttribute disp = attr as EnumDisplayNameAttribute;

        return (disp == null) ? null : disp.GetName(culture);
    }
}

肝はBuildTable()です。このクラスを継承してBuildTableをオーバーライドし、変換用のテーブルを構築してあげるだけで後は勝手に変換します。


それに加えてConvertFromとConvertToに変換テーブルを使用して変換する処理が追加されています。
まず変換テーブルで変換を実行し、変換できなければEnumDisplayNameAttributeの値を使用するようになっています。


ということで試しにSystem.Drawing.ContentAlignmentのConverterを作成してみました。

using System;
using System.Drawing;

public class ContentAlignmentDisplayNameConverter : EnumDisplayNameConverter {
    public ContentAlignmentDisplayNameConverter(Type type)
        : base(type) {
    }

    protected override void BuildTable(System.Collections.Generic.Dictionary<object, string> table) {
        table.Add(ContentAlignment.TopLeft, "左上");
        table.Add(ContentAlignment.TopCenter, "中央上");
        table.Add(ContentAlignment.TopRight, "右上");
        table.Add(ContentAlignment.MiddleLeft, "左中央");
        table.Add(ContentAlignment.MiddleCenter, "中央");
        table.Add(ContentAlignment.MiddleRight, "右中央");
        table.Add(ContentAlignment.BottomLeft, "左下");
        table.Add(ContentAlignment.BottomCenter, "中央下");
        table.Add(ContentAlignment.BottomRight, "右下");
    }
}

これくらいのコード量に収まる+書式が固定化されれば大分楽になりそうです。