2013年9月10日火曜日

【VB.NET】DataGridViewのComboBoxのリストに複数の列を表示する

DataGridViewComboBoxのDataGridViewComboBoxColumn、DataGridViewComboBoxCellおよび
DataGridViewComboBoxEditingControlの各クラスを継承しドロップダウンリストに複数列表示する
DataGridViewCustomDropDownComboBoxの作成。

【VB.NET】ComboBoxのリストに複数の列を表示と同様に複数列の表示はDataSourceがDataTableの場合のみに限定します。DataSourceがDataTable以外の時は複数列の表示設定は無視されます。

先ず表示する列を定義するコレクションのためのDropDownColumnクラスを作ります。
(これは【VB.NET】ComboBoxのリストに複数の列を表示するのDropDownColumnクラス同じです
したがって同じプロジェクト内に含める場合は追加の必要はありません)

Imports System.Drawing
Imports System.ComponentModel
 
''' <summary>
''' ドロップダウンリストに表示される列の定義
''' </summary>
''' <remarks></remarks>
Public Class DropDownColumn
 
    ''' <summary>
    ''' 列のプロパティ
    ''' </summary>
    ''' <remarks></remarks>
    Private _member As String
 
    ''' <summary>
    ''' 列のプロパティを設定または取得します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Description("列のプロパティ")>
    Public Property Member As String
        Get
            Return _member
        End Get
        Set(ByVal value As String)
            _member = value
        End Set
    End Property
 
    ''' <summary>
    ''' 列の幅
    ''' </summary>
    ''' <remarks></remarks>
    Private _width As Integer = Nothing
 
    ''' <summary>
    ''' 列の幅を設定または取得します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Description("列の幅")>
    Public Property Width As Integer
        Get
            Return _width
        End Get
        Set(ByVal value As Integer)
            _width = value
        End Set
    End Property
 
    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        'インスタンスの初期化
    End Sub
 
    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="paramMember">表示する列名</param>
    ''' <param name="paramWidth">幅</param>
    ''' <remarks>ドロップダウンリストに表示される列の定義</remarks>
    Public Sub New(ByVal paramMember As String, ByVal paramWidth As Integer)
        Member = paramMember
        Width = paramWidth
    End Sub
End Class

次に各継承クラスを追加します。
  1. DataGridViewCustomDropDownComboBoxColumn‥DataGridViewComboBoxColumnを継承しCellTemplateに
    DataGridViewCustomDropDownComboBoxCellクラスのインスタンスを設定します。
  2. DataGridViewCustomDropDownComboBoxCell‥DataGridViewComboBoxCellを継承し
    DataGridViewCustomDropListComboBoxEditingControlをホストします。
  3. DataGridViewCustomDropListComboBoxEditingControl‥DataGridViewComboBoxEditingControlを継承しここでドロップダウンリストのアイテムが
    描画される時に複数列を描画します。
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Drawing
 
''' <summary>
''' 複数列のドロップダウンを表示するComboBoxColumn
''' </summary>
''' <remarks></remarks>
Public Class DataGridViewCustomDropDownComboBoxColumn
    Inherits DataGridViewComboBoxColumn
 
    Private _item As New MyCollection(Me)
 
    ''' <summary>
    ''' ドロップダウンリストの列を設定します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
    <Category("ドロップダウンリストの列のカスタマイズ")> _
    <Description("ドロップダウンリストの列を設定します")>
    Public Property DropDownColumns() As MyCollection
        Get
            Return _item
        End Get
        Set(ByVal value As MyCollection)
            _item = value
        End Set
    End Property
 
    ''' <summary>
    ''' ドロップダウンリストの列コレクションの設定。
    ''' </summary>
    ''' <remarks></remarks>
    Public Class MyCollection
        Inherits System.Collections.ObjectModel.Collection(Of DropDownColumn)
 
        Private _parent As DataGridViewCustomDropDownComboBoxColumn
 
        Friend Sub New(ByVal parent As DataGridViewCustomDropDownComboBoxColumn)
            _parent = parent
        End Sub
 
        Protected Overrides Sub ClearItems()
            MyBase.ClearItems()
        End Sub
 
        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As DropDownColumn)
            MyBase.InsertItem(index, item)
 
        End Sub
 
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            MyBase.RemoveItem(index)
        End Sub
 
        Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As DropDownColumn)
            MyBase.SetItem(index, item)
        End Sub
    End Class
 
    '新しいプロパティを追加しているため、
    ' Cloneメソッドをオーバーライドする必要がある
    Public Overrides Function Clone() As Object
        Dim col As DataGridViewCustomDropDownComboBoxColumn = _
            DirectCast(MyBase.Clone(), DataGridViewCustomDropDownComboBoxColumn)
        col.DropDownColumns = Me.DropDownColumns
        Return col
    End Function
 
    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        Dim cel As New DataGridViewCustomDropDownComboBoxCell
        Me.CellTemplate = cel
    End Sub
 
    ''' <summary>
    ''' 現在選択されている行のメンバーの値を取得します。
    ''' </summary>
    ''' <param name="member"></param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property selectMemberValue(ByVal member As String)
        Get
            Dim cel As DataGridViewCustomDropDownComboBoxCell = _
                TryCast(Me.CellTemplate, DataGridViewCustomDropDownComboBoxCell)
 
            Return cel.selectMemberValue(member)
        End Get
    End Property
End Class
 
''' <summary>
''' 複数列のドロップダウンを表示するComboBoxCell
''' </summary>
''' <remarks></remarks>
Public Class DataGridViewCustomDropDownComboBoxCell
    Inherits DataGridViewComboBoxCell
 
    ''' <summary>
    ''' EditType
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides ReadOnly Property EditType() As Type
        Get
            Return GetType(DataGridViewCustomDropListComboBoxEditingControl)
        End Get
    End Property
 
    ''' <summary>
    ''' InitializeEditingControl
    ''' </summary>
    ''' <param name="rowIndex"></param>
    ''' <param name="initialFormattedValue"></param>
    ''' <param name="dataGridViewCellStyle"></param>
    ''' <remarks></remarks>
    Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, ByVal initialFormattedValue As Object, _
                                                  ByVal dataGridViewCellStyle As DataGridViewCellStyle)
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
 
        'セルを格納している複数列表示のコンボボックス列を取得
        Dim clm As DataGridViewCustomDropDownComboBoxColumn = _
            TryCast(Me.OwningColumn, DataGridViewCustomDropDownComboBoxColumn)
 
        '現在のセルでホストされている複数列表示のコンボボックスコントロール
        Dim ctrl As DataGridViewCustomDropListComboBoxEditingControl = _
            TryCast(DataGridView.EditingControl, DataGridViewCustomDropListComboBoxEditingControl)
 
        ctrl.ownerCell = Me
 
        'ホストされている複数列表示のコンボボックスコントロールの列の設定をクリア
        ctrl.DropDownColumns.Clear()
 
        'ホストされている複数列表示のコンボボックスコントロールの列の設定
        For i = 0 To clm.DropDownColumns.Count - 1
            ctrl.DropDownColumns.Add(New DropDownColumn(clm.DropDownColumns(i).Member, clm.DropDownColumns(i).Width))
        Next
 
    End Sub
 
    ''' <summary>
    ''' 現在選択されている行のメンバーの値を取得します。
    ''' </summary>
    ''' <param name="member"></param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property selectMemberValue(ByVal member As String)
        Get
            '現在のセルでホストされている複数列表示のコンボボックスコントロール
            Dim ctrl As DataGridViewCustomDropListComboBoxEditingControl = _
                TryCast(DataGridView.EditingControl, DataGridViewCustomDropListComboBoxEditingControl)
 
            Return ctrl.selectMemberValue(member)
        End Get
    End Property
 
End Class
 
''' <summary>
''' DataGridViewCustomDropListComboBoxEditingControl
''' </summary>
''' <remarks></remarks>
Public Class DataGridViewCustomDropListComboBoxEditingControl
    Inherits DataGridViewComboBoxEditingControl
 
    Private _item As New MyCollection(Me)
 
    ''' <summary>
    ''' ドロップダウンリストの列を設定します。
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
    <Category("ドロップダウンリストの列のカスタマイズ")> _
    <Description("ドロップダウンリストの列を設定します")>
    Public Property DropDownColumns() As MyCollection
        Get
            Return _item
        End Get
        Set(ByVal value As MyCollection)
            _item = value
        End Set
    End Property
 
    ''' <summary>
    ''' ドロップダウンリストの列コレクションの設定。
    ''' </summary>
    ''' <remarks></remarks>
    Public Class MyCollection
        Inherits System.Collections.ObjectModel.Collection(Of DropDownColumn)
 
        Private _parent As DataGridViewCustomDropListComboBoxEditingControl
 
        Friend Sub New(ByVal parent As DataGridViewCustomDropListComboBoxEditingControl)
            _parent = parent
        End Sub
 
        Protected Overrides Sub ClearItems()
            MyBase.ClearItems()
        End Sub
 
        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As DropDownColumn)
            MyBase.InsertItem(index, item)
        End Sub
 
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            MyBase.RemoveItem(index)
        End Sub
 
        Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As DropDownColumn)
            MyBase.SetItem(index, item)
        End Sub
    End Class
 
    Property ownerCell As DataGridViewCustomDropDownComboBoxCell = Nothing
 
    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        Me.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed
    End Sub
 
    ''' <summary>
    ''' 現在選択されている行のメンバーの値を取得します。
    ''' </summary>
    ''' <param name="member"></param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property selectMemberValue(ByVal member As String)
        Get
            'データソースがない場合はNothingを返す。
            If MyBase.DataSource Is Nothing Then
                Return Nothing
            End If
 
            'データソースがデータテーブルでない場合はNothingを返す。
            If MyBase.DataSource.GetType IsNot GetType(DataTable) Then
                Return Nothing
            End If
 
            'DataTableを取得
            Dim dt As DataTable = CType(Me.DataSource, DataTable)
 
            'メンバーがデータテーブルの列でない場合はNothingを返す。
            If dt.Columns.IndexOf(member) = -1 Then
                Return Nothing
            End If
 
            'メンバーがデータテーブルの列の場合値を返す。
            Return dt.Rows(Me.SelectedIndex).Item(member)
        End Get
    End Property
 
    ''' <summary>
    ''' ドロップダウンリストItemが描画される時
    ''' </summary>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
        MyBase.OnDrawItem(e)
        '選択されているアイテムのハイライト
        e.DrawBackground()
        e.DrawFocusRectangle()
 
        'データソースがない場合は除外。
        If MyBase.DataSource Is Nothing Then
            Exit Sub
        End If
 
        'データソースがデータテーブルでない場合は除外
        If MyBase.DataSource.GetType IsNot GetType(DataTable) Then
            Exit Sub
        End If
 
        'データテーブルの取得
        Dim dt As DataTable = CType(MyBase.DataSource, DataTable)
 
        'アイテムの描画座標の取得
        Dim x As Integer = e.Bounds.X
        Dim y As Integer = e.Bounds.Y
        Dim width As Integer = e.Bounds.X
        Dim height As Integer = e.Bounds.Height
        Dim top As Integer = e.Bounds.Top
        Dim bottom As Integer = e.Bounds.Bottom
 
        '色の取得
        Dim grayPen As New Pen(Brushes.Gray)
        Dim brsh As New SolidBrush(e.ForeColor)
 
        '文字列の表示形式
        Dim sf As New StringFormat
        sf.Trimming = StringTrimming.EllipsisWord
 
        Try
            For i = 0 To Me.DropDownColumns.Count - 1
 
                '列の幅の取得
                width = Me.DropDownColumns(i).Width
 
                '最後の列の時は幅は右端まで
                If i = Me.DropDownColumns.Count - 1 AndAlso Me.Width > Me.DropDownWidth Then
                    width += Me.DropDownWidth - width
                End If
 
                '列の境界線の描画
                If i < Me.DropDownColumns.Count - 1 Then
                    e.Graphics.DrawLine(grayPen, x + width, top, x + width, bottom)
                End If
 
                '列のメンバーの取得
                Dim columnIndex As String = Me.DropDownColumns(i).Member
 
                '列のメンバーがない場合またはデータテーブルの列でない場合は除外
                If columnIndex Is Nothing OrElse columnIndex = String.Empty _
                    OrElse dt.Columns.IndexOf(columnIndex) = -1 Then
                    x += width
                    Continue For
                End If
 
                '列に表示する値を取得
                Dim text As String = dt.Rows(e.Index)(columnIndex).ToString
                '描画領域
                Dim rectF As New RectangleF(CSng(x), CSng(y), CSng(width), CSng(height))
                '値の描画
                e.Graphics.DrawString(text, e.Font, brsh, rectF, sf)
 
                '次の列の座標
                x += width
 
            Next
        Finally
            'リソースの解放
            grayPen.Dispose()
            brsh.Dispose()
            sf.Dispose()
        End Try
    End Sub
 
End Class
以下は簡単な使用例です。
Public Class Form1
 
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 
        'テーブルの作成
        Dim DtPerson As New DataTable
 
        DtPerson.Columns.Add("ID", Type.GetType("System.String"))
        DtPerson.Columns.Add("NAME", Type.GetType("System.String"))
        DtPerson.Columns.Add("ADDRESS", Type.GetType("System.String"))
 
        For i = 0 To 10
            Dim nr As DataRow = DtPerson.NewRow
            nr("ID") = i.ToString("000")
            nr("NAME") = "氏名" & i.ToString("000")
            nr("ADDRESS") = "新潟県○○市" & i.ToString("000")
            DtPerson.Rows.Add(nr)
 
        Next
 
        'DataGridViewComboBoxColumnを作成()
        Dim comboBoxColumn As New DataGridViewCustomDropDownComboBoxColumn
 
        With comboBoxColumn
            .HeaderText = "ID"
            .DataPropertyName = "ID"
            .DisplayMember = "ID"
            .ValueMember = "ID"
            .DataSource = DtPerson
            .DropDownWidth = 450
            .DropDownColumns.Add(New MyLibrary.DropDownColumn("ID", 100))
            .DropDownColumns.Add(New MyLibrary.DropDownColumn("NAME", 150))
            .DropDownColumns.Add(New MyLibrary.DropDownColumn("ADDRESS", 200))
        End With
 
        Me.DataGridView1.Columns.Add(comboBoxColumn)
    End Sub
End Class
実行結果
以外にあっさりできたので色々不具合でもあるのではと考えてしまいます。