2013年8月20日火曜日

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

AccessのComboBoxのようにVB.NETのComboBoxのリストに複数の列を表示します。
複数の列の表示はComboBoxのDataSourceがDataTableの場合に限ります。


先ずプロジェクトに表示する列を定義するコレクションのための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

次にComboBoxの継承クラスをプロジェクトに作成します。
DrawItemイベントで列の境界線を描画しDataSourceに連結されたDataTableから列の値を取得して表示します。
selectMemberValueプロパティでは選択されている行の他の列の値を取得します。
Imports System.Windows.Forms
Imports System.Drawing
Imports System.ComponentModel
 
Public Class CustomDropDownComboBox
    Inherits System.Windows.Forms.ComboBox
 
    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>
    Friend Sub OnCollectionChanged()
 
        '列の幅の合計をドロップダウン部分の幅に設定。
        If DropDownColumns.Count > 0 Then
            MyBase.DropDownWidth = DropDownColumns(0).Width
 
            For i = 1 To DropDownColumns.Count - 1
                MyBase.DropDownWidth += DropDownColumns(i).Width
            Next
        End If
    End Sub
 
    ''' <summary>
    ''' ドロップダウンリストの列コレクションの設定。
    ''' </summary>
    ''' <remarks></remarks>
    Public Class MyCollection
        Inherits System.Collections.ObjectModel.Collection(Of DropDownColumn)
 
        Private _parent As CustomDropDownComboBox
 
        Friend Sub New(ByVal parent As CustomDropDownComboBox)
            _parent = parent
        End Sub
 
        Protected Overrides Sub ClearItems()
            MyBase.ClearItems()
            _parent.OnCollectionChanged()
        End Sub
 
        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As DropDownColumn)
 
            '列の幅の初期値
            If item.Width = Nothing Then
                item.Width = _parent.Width
            End If
 
            MyBase.InsertItem(index, item)
            _parent.OnCollectionChanged()
 
        End Sub
 
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            MyBase.RemoveItem(index)
            _parent.OnCollectionChanged()
        End Sub
 
        Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As DropDownColumn)
            MyBase.SetItem(index, item)
            _parent.OnCollectionChanged()
        End Sub
    End Class
 
    ''' <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>
    ''' 描画処理
    ''' </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
 
                '列の境界線の描画
                e.Graphics.DrawLine(grayPen, x + width, top, x + width, bottom)
 
                '列のメンバーの取得
                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


フォームに配置しての使用例

DrawModeをOwnerDrawFixedにして使用します。またDropDownStyleのSimpleには対応していません。

Imports System.Reflection
Imports System.Windows.Forms.ComboBox
 
Public Class Form1
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
 
        'サンプルデータテーブル
        Dim dt As New DataTable
        dt.Columns.Add("ID", Type.GetType("System.String"))
        dt.Columns.Add("NAME", Type.GetType("System.String"))
        dt.Columns.Add("ADDR", Type.GetType("System.String"))
 
        dt.Rows.Add("001", "名前001", "住所001")
        dt.Rows.Add("002", "名前002", "住所002")
        dt.Rows.Add("003", "名前003", "住所003")
        dt.Rows.Add("004", "名前004", "住所004")
        dt.Rows.Add("005", "名前005", "住所005")
        dt.Rows.Add("006", "名前006", "住所006")
        dt.Rows.Add("007", "名前007", "住所007")
        dt.Rows.Add("008", "名前008", "住所008")
        dt.Rows.Add("009", "名前009", "住所009")
        dt.Rows.Add("009", "名前009", "住所009")
 
        
        'CustomDropDownComboBoxの設定
        Me.CustomDropDownComboBox1.DataSource = dt
        Me.CustomDropDownComboBox1.DisplayMember = "NAME"
        Me.CustomDropDownComboBox1.ValueMember = "ID"
 
        'ドロップダウンリストの列の追加
        Dim cols As New DropDownColumn
        cols.Member = "ID"
        cols.Width = 50
        Me.CustomDropDownComboBox1.DropDownColumns.Add(cols)
 
        cols = New DropDownColumn
        cols.Member = "NAME"
        cols.Width = 100
        Me.CustomDropDownComboBox1.DropDownColumns.Add(cols)
 
        cols = New DropDownColumn("ADDR", 100)
        Me.CustomDropDownComboBox1.DropDownColumns.Add(cols)
 
        '描画モード
        Me.CustomDropDownComboBox1.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
 
    End Sub
End Class
実行結果
(注意)
DataSourceがDataTableでない場合は列は表示されません。
表示しようとする列がDataTableに無い場合の表示はされません。

1 件のコメント:

  1. mas hira様

    お~!コレ、やりたかったこと完璧です!
    ついこの前までAccessで開発してた人間なので、
    VBとの違いに四苦八苦してます(^_^;)

    このコードを参考に、
    いろいろ拡張できるように勉強します!

    ありがとうございました!!

    返信削除