かずきのBlog@hatena

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

自作クラスのプロパティ

さて、Greetプロパティは文字列でした。
これを、こんなクラスに変更してみたいと思います。

namespace CustomControlTest
{
    public class GreetMessage
    {
        private string name;
        /// <summary>
        /// 挨拶をする人の名前
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string message;

        /// <summary>
        /// 挨拶の内容
        /// </summary>
        public string Message
        {
            get { return message; }
            set { message = value; }
        }

        /// <summary>
        /// Name > Messageという内容の文字列を返す
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0} > {1}", Name, Message);
        }

    }
}


コントロールのGreetプロパティとボタンクリックのイベントはこんな感じに書きかわります。

namespace CustomControlTest
{
    [DefaultProperty("Greet")]
    public partial class HelloControl : UserControl
    {
        public HelloControl()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show(Greet.ToString());
        }

        #region プロパティ
        private GreetMessage greet;

        [Category("表示")]
        [Browsable(true)]
        public GreetMessage Greet
        {
            get { return greet; }
            set { greet = value; }
        }

        #endregion
    }
}

ここで、コンパイルするとコンパイルエラー。
先ほどデザイナでGreetプロパティに「こんにちは」って入れていた所でエラー!
エラー箇所の行をさくっと削除。
動作確認をしてみるとヌルリで落ちます。
そりゃそうだ。GreetプロパティはNullでしたorz


まだ、プロパティエディタからは編集できないのでInitializeComponentsの後に初期化コードを自前で入れます。

namespace CustomControlTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            helloControl1.Greet = new GreetMessage();
            helloControl1.Greet.Name = "太郎";
            helloControl1.Greet.Message = "こんにちは";
        }
    }
}

これで実行すると、「太郎 > こんにちは」と表示されたメッセージボックスが出ます。


さて、このGreetプロパティをプロパティエディタで編集できるようにしてやらないといけません。
これは、ExpandableObjectConverterを実装したクラスを定義してやらないといけない。
そんな難しくないので楽勝!
オーバーライドするメソッドは大体以下の4つで事足りる

  • CanConvertTo
  • CanConvertFrom
  • ConvertTo
  • ConvertFrom

実装例は以下の通り。

    /// <summary>
    /// string <-> GreetMessageへの変換を行う
    /// </summary>
    public class GreetMessageConverter : ExpandableObjectConverter
    {
        /// <summary>
        /// sourceTypeがstringならtrueを返す。
        /// </summary>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            // stringからなら変換可能
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        /// <summary>
        /// destinationTypeがGreetMessageならtrue
        /// </summary>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        /// <summary>
        /// 文字列からGreetMessageへ変換する
        /// </summary>
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                string str = (string) value;
                string[] ss = str.Split(new char[] { ',' }, 2);
                GreetMessage g = new GreetMessage();
                g.Name = ss[0];
                g.Message = ss[1];
                return g;
            }
            return base.ConvertFrom(context, culture, value);
        }

        /// <summary>
        /// GreetMessageから文字列へ変換する
        /// </summary>
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is GreetMessage)
            {
                GreetMessage g = (GreetMessage) value;
                return string.Format("{0},{1}", g.Name, g.Message);
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

やってることは型を見てstring <-> GreetMessageの相互変換。
NameとMessageをカンマ区切りにするという仕様にしました。


後は、このコンバータをGreetMessageと関連付けるだけ。
関連付けは、もちろんAttributeでやります。
使うAttributeは

TypeConverterAttribute

です。


早速つけてみた。

    [TypeConverter(typeof(GreetMessageConverter))]
    public class GreetMessage
    {
        private string name;
        /// <summary>
        /// 挨拶をする人の名前
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string message;

        /// <summary>
        /// 挨拶の内容
        /// </summary>
        public string Message
        {
            get { return message; }
            set { message = value; }
        }

        /// <summary>
        /// Name > Messageという内容の文字列を返す
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0} > {1}", Name, Message);
        }

    }

とやってみたがうまくいかんorz
Greetプロパティには、GreetMessage#ToStringの結果が表示されちゃう。
ちゃんと横に+マークは出てNameプロパティとMessageプロパティは表示できてるんだけどなぁ。
なぞだ。

# 追記
# 一晩寝かせたらうまいこといった。
# VisualStudioの再起動しないといけないみたい?