Enum をローカライズして WPF でコンボボックスなどにバインドする方法

Infragistics のコンポーネント作ってる中の人かつ WPF/Xamarin.Forms でアプリ開発している人なら名前を聞いたことがない人はいないくらい有名な Prism のコミッターで筋肉が凄い Brian Lagunas さんの YouTube チャンネルで先日こんなネタが取り上げられていました。

[How to Bind an Enum to a ComboBox in WPF](https://youtu.be/Bp5LFXjwtQ0)

この動画で Enum の要素を簡単に ComboBox の選択肢として出す方法が紹介されていたのですが、ローカライズは考慮していなかったので、いいブログネタだなと思ってました。
そう思ってたら、今日こんな動画があがってました。

[How to Localize Enums in C#](https://youtu.be/T1mhORJCDsY)

ネタかぶったぁぁぁぁぁぁぁぁぁ！！

## 動画内の方法の紹介

マークアップ拡張で Enum 型の値の配列を返すものを作ることで以下のように簡単に書ける。

```xml
<ComboBox ItemsSource="{Binding Source={local:EnumBindingSource {x:Type local:Status}}}" />
```

Enum を任意の文字列に変換するには、TypeConverter を使って Enum が WPF のコントロールに表示される処理をカスタマイズして  DescriptionAttribute があったら、そこに指定されている値を使うという感じでした。ローカライズは LocalizedDescriptionAttirubute を使ってリソースの定義されている型とキーを指定できる独自の属性を定義する感じにしてました。

なるほど、TypeConverter の存在を忘れかけてましたが、こういう風にも使えるんですね。

## ちょっとだけ改善？

動画内では DescriptionAttribute を使って Enum に表示用文字列を指定していて、ローカライズ可能にする場合は自前のローカライズした文字列を指定可能な属性を自作していましたが、一応標準の中にもローカライズ機能つきの属性として DisplayAttribute があるので、そっちを使えば独自属性が無くてもよさそうかなって思いました。

ただ、純粋にローカライズ可能な表示名だけを提供したい場合にはオーバースペックなのでカスタム属性のほうがいいかもしれません。でもとりあえずやってみましょう。

TypeConverter を以下のようにして DisplayAttribute を使うようにして…

```csharp:EnumDisplayTypeConverter.cs
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;

namespace BindingEnums
{
    public class EnumDisplayTypeConverter : EnumConverter
    {
        public EnumDisplayTypeConverter(Type type) : base(type)
        {
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                if (value != null)
                {
                    var field = value.GetType().GetField(value.ToString());
                    if (field != null)
                    {
                        var attribute = field.GetCustomAttribute<DisplayAttribute>(false);
                        return attribute == null ? value.ToString() : attribute.GetName();
                    }
                }
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }

    }
}
```

そして、Enum に以下のように Display 属性つきで定義します。

```csharp:Status.cs
using BindingEnums.Resources;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace BindingEnums
{
    [TypeConverter(typeof(EnumDisplayTypeConverter))]
    public enum Status
    {
        [Display(ResourceType = typeof(EnumResource), Name = nameof(EnumResource.Horrible))]
        Horrible,
        [Display(ResourceType = typeof(EnumResource), Name = nameof(EnumResource.Bad))]
        Bad,
        [Display(ResourceType = typeof(EnumResource), Name = nameof(EnumResource.SoSo))]
        SoSo,
        [Display(ResourceType = typeof(EnumResource), Name = nameof(EnumResource.Good))]
        Good,
        [Display(ResourceType = typeof(EnumResource), Name = nameof(EnumResource.Better))]
        Better,
        [Display(ResourceType = typeof(EnumResource), Name = nameof(EnumResource.Best))]
        Best,
    }
}
```

リソースは間に合わせで適当に用意しておきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/7c5a511f-9e70-d7d2-3622-6cef1e939ad2.png)

実行すると、こんな感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/8a2801a5-edca-baa7-bf61-afcf890799ed.png)

いいね。

## まとめ

ということで自分でリソースから文字列取ってくる処理をしなくても、標準である属性の中だと DisplayAttribute あたりを使うと自前で属性作らなくてもいけるけど、若干 DisplayAttribute はオーバースペック感があるのは否めない…。

ソースコードは以下のリポジトリにアップしておきました。

https://github.com/runceel/BindingEnums

あと Brian Lagunas さんの YouTube チャンネルは XAML 系のことをやってる人にとっては身近な話題で英語が聞けるし、自動生成字幕でもかなりイケてるので個人的におすすめです。

Brian Lagunas さんの YouTube チャンネルは以下のリンクからいけます。

[Brian Lagunas - YouTube](https://www.youtube.com/c/BrianLagunas)