かずきのBlog@hatena

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

コレクションエディタ

さて、ボタンを押したら挨拶してくれる機能を簡単に作れる素敵なコントロールも大きな分岐点っぽいです。
ただたんに挨拶するだけじゃぁつまらない。

ってことで、挨拶をいっぱい登録できて10種類の挨拶を登録したら10回ダイアログに挨拶を表示させるようにしてみようと思う。
さらに挨拶も「名前 > メッセージ」という内容の挨拶と、「メッセージ」だけの挨拶の二種類にしてみようと思う。


改造ポイント

  • 挨拶をいっぱい登録できるようにする
  • 挨拶の種類を2パターン用意する

う〜ん。
改造じゃなくて1から作り直そう。
そのほうが楽そうだ。

プロジェクト作成

Greetという名前でWindowsアプリケーションを作成します。

ユーザコントロールの作成

プロジェクトに、GreetControlという名前でユーザコントロールを作成します。

データを用意

ということで挨拶を抽象化。
IGreetインターフェイスを用意します。

IGreet.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Greet
{
    /// <summary>
    /// 挨拶といったらこれがいるでしょ
    /// </summary>
    public interface IGreet
    {
        string GetGreetMessage();
    }
}

要は、何か文字列返せればOK。


さてGreet.csに引き続きクラスを3つ定義します。

    /// <summary>
    /// 普通の挨拶
    /// </summary>
    [Serializable]
    public class NormalGreet : IGreet
    {
        #region プロパティ
        private string message;

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
        #endregion

        #region コンストラクタ
        public NormalGreet() { }
        public NormalGreet(string message)
        {
            this.message = message;
        }
        #endregion

        #region IGreet メンバ

        public virtual string GetGreetMessage()
        {
            return Message;
        }

        #endregion
    }

    /// <summary>
    /// 誰かの挨拶
    /// </summary>
    [Serializable]
    public class PersonGreet : NormalGreet, IGreet
    {
        #region プロパティ
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        #endregion

        #region コンストラクタ
        public PersonGreet() : base() { }
        public PersonGreet(string name, string message) : base(message)
        {
            this.name = name;
        }
        #endregion

        #region IGreet メンバ
        public override string GetGreetMessage()
        {
            return string.Format("{0} > {1}", Name, base.GetGreetMessage());
        }
        #endregion
    }

    /// <summary>
    /// 挨拶のリスト。手抜き
    /// </summary>
    public class GreetList : List<IGreet> { }

ユーザコントロールの実装

とりあえずプロパティエディタのご機嫌は後でとるとして機能を実装してみます。

といっても解説いらないくらい簡単。

GreetControl.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace Greet
{
    public partial class GreetControl : UserControl
    {
        #region プロパティ
        private GreetList greets = new GreetList();
        public GreetList Greets
        {
            get { return greets; }
        }
        #endregion

        public GreetControl()
        {
            InitializeComponent();
        }

        private void greetButton_Click(object sender, EventArgs e)
        {
            foreach (IGreet greet in Greets)
            {
                MessageBox.Show(greet.GetGreetMessage());
            }
        }
    }
}

画面にはボタンが1つ置かれているのであしからず。

TypeConverterの作成

さて、ポイントその1.
さっき作ったIGreetの2つの実装クラスにTypeConverterを作ってあげる。
何に変換するのかというと、InstanceDescriptorクラス。
これをもとにデザイナがコードを吐いてくれるみたい。


InstanceDescriptorにはコントラクタと引数を渡してあげる。
実装は下のような感じ。

    internal class NormalGreetConverter : TypeConverter
    {
        public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor) && value is NormalGreet)
            {
                NormalGreet greet = (NormalGreet)value;
                return new InstanceDescriptor(
                    greet.GetType().GetConstructor(new Type[] { typeof(string) }),
                    new object[] { greet.Message });
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    internal class PersonGreetConverter : TypeConverter
    {
        public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor) && value is PersonGreet)
            {
                PersonGreet greet = (PersonGreet)value;
                return new InstanceDescriptor(
                    greet.GetType().GetConstructor(new Type[] { typeof(string), typeof(string) }),
                    new object[] { greet.Name, greet.Message });
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }


NormalGreetとPersonGreetにTypeConverterを設定してあげる。

    /// <summary>
    /// 普通の挨拶
    /// </summary>
    [Serializable]
    [TypeConverter(typeof(NormalGreetConverter))]
    public class NormalGreet : IGreet
    {
        // 略
    }

    /// <summary>
    /// 誰かの挨拶
    /// </summary>
    [Serializable]
    [TypeConverter(typeof(PersonGreetConverter))]
    public class PersonGreet : NormalGreet, IGreet
    {
        // 略
    }

コレクションエディタ作成

さて、コレクション型のプロパティにはデフォでコレクション用のエディタが設定されるのでプロパティエディタでそれっぽいダイアログを出すことが出来る。
だけど、デフォだとコレクションの扱う型(この場合IGreet)のみしか扱えない。
しかも、今回はインターフェイスなので追加ボタンを押すとエラーになる。


こういうときはCollectionEditorを拡張してやる。
ちなみに、CollectionEditorはSystem.Design.dllに入ってるので参照を追加しよう。


実装はこんなん

    internal class GreetCollectionEditor : CollectionEditor
    {
        private Type[] supportTypes = new Type[] { typeof(NormalGreet), typeof(PersonGreet) };

        public GreetCollectionEditor(Type type) : base(type)
        {
            
        }

        protected override Type[] CreateNewItemTypes()
        {
            return supportTypes;
        }

        protected override Type CreateCollectionItemType()
        {
            // ちょっと苦し紛れ…
            return typeof(PersonGreet);
        }
    }

このコンバータを関連付ける

    /// <summary>
    /// 挨拶のリスト。手抜き
    /// </summary>
    [Editor(typeof(GreetCollectionEditor), typeof(UITypeEditor))]
    public class GreetList : List<IGreet> { }

後はGreetsプロパティに属性追加!

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public GreetList Greets
        {
            get { return greets; }
        }

これやらないとシリアライズしてくれない。


そしてタイムアップなので詳しいことはまた後で追記することに。