C#でツールを作る その16 -PropertyGridを使ってみる-

超便利ですね。PropertyGridまじパネェっす!
C#を使い始めて以来、多分最も感動したのではないかと。


昨日までC#のプロパティとは、
「get、setメソッドを多少簡単に記述できるようにしただけ」
と勝手に思い込んでいたのですが、
PropertyGridを使ってみて初めてその便利さを実感しました。
単にクラス間のメンバアクセスを簡略化できるだけでなく、
PropertyGridと組み合わせることで、アプリケーションからのアクセスも容易になるわけですね!


ということで、今回はPropertyGridな話です。
基本的な使い方はこちらを参照してください。

実際使ってみるとこんな感じ。



上の図のようなfloatで設定可能な色クラスColorFを作ってみます。

PropertyGrid

  • まずは適当にPropertyGridを貼り付けます。


ColorF構造体

  • 0.0f〜1.0fの範囲で色の成分を扱うための構造体
    public struct ColorF
    {
        public float r, g, b, a;
    }

ApplicationSettingsクラス

  • PropertyGridからアクセスするクラス。
  • メンバのcolorをプロパティとして公開。
    public class ApplicationSettings
    {
        private int hoge;
        private ColorF color;

        public ColorF Color
        {
            get{ return color; }
            set{ color = value; }
        }
    }

Form1クラス

  • PropertyGridとApplicationSettingsを関連付ける。
    public partial class Form1 : Form
    {
        private ApplicationSettings app_settings = new ApplicationSettings();

        public Form1()
        {
            InitializeComponent();

            propertyGrid1.SelectedObject = app_settings;
        }
    }

実行結果


Colorのみ表示されますが、メンバのr, g, b, aが表示されません。


ちなみに、Form1を関連付けるとFormのプロパティが表示されて、値を変えることが出来ます。

            propertyGrid1.SelectedObject = this;


関連付けるだけで、アプリケーションからメンバの修正が行えます。すごいです。





話を元に戻して、さらにカスタマイズしていきます。

    public class ApplicationSettings
    {
        private ColorF color;
        private Point pos;

        public ColorF Color
        {
            get{ return color; }
            set{ color = value; }
        }

        public Point Pos
        {
            get{ return pos; }
            set{ pos = value; }
        }
    }


C#の標準の型はデフォルトでメンバが表示されますが、自分で作ったクラスは表示されません。

メンバを表示させる

  • TypeConverterを継承したクラスを用意して、ColorFのメンバを文字列に変換する
    • ConvertToをオーバーライドし、変換先が文字列かどうか確認して変換
    • ColorFに設定
using System.ComponentModel;

    class ColorFTypeConverter : TypeConverter
    {
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(String))
            {
                ColorF c = (ColorF)value;
                return c.r + " " + c.g + " " + c.b + " " + c.a;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    [TypeConverter( typeof( ColorFTypeConverter ) )]
    public struct ColorF
    {
    /*...*/
    }


表示されました。



ReadOnlyになっているので、値を変更できるように、オーバーライドを追加します。

  • CanConvertFromのオーバーライド
    • 値の変更を有効にする
  • ConvertFromのオーバーライド
    • 文字列からColorFに変換
    • 書式が不正など、変換できない場合は例外が発生する。
    class ColorFTypeConverter : TypeConverter
    {
    /*...*/

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(String))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is String)
            {
                String[] v = ((String)value).Split(new char[] { ' ' });
                return new ColorF(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
            }
            return base.ConvertFrom(context, culture, value);
        }
    }

変換不能な場合は元の値に戻すようにしてみました。適当です。

    class ColorFTypeConverter : TypeConverter
    {
        private ColorF current;

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(String))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is String)
            {
                try
                {
                    String[] v = ((String)value).Split(new char[] { ' ' });
                    return new ColorF(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
                }
                catch { }
                //変換できない場合は以前の色に戻す
                return current;
            }
            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(String))
            {
                //現在の色をとっておく
                current = (ColorF)value;

                ColorF c = (ColorF)value;
                return c.r + " " + c.g + " " + c.b + " " + c.a;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

現在の色を表示

  • UITypeEditorを継承したクラスを用意して、専用の描画領域を塗りつぶす
  • GetPaintValueSupportedのオーバーライド
    • 描画を有効にする
  • PaintValueのオーバーライド
    • 塗りつぶし
  • ColorFに設定
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;

    class ColorFTypeEditor : UITypeEditor
    {
        //描画を有効にする
        public override bool  GetPaintValueSupported(ITypeDescriptorContext context)
        {
 	        return true;
        }

        //塗りつぶし
        public override void  PaintValue(PaintValueEventArgs e)
        {
            Color color = ( ( ColorF )e.Value ).ToColor();
            e.Graphics.FillRectangle(new SolidBrush(color), e.Bounds);
        }
    }

    [TypeConverter( typeof( ColorFTypeConverter ) )]
    [Editor( typeof( ColorFTypeEditor ), typeof( UITypeEditor ) )]
    public struct ColorF
    {
    /*...*/
    }

色選択ダイアログから色を設定

  • GetEditStyleのオーバーライド
    • モーダルでダイアログを表示
  • EditValueのオーバーライド
    • 色選択ダイアログの表示
    class ColorFTypeEditor : UITypeEditor
    {
    /*...*/

        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            ColorDialog d = new ColorDialog();
            d.ShowDialog();
            return new ColorF( d.Color );
        }
    }

Description

  • メンバの説明
    • プロパティにDescriptionを指定
    public class ApplicationSettings
    {
        private ColorF color;
        private Point pos;

        [Description( "r, g, b, aを0.0〜1.0で設定します" )]
        public ColorF Color
        {
            get{ return color; }
            set{ color = value; }
        }

        public Point Pos
        {
            get{ return pos; }
            set{ pos = value; }
        }
    }

Category

  • プロパティの分類
    public class ApplicationSettings
    {
        private ColorF color;
        private Point pos;

        [Category( "Hoge" )]
        [Description( "r, g, b, aを0.0〜1.0で設定します" )]
        public ColorF Color
        {
            get{ return color; }
            set{ color = value; }
        }

        [Category( "Piyo" )]
        public Point Pos
        {
            get{ return pos; }
            set{ pos = value; }
        }
    }

まとめ

  • メンバの値をカスタムして表示する場合は、はっきり言って面倒。オーバーロードしたり、文字列を解析したり。。。
  • windows標準のColorPickerはショボイので自作したい。フリーで誰か公開してないだろうか。