2015年4月15日水曜日

PropertyGridのプロパティ名をリソースを使って日本語化する

.NETで設定画面のGUIを作るとき、PropertyGridを使う人は多いだろう。
その際、例えばこんなモデルを設定したとする。

public class AppProperty {
    [Category("基本情報"), Description("名前です。")]
    public string Name { get; set; }
}

すると、画面には「Name」と表示される。
「Name」ならまだ良いが、これがもっと複雑なプロパティ名だった場合は、使いづらくて仕方がない。

解決策としては @IT さんの PropertyGridコントロールに表示されるプロパティ名を変更するには? に自作のAttributeを使う方法が提示されている。非常に有用な方法なのでそのままコピーして再利用されている方も多いだろう。

その方法では、こんな感じにモデルを作ることになる。

[TypeConverter(typeof(PropertyDisplayConverter))]
public class AppProperty {
    [Category("基本情報"), PropertyDisplayName("名前"), Description("名前です。")]
    public string Name { get; set; }
}

大抵の場合は、上記の方法で必要十分だろう。

だが、例えば以下のような場合では上記以外の方法が必要になる。
  • 多言語対応したアプリを作りたい
  •  同じ名前のプロパティが一杯あって、同じ属性の記述を量産するのは何か違う気がする。
こういう用途には、リソースファイルが非常に相性が良い。
つまり、属性からではなくリソースファイルからプロパティ名を取得するようなTypeConverterを自作してやれば良い。

と言う訳で LocalizableConverter なるものを作ってみた。

public class LocalizableConverter<RESOURCES>: TypeConverter {
    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
        return new PropertyDescriptorCollection(
            TypeDescriptor.GetProperties(value, attributes, true)
            .Cast<PropertyDescriptor>()
            .Select(original => new LocalazablePropertyDescriptor(original)).ToArray());
    }
    public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
        return true;
    }
    public class LocalazablePropertyDescriptor: PropertyDescriptor {
        readonly PropertyDescriptor original;
        public LocalazablePropertyDescriptor(PropertyDescriptor original)
            : base(original) {
            this.original = original;
        }
        public override bool CanResetValue(object component) {
            return original.CanResetValue(component);
        }
        public override Type ComponentType {
            get { return original.ComponentType; }
        }
        public override object GetValue(object component) {
            return original.GetValue(component);
        }
        public override string Description {
            get { return original.Description; }
        }
        public override string Category {
            get { return original.Category; }
        }
        public override bool IsReadOnly {
            get { return original.IsReadOnly; }
        }
        public override void ResetValue(object component) {
            original.ResetValue(component);
        }
        public override bool ShouldSerializeValue(object component) {
            return original.ShouldSerializeValue(component);
        }
        public override void SetValue(object component, object value) {
            original.SetValue(component, value);
        }
        public override Type PropertyType {
            get { return original.PropertyType; }
        }
        public override string DisplayName {
            get {
                var name = base.DisplayName;
                var prop = typeof(RESOURCES).GetProperty(name, BindingFlags.Static | BindingFlags.NonPublic);
                if(prop != null) {
                    return (string)prop.GetValue(null);
                }
                return name;
            }
        }
    }
}


ジェネリクスを利用し、型パラメータにResourcesクラスを指定して使う。
リソースにプロパティ名と同名の文字列を定義していればその値が表示され、定義していなければプロパティ名がそのまま表示されるという寸法だ。

使い方としては、こんな感じになる。

[TypeConverter(typeof(LocalizableConverter<Resources>))]
public class AppProperty {
    [Category("基本情報"), Description("名前です。")]
    public string Name { get; set; }
}

あとは、Properties\Resources.resxに「Name」=「名前」になるように文字列を追加してやれば良い。

0 件のコメント:

コメントを投稿