かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPFのプロパティエディタをちょっとカスタマイズ

今回は、Windowを継承したクラスの一覧を取得してプロパティエディタでドロップダウンさせたいという要望です。
mnowさんにTypeConverter使えばいいよと教えてもらって、考えてみたら昔自分で記事まで書いてました。

ということで同じようなノリで作っていきます。
TypeConverterを継承したWindowTypeConverterを作成して以下のように記述します。

namespace WpfApplication1
{
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;

    public class WindowTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            // string型はコンバートできることにしておく
            if (typeof(string) == sourceType)
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            var typeName = value as string;
            if (typeName != null)
            {
                // アセンブリ内から型名が同じ型を探す(Type.GetTypeだとnullが返るケースがあるため)
                var targetType = AppDomain.CurrentDomain.GetAssemblies()
                    .Where(assm => !assm.GlobalAssemblyCache)
                    .Select(assm => assm.GetTypes())
                    .SelectMany(types => types)
                    .Where(type => typeof(Window).IsAssignableFrom(type))
                    .Where(type => type.FullName == typeName)
                    .FirstOrDefault();
                return targetType;
            }
            return base.ConvertFrom(context, culture, value);
        }

        public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            // GACに登録されているアセンブリ以外でWindowを継承している型のFullNameの一覧を選択させる
            var targetTypes = AppDomain.CurrentDomain.GetAssemblies()
                .Where(assm => !assm.GlobalAssemblyCache)
                .Select(assm => assm.GetTypes())
                .SelectMany(types => types)
                .Where(type => typeof(Window).IsAssignableFrom(type))
                .Select(type => type.FullName)
                .ToArray();
            return new StandardValuesCollection(targetTypes);
        }
        public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
        {
            // 自由入力禁止
            return false;
        }
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            // 項目を選択させる
            return true;
        }
    }
}

使ってみよう

適当なUserControlを作って以下のようにTypeConverterを指定したプロパティを作成します。

namespace WpfApplication1
{
    using System;
    using System.ComponentModel;
    using System.Windows.Controls;

    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        [TypeConverter(typeof(WindowTypeConverter))]
        public Type WindowType { get; set; }
    }
}

プロジェクト内や、クラスライブラリのプロジェクト内に適当に何個かWindowを作っておきます。
そしてMainWindowにこのUserControlを張り付けてWindowTypeプロパティを選択してみると以下のように定義したWindowの一覧が表示されます!

設定した値がちゃんとXAMLにも反映されています。{x:Type .....}の形式じゃないのがちょっと残念ですが・・・。

WindowType="my:Window1"

対応するxmlnsも必要に応じて追加してくれるみたいで至れり尽くせりです。