2007年8月20日月曜日

DataGridViewのセルを拡張する(2) コンボボックスをアンバウンドで使う その2

初期状態のコンボボックスセルでは、普通のコントロールのように適当なクラスをアイテムに利用していると、
フォーカスが遷移するあたりで例外がサックリと発生してしまうと思う。逆に言えば、ここをクリアするとDataGridViewでも
コンボボックスは普段通り利用可能になるというコトで。じゃあ何が問題で例外が発生しているかというと。

  • コンボボックスの編集結果をセルに表示する
  • セルに表示されている値から編集時のコンボボックスを調整する

初期状態で足りないのはこの2点。実際には上の「セルに表示する」ってのは、致命傷というほどじゃなくそのままいこうと思えば
そのまま行ってしまえる程度なんだよね。なので本当に問題なのは下の「セルから編集時のコンボボックスを調整」する方。
ここが初期状態のコンボボックスセル関連で不足している部分。アンバウンドだから発生するんだよね、ここは。

じゃあこれをどこで行えばいいか、と言われると。
「編集の前段階」なのでDataGridViewCellで行う処理だ、ということになるんだよね。実際には

DataGridViewCell.ParseFormattedValue メソッド (System.Windows.Forms)

というメソッドがあるのでここで行うことに。実は違う方法もあるんだけど、それはまた別の機会かな。
TypeConverterを利用する方法は結構難しいところがあるので。

''' <summary>表示用に書式設定された値を実際のセル値に変換します</summary>
Public Overrides Function ParseFormattedValue( _
    ByVal formattedValue As Object, _
    ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
    ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
    ByVal valueTypeConverter As System.ComponentModel.TypeConverter) As Object

    Dim columnEdit As ExComboItemColumn _
        = TryCast(Me.DataGridView.Columns(ColumnIndex), ExComboItemColumn)
    If columnEdit Is Nothing Then Return Nothing

    For Each cellItem As Object In columnEdit.Items
        If cellItem.ToString = formattedValue.ToString Then Return cellItem
    Next
    Return Nothing
End Function

大体こんな感じ。ここだけみると「あれ?」という感じになるんだけど、実はカラム側で下準備が一つある。

''' <summary>コンストラクタ</summary>
Public Sub New()
    MyBase.New()
    Me.CellTemplate = New ExComboItemCell
    Me.ValueType = GetType(ComboItem)
End Sub

カラム側のコンストラクタでValueTypeプロパティにComboItemクラスのTypeを設定しているところがある。
こうしておくと、このカラムではComboItemクラスのインスタンスを値として扱うよ、という宣言になるのよ。
では最終的なソースを。

''' <summary>独自クラスを利用できるコンボボックス型DataGridViewCell</summary>
Public Class ExComboItemCell
    Inherits System.Windows.Forms.DataGridViewComboBoxCell

    ''' <summary>
    ''' EditTypeプロパティ
    ''' </summary>
    ''' <value>ExComboItemEditingControlのType</value>
    ''' <remarks>ExComboItemEditingControlを返却する</remarks>
    Public Overrides ReadOnly Property EditType() As System.Type
        Get
            Return GetType(ExComboItemEditingControl)
        End Get
    End Property

    ''' <summary>入力コントロール初期化</summary>
    Public Overrides Sub InitializeEditingControl( _
        ByVal rowIndex As Integer, _
        ByVal initialFormattedValue As Object, _
        ByVal dataGridViewCellStyle As System.Windows.Forms.DataGridViewCellStyle)

        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
        Dim columnEdit As ExComboItemColumn _
            = DirectCast(Me.DataGridView.Columns(ColumnIndex), ExComboItemColumn)
        If columnEdit IsNot Nothing Then

        End If
    End Sub

    ''' <summary>クローンの生成</summary>
    Public Overrides Function Clone() As Object
        'プロパティのクローン作成
        Dim cloneCell As ExComboItemCell = DirectCast(MyBase.Clone, ExComboItemCell)

        Return cloneCell
    End Function

    ''' <summary>表示用に書式設定された値を実際のセル値に変換します</summary>
    Public Overrides Function ParseFormattedValue( _
        ByVal formattedValue As Object, _
        ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
        ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal valueTypeConverter As System.ComponentModel.TypeConverter) As Object

        Dim columnEdit As ExComboItemColumn _
            = TryCast(Me.DataGridView.Columns(ColumnIndex), ExComboItemColumn)
        If columnEdit Is Nothing Then Return Nothing

        For Each cellItem As Object In columnEdit.Items
            If cellItem.ToString = formattedValue.ToString Then Return cellItem
        Next
        Return Nothing
    End Function

    ''' <summary>表示用に書式指定済みのセル値を取得します</summary>
    Protected Overrides Function GetFormattedValue( _
        ByVal value As Object, _
        ByVal rowIndex As Integer, _
        ByRef cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
        ByVal valueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal context As System.Windows.Forms.DataGridViewDataErrorContexts) As Object

        If value Is Nothing Then Return ""
        Return value.ToString
    End Function
End Class

''' <summary>独自クラスを利用できるコンボボックス型DataGridViewColumn</summary>
<System.Diagnostics.DebuggerStepThrough()> _
Public Class ExComboItemColumn
    Inherits System.Windows.Forms.DataGridViewComboBoxColumn

    ''' <summary>コンストラクタ</summary>
    Public Sub New()
        MyBase.New()
        Me.CellTemplate = New ExComboItemCell
        Me.ValueType = GetType(ComboItem)
    End Sub

    ''' <summary>CellTemplateプロパティ</summary>
    ''' <exception cref="ArgumentException">ExComboItemCell以外が指定されました</exception>
    Public Overrides Property CellTemplate() As System.Windows.Forms.DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As System.Windows.Forms.DataGridViewCell)
            If Not (TypeOf value Is ExComboItemCell) Then
                Throw New ArgumentException("CellTemplateプロパティはExComboItemCellを設定する必要があります。")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

End Class

''' <summary>独自クラスを利用できるコンボボックス型DataGridViewEditingControl</summary>
<System.ComponentModel.DesignTimeVisible(False)> _
<System.ComponentModel.ToolboxItem(False)> _
Public Class ExComboItemEditingControl
    Inherits System.Windows.Forms.DataGridViewComboBoxEditingControl

    ''' <summary>Enterイベント</summary>
    Protected Overrides Sub OnEnter(ByVal e As System.EventArgs)

        Dim nowIndex As Integer = Me.SelectedIndex
        Dim editColumn As ExComboItemColumn _
            = TryCast(Me.EditingControlDataGridView.Columns(Me.EditingControlDataGridView.CurrentCell.ColumnIndex), ExComboItemColumn)
        If editColumn Is Nothing Then Return
        Me.Items.Clear()
        For Each columnItem As Object In editColumn.Items
            Me.Items.Add(columnItem)
        Next
        Me.SelectedIndex = nowIndex
        MyBase.OnEnter(e)

    End Sub

End Class

ポイントは「面倒だからもともとのComboBoxセル関係を継承してしまう」というところ。
あと、これは微妙なんだけどExComboItemEditingControlのEnter時にアイテムの再設定を行っているんだけど、
これはセルのInitializeEditingControlメソッドで行うのがベターな気がするね。

0 件のコメント:

コメントを投稿