DataGridViewComboBoxCellのデータ連結 まとめ

過去の日記でも採りあげていろいろ試してきたが、DataGridViewのコンボボックスカラムに設定される、DataGridViewComboBoxCellにデータ連結を行う場合のまとめを自分のために書いておこうと思う。(また、暫くしたら忘れるだろうし、何よりここに書くことで処理の推敲になり、書いている傍からバグに気づいたり、もっと良い書き方を思いついたりするのだ。それが素晴らしい)

  • データ連結対象のオブジェクトと型について

コンボボックス型のカラムを設定するということは、「複数の候補の中から一つを選択する」というユーザインタフェースを実現したいからに他ならないはずである。しかし、だからといってリスト形式のオブジェクトを連結の対象とすると失敗する。最終的にデータストアに格納するのは「選択した中の1値」であり複数の候補ではないのだから、この場合連結対象とする型は、データストアに準じた型か、それを含んだ型にするのが正しい。(それでもちゃんとコンボボックスに連結できる)例えば、日記で採りあげたGenderクラスだが、複数のGenderを格納したGenderListでは無くGender型自身を連結対象とするのだ。

public class Hoge
{
    public Gender Gender  // コンボボックスへのデータ連結対象(リストやコレクション型である必要は無い)
    {
        get { return this.gender; }
        set { this.gender = value; }
    }
}
public class Gender
{
    private int value;
    private string name;
    public Gender(int value, string name)
        :base()
    {
        this.value = value;
        this.name = name;
    }
    public int Value
    {
        get { return this.value; }
        set { this.value = value; }
    }
    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }
    public override string ToString()
    {
        return this.Name;
    }
    /* 共有する同値のインスタンスを生成するファクトリ */
    private static Gender male = new Gender(1, "男");
    private static Gender female = new Gender(0, "女");

    public static Gender GetMale()
    {
        return male;
    }
    public static Gender GetFeMale()
    {
        return female;
    }
    public static Gender GetGender(string name)
    {
        return (name == "男") ?
            GetMale() :
            GetFeMale();
    }
}
  • データ連結対象のプロパティについて

複数の候補の中から選んだ一値をデータ連結の対象にするので、リスト型の連結で使用していたDataSourceに直接連結する方法は使わない。通常のComboBoxクラスであれば"SelectedItem"プロパティだが、DataGridViewComboBoxCellにそのようなプロパティは無い。"DataPropertyName"に、対象のオブジェクトのプロパティを連結する。

  • DataGridViewComboBocCellの派生クラスについて

DataGrifViewComboBoxColumnは、セルのテンプレートとして、DataGridViewComboBocCellクラスか、その派生クラスを使うが、ユーザが独自のセルクラスをテンプレートとして使うための方法を提供している。

DataGridViewComboBoxColumn comboCol = new DataGridViewComboBoxColumn();
comboCol.CellTemplate = new GenderComboBoxCell();

いろいろ悩むよりはDataGridViewComboBoxCellでデータ連結を行いたいのであれば、連結したい型に合わせて、同クラスの派生クラスを用意したほうが結果として楽だし確実だ。派生クラスをGenderComboBoxCellクラスとして考えてみよう。オーバライドが必須となるメソッドは以下の通り。

    • Clone メソッド

一値に連結するのだが、その一値を候補の中から選ぶのがコンボボックスである。従って候補となる値を予めコンボボックス中に追加しておく必要がある。DataGridViewComboBoxColumnはセルのテンプレートをクローンによって生成するので、元々のDataGridViewComboBoxCellクラスで定義されていないプロパティやフィールドは、同メソッドでセットしてやる必要がある。また、同メソッドは何度も呼ばれるようで、単に値をコンボボックスのItemsに追加するだけだと重複してしまうのでItemsをクリアする必要がある。

    public override object Clone()
    {
        GenderComboBoxCell newInstance = base.Clone() as GenderComboBoxCell;
        newInstance.Items.Clear();
        newInstance.Items.Add(Gender.GetMale());
        newInstance.Items.Add(Gender.GetFeMale());
        return newInstance;
    }

今思いついたが、newInstance.Items.Clear()は無駄な処理なので、今回のようにCloneで他の状態や属性などをコピーする必要が無いのであれば(Cloneが単純に同型のインスタンスを返すだけでよいならば)、以下のようにコンストラクタでアイテムをセットして、Cloneでは毎回新たなインスタンスを生成してやるほうがいいのかもしれない。

    public GenderComboBoxCell()
        :base()
    {
        this.Items.Add(Gender.GetMale());
        this.Items.Add(Gender.GetFeMale());
    }
    public override object Clone()
    {
        return new GenderComboBoxCell();
    }
    • ParseFormattedValue メソッド

セルに入力された値(大抵は文字列)を連結対象の型に変換してやる処理が必要になる。例となったGenderクラスの場合、ファクトリメソッドやパースメソッドを用意して、それを同メソッドから呼べば良いだろう。

    public override object ParseFormattedValue(
        object formattedValue,
        DataGridViewCellStyle cellStyle,
        TypeConverter formattedValueTypeConverter,
        TypeConverter valueTypeConverter)
    {
        return Gender.GetGender(formattedValue as string);
    }
    • GetFormattedValue メソッド

このメソッドは同クラスとして設定されているセルが描画対象となった際に、連結対象の型を表示形式に変換してやる処理が必要になる。一番プリミティブなのは、ToSting()メソッドを呼ぶだけの実装だろう。

    protected override object GetFormattedValue(
        object value,
        int rowIndex,
        ref DataGridViewCellStyle cellStyle,
        TypeConverter valueTypeConverter,
        TypeConverter formattedValueTypeConverter,
        DataGridViewDataErrorContexts context)
    {
        return ((value as Gender) != null) ? (value as Gender).ToString():"";
    }