DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DataGridでComboBoxを使う

注意:ここで紹介しているDataGridは、System.Windows.Forms名前空間のDataGrid(Windowsフォーム)です。System.Web.UI.WebControls名前空間のDataGrid(Webフォーム)ではありません。
注意:CodeZineの「DataGridにComboBoxを表示可能な列を作る」で詳しい解説と、最新版の公開を行っています。

DataGridでComboBoxを使用する方法はいくつかあるようですが、主に使われているのは、単純にDataGridコントロールにComboBoxコントロールをのせる方法と、DataGridColumnStyleクラスから派生した新しいクラスを作成する方法であるようです。

単純にDataGridコントロールにComboBoxコントロールをのせる方法は、マイクロソフトサポート技術情報「323167 - [HOWTO] Windows フォームの DataGrid コントロールに ComboBox コントロールを追加する方法」(リンク切れ)で紹介されています。

また、DataGridColumnStyleクラスから派生した新しいクラスを作成する方法としては、例えば C# CornerGenerating Combo Box in DataGrid Columnsなどがあります。

DataGridComboBoxColumnクラス

以下に私の作成したComboBoxをDataGridに表示するためのクラス「DataGridComboBoxColumnクラス」を紹介します。このクラスは、以下に示すページで紹介されているコードを参考にして作成しました。

本当なら、「Generating Combo Box in DataGrid Column」のようにDataGridColumnStyleクラスからの派生クラスとすべきかもしれませんが、ここでは手を抜いてDataGridTextBoxColumnクラスからの派生クラスとしています。

このDataGridComboBoxColumnクラスは、コンストラクタでComboBoxのDataSource、DisplayMember、ValueMemberプロパティに設定するデータを指定して使用します。ComboBoxとDataGridに表示されるデータは、DisplayMemberで指定されたデータになりますが、実際のデータはValueMemberで指定されたデータとなります。表示するデータと実際に使われるデータが同じでよければ、DisplayMemberとValueMemberを同じにしてもかまいません。また、DataSourceにはDataViewオブジェクトを指定します。

DataGridComboBoxColumnクラスの仕様は次のようなものです。

  • コンボボックスのDropDownStyleプロパティはComboBoxStyle.DropDownListです。
  • ReadOnlyの時は、コンボボックスが表示されません。
  • ComboBoxのDataSourceをDataGridで使用しているDataSourceと同じにすると、不都合が起こるかもしれません。
  • ComboBoxをキーで操作することはできません。
  • ComboBoxのValueMemberに登録されていないデータがDataGridで使用されている場合、DBNullとして表示されます。

DataGridComboBoxColumnクラスを使用したサンプルをこちらでも紹介しています。

コード

VB.NET
コードを隠すコードを選択
Imports System
Imports System.Data
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Security.Permissions

Namespace Dobon.Samples.Forms
    Public Class DataGridComboBoxColumn
        Inherits DataGridTextBoxColumn
        Private _comboBox As DataGridComboBox
        Private _sorce As CurrencyManager
        Private _rowNumber As Integer
        Private _editing As Boolean

        ''' <summary>
        ''' DataGridComboBoxColumnのコンストラクタ
        ''' </summary>
        ''' <param name="dataSource">ComboBoxのDataSource</param>
        ''' <param name="displayMember">ComboBoxのDisplayMember</param>
        ''' <param name="valueMember">ComboBoxのValueMember</param>
        Public Sub New(ByVal dataSource As DataView, _
                ByVal displayMember As String, _
                ByVal valueMember As String)
            '初期設定
            _sorce = Nothing
            _editing = False

            'DataGridComboBoxの作成
            _comboBox = New DataGridComboBox
            'ComboBoxの設定
            _comboBox.DropDownStyle = ComboBoxStyle.DropDownList
            _comboBox.Visible = False
            'データソースの設定
            _comboBox.DataSource = dataSource
            _comboBox.DisplayMember = displayMember
            _comboBox.ValueMember = valueMember

            'イベントハンドラ
            AddHandler _comboBox.Leave, AddressOf _comboBox_Leave
            AddHandler _comboBox.SelectionChangeCommitted, _
                AddressOf _comboBox_SelectionChangeCommitted
        End Sub

        'プロパティ
        ''' <summary>
        ''' 管理されている ComboBox コントロールを取得します。
        ''' </summary>
        Public ReadOnly Property ComboBox() As DataGridComboBox
            Get
                Return _comboBox
            End Get
        End Property

        'ComboBoxからフォーカスが離れた
        Private Sub _comboBox_Leave(ByVal sender As Object, _
                ByVal e As EventArgs)
            If _editing Then
                'ComboBoxが編集中だったとき
                _editing = False
                '行の値を更新する
                SetColumnValueAtRow(_sorce, _rowNumber, _
                    _comboBox.Text)
                Invalidate()
            End If
            'ComboBoxを隠す
            _comboBox.Visible = False
            'DataGridのスクロールイベントを捕捉する
            AddHandler DataGridTableStyle.DataGrid.Scroll, _
                AddressOf DataGrid_Scroll
        End Sub

        'DataGridがスクロール
        Private Sub DataGrid_Scroll(ByVal sender As Object, _
                ByVal e As EventArgs)
            'ComboBoxを消す
            If _comboBox.Visible Then
                _comboBox.Visible = False
            End If
        End Sub

        'ComboBoxで選択した項目が変更されて、その変更がコミットされた
        Private Sub _comboBox_SelectionChangeCommitted( _
                ByVal sender As Object, ByVal e As EventArgs)
            _editing = True
            'DataGridに列が編集開始されたことを知らせる
            MyBase.ColumnStartedEditing(CType(sender, Control))
        End Sub

        '行の最小の高さ
        Protected Overrides Function GetMinimumHeight() As Integer
            Return _comboBox.PreferredHeight
        End Function

        'ComboBoxをDataGridのControl.ControlCollectionに追加する
        Protected Overrides Sub SetDataGridInColumn( _
                ByVal value As DataGrid)
            MyBase.SetDataGridInColumn(value)
            _comboBox.Parent = CType(value, Control)
        End Sub

        '列が管理しているコントロールへのフォーカスを
        '放棄する必要があることを列に通知
        Protected Overrides Sub ConcedeFocus()
            MyBase.ConcedeFocus()
            _comboBox.Visible = False
        End Sub


        ''編集するためにセルを準備する
        Protected Overloads Overrides Sub Edit( _
                ByVal source As CurrencyManager, _
                ByVal rowNum As Integer, _
                ByVal bounds As Rectangle, _
                ByVal [readOnly] As Boolean, _
                ByVal instantText As String, _
                ByVal cellIsVisible As Boolean)
            '基本クラスのEditを呼び出す
            MyBase.Edit(source, rowNum, bounds, [readOnly], _
                instantText, cellIsVisible)
            'TextBoxを消す
            Me.TextBox.Visible = False

            '値の保存
            _rowNumber = rowNum
            _sorce = source

            '表示させるComboBoxの設定をする
            _comboBox.Bounds = bounds
            _comboBox.RightToLeft = _
                Me.DataGridTableStyle.DataGrid.RightToLeft

            '非表示、読み取り専用のときは、ComboBoxを表示しない
            If cellIsVisible AndAlso Not [readOnly] Then
                'ComboBoxを表示
                _comboBox.Visible = True
                _comboBox.BringToFront()
                _comboBox.Focus()
            End If

            '選択項目の変更
            'ComboBoxを表示する前に行うと、
            '新しい行で値を変更した時不都合が起きる
            _comboBox.SelectedIndex = _
                _comboBox.FindStringExact(Me.TextBox.Text)

            'DataGridのスクロールイベントを捕捉する
            AddHandler DataGridTableStyle.DataGrid.Scroll, _
                AddressOf DataGrid_Scroll
        End Sub

        '編集プロシージャを完了する
        Protected Overrides Function Commit( _
                ByVal dataSource As CurrencyManager, _
                ByVal rowNum As Integer) As Boolean
            If _editing Then
                '編集中のときは、rowNum行の値を設定する
                _editing = False
                SetColumnValueAtRow(dataSource, rowNum, _
                    _comboBox.Text)
            End If

            Return True
        End Function

        '指定した行の値を設定
        Protected Overrides Sub SetColumnValueAtRow( _
                ByVal source As CurrencyManager, _
                ByVal rowNum As Integer, ByVal value As Object)
            'ValueMemberの値を設定する
            MyBase.SetColumnValueAtRow(source, rowNum, _
                _comboBox.FindValueMember(value))
        End Sub

        '指定した行の値を取得
        Protected Overrides Function GetColumnValueAtRow( _
                ByVal source As CurrencyManager, _
                ByVal rowNum As Integer) As Object
            Dim val As Object = _
                MyBase.GetColumnValueAtRow(source, rowNum)
            'DisplayMemberの値を返す
            Return _comboBox.FindDisplayMember(val)
        End Function
    End Class

    Public Class DataGridComboBox
        Inherits ComboBox
        Private WM_KEYUP As Integer = &H101

        <SecurityPermission(SecurityAction.Demand, _
            Flags:=SecurityPermissionFlag.UnmanagedCode)> _
        Protected Overrides Sub WndProc( _
                ByRef theMessage As Message)
            'キー操作を無効にする
            If theMessage.Msg = WM_KEYUP Then
                Return
            End If
            MyBase.WndProc(theMessage)
        End Sub

        ''' <summary>
        ''' DisplayMemberからValueMemberの値を探す
        ''' </summary>
        ''' <param name="display">探すDisplayMember値</param>
        ''' <returns>見つかったValueMember値</returns>
        Public Function FindValueMember( _
                ByVal display As Object) As Object
            Dim dv As DataView = CType(DataSource, DataView)
            Dim rowCount As Integer = dv.Count

            'ループして探す
            Dim disp As Object
            Dim i As Integer
            For i = 0 To rowCount - 1
                disp = dv(i)(DisplayMember)
                '見つかった時
                If display.Equals(disp) Then
                    Return dv(i)(ValueMember)
                End If
            Next i
            '見つからなかった時
            Return DBNull.Value
        End Function

        ''' <summary>
        ''' ValueMemberからDisplayMemberの値を探す
        ''' </summary>
        ''' <param name="value">探すValueMember値</param>
        ''' <returns>見つかったDisplayMember値</returns>
        Public Function FindDisplayMember( _
                ByVal value As Object) As Object
            Dim dv As DataView = CType(DataSource, DataView)
            Dim rowCount As Integer = dv.Count

            'ループして探す
            Dim val As Object
            Dim i As Integer
            For i = 0 To rowCount - 1
                val = dv(i)(ValueMember)
                '見つかった時
                If value.Equals(val) Then
                    Return dv(i)(DisplayMember)
                End If
            Next i
            '見つからなかった時
            Return DBNull.Value
        End Function
    End Class
End Namespace
C#
コードを隠すコードを選択
using System;
using System.Data;
using System.Windows.Forms;
using System.Drawing;
using System.Security.Permissions;

namespace Dobon.Samples.Forms
{
    public class DataGridComboBoxColumn : DataGridTextBoxColumn
    {
        private DataGridComboBox _comboBox;
        private CurrencyManager _sorce;
        private int _rowNumber;
        private bool _editing;

        /// <summary>
        /// DataGridComboBoxColumnのコンストラクタ
        /// </summary>
        /// <param name="dataSource">ComboBoxのDataSource</param>
        /// <param name="displayMember">ComboBoxのDisplayMember</param>
        /// <param name="valueMember">ComboBoxのValueMember</param>
        public DataGridComboBoxColumn(DataView dataSource, 
            string displayMember, 
            string valueMember)
        {
            //初期設定
            _sorce = null;
            _editing = false;

            //DataGridComboBoxの作成
            _comboBox = new DataGridComboBox();
            //ComboBoxの設定
            _comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
            _comboBox.Visible = false;
            //データソースの設定
            _comboBox.DataSource = dataSource;
            _comboBox.DisplayMember = displayMember;
            _comboBox.ValueMember = valueMember;

            //イベントハンドラ
            _comboBox.Leave += new EventHandler(_comboBox_Leave);
            _comboBox.SelectionChangeCommitted += 
                new EventHandler(_comboBox_SelectionChangeCommitted);
        }

        //プロパティ
        /// <summary>
        /// 管理されている ComboBox コントロールを取得します。
        /// </summary>
        public DataGridComboBox ComboBox
        {
            get
            {
                return _comboBox;
            }
        }

        //ComboBoxからフォーカスが離れた
        private void _comboBox_Leave(object sender, EventArgs e)
        {
            if (_editing)
            {
                //ComboBoxが編集中だったとき
                _editing = false;
                //行の値を更新する
                SetColumnValueAtRow(
                    _sorce, _rowNumber, _comboBox.Text);
                Invalidate();
            }
            //ComboBoxを隠す
            _comboBox.Visible = false;
            //DataGridのスクロールイベントを捕捉する
            DataGridTableStyle.DataGrid.Scroll += 
                new EventHandler(DataGrid_Scroll);
        }

        //DataGridがスクロール
        private void DataGrid_Scroll(object sender, EventArgs e)
        {
            //ComboBoxを消す
            if (_comboBox.Visible)
                _comboBox.Visible = false;
        }

        //ComboBoxで選択した項目が変更されて、その変更がコミットされた
        private void _comboBox_SelectionChangeCommitted(
            object sender, EventArgs e)
        {
            _editing = true;
            //DataGridに列が編集開始されたことを知らせる
            base.ColumnStartedEditing((Control) sender);
        }

        //行の最小の高さ
        protected override int GetMinimumHeight()
        {
            return _comboBox.PreferredHeight;
        }

        //ComboBoxをDataGridのControl.ControlCollectionに追加する
        protected override void SetDataGridInColumn(DataGrid value)
        {
            base.SetDataGridInColumn(value);
            _comboBox.Parent = (Control) value;
        }

        //列が管理しているコントロールへのフォーカスを
        //放棄する必要があることを列に通知
        protected override void ConcedeFocus()
        {
            base.ConcedeFocus();
            _comboBox.Visible = false;
        }

        //編集するためにセルを準備する
        protected override void Edit(CurrencyManager source,
            int rowNum, Rectangle bounds, bool readOnly,
            string instantText, bool cellIsVisible)
        {
            //基本クラスのEditを呼び出す
            base.Edit(source, rowNum, bounds, readOnly, instantText,
                cellIsVisible);
            //TextBoxを消す
            this.TextBox.Visible = false;
            
            //値の保存
            _rowNumber = rowNum;
            _sorce = source;

            //表示させるComboBoxの設定をする
            _comboBox.Bounds = bounds;
            _comboBox.RightToLeft =
                this.DataGridTableStyle.DataGrid.RightToLeft;
            //非表示、読み取り専用のときは、ComboBoxを表示しない
            if (cellIsVisible && !readOnly)
            {
                //ComboBoxを表示
                _comboBox.Visible = true;
                _comboBox.BringToFront();
                _comboBox.Focus();
            }

            //選択項目の変更
            //ComboBoxを表示する前に行うと、
            //新しい行で値を変更した時不都合が起きる
            _comboBox.SelectedIndex =
                _comboBox.FindStringExact(this.TextBox.Text);

            //DataGridのスクロールイベントを捕捉する
            DataGridTableStyle.DataGrid.Scroll +=
                new EventHandler(DataGrid_Scroll);
        }

        //編集プロシージャを完了する
        protected override bool Commit(
            CurrencyManager dataSource, int rowNum)
        {
            if (_editing)
            {
                //編集中のときは、rowNum行の値を設定する
                _editing = false;
                SetColumnValueAtRow(
                    dataSource, rowNum, _comboBox.Text);
            }

            return true;
        }

        //指定した行の値を設定
        protected override void SetColumnValueAtRow(
            CurrencyManager source, int rowNum, object value)
        {
            //ValueMemberの値を設定する
            base.SetColumnValueAtRow(
                source, rowNum, _comboBox.FindValueMember(value));
        }

        //指定した行の値を取得
        protected override object GetColumnValueAtRow(
            CurrencyManager source, int rowNum)
        {
            object val = base.GetColumnValueAtRow(source, rowNum);
            //DisplayMemberの値を返す
            return _comboBox.FindDisplayMember(val);
        }

    }

    public class DataGridComboBox: ComboBox
    {
        private const int WM_KEYUP = 0x101;

        [SecurityPermission(SecurityAction.Demand,
            Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(
            ref Message theMessage)
        {
            //キー操作を無効にする
            if (theMessage.Msg == WM_KEYUP)
            {
                return;
            }
            base.WndProc(ref theMessage);
        }

        /// <summary>
        /// DisplayMemberからValueMemberの値を探す
        /// </summary>
        /// <param name="display">探すDisplayMember値</param>
        /// <returns>見つかったValueMember値</returns>
        public object FindValueMember(object display)
        {
            DataView dv = (DataView) DataSource;
            int rowCount = dv.Count;

            //ループして探す
            object disp;
            for (int i = 0; i < rowCount; i++)
            {
                disp = dv[i][DisplayMember];
                //見つかった時
                if (display.Equals(disp))
                {
                    return dv[i][ValueMember];
                }
            }
            //見つからなかった時
            return DBNull.Value;
        }
        
        /// <summary>
        /// ValueMemberからDisplayMemberの値を探す
        /// </summary>
        /// <param name="value">探すValueMember値</param>
        /// <returns>見つかったDisplayMember値</returns>
        public object FindDisplayMember(object value)
        {
            DataView dv = (DataView) DataSource;
            int rowCount = dv.Count;

            //ループして探す
            object val;
            for (int i = 0; i < rowCount; i++)
            {
                val = dv[i][ValueMember];
                //見つかった時
                if (value.Equals(val))
                {
                    return dv[i][DisplayMember];
                }
            }
            //見つからなかった時
            return DBNull.Value;
        }
    }
}

使い方

DataGridComboBoxColumnクラスの具体的な使用法を以下に示します。ここでは、1から10までの数字を入力するためにComboBoxを使っています。表示させる文字列は、漢字(一二三...)としています。

VB.NET
コードを隠すコードを選択
'DataGridに表示するDataTableの作成
Dim dt As New DataTable("DataTable1")
dt.Columns.Add("Column1", GetType(Integer))
dt.Rows.Add(New Object() {5})
dt.Rows.Add(New Object() {9})
dt.Rows.Add(New Object() {3})

'DataGridで表示するデータソースに設定
DataGrid1.DataSource = dt

'DataGridTableStyleの作成
Dim ts As New DataGridTableStyle
ts.MappingName = "DataTable1"

'DataGridComboBoxColumnで使用するDataTableの作成
'"DisplayMember"列はComboBoxに表示される値
'"ValueMember"列は実際の値
Dim comboSorce As New DataTable("ComboBox")
comboSorce.Columns.Add("DisplayMember", GetType(String))
comboSorce.Columns.Add("ValueMember", GetType(Integer))
comboSorce.Rows.Add(New Object() {"一", 1})
comboSorce.Rows.Add(New Object() {"二", 2})
comboSorce.Rows.Add(New Object() {"三", 3})
comboSorce.Rows.Add(New Object() {"四", 4})
comboSorce.Rows.Add(New Object() {"五", 5})
comboSorce.Rows.Add(New Object() {"六", 6})
comboSorce.Rows.Add(New Object() {"七", 7})
comboSorce.Rows.Add(New Object() {"八", 8})
comboSorce.Rows.Add(New Object() {"九", 9})
comboSorce.Rows.Add(New Object() {"十", 10})

'DataGridComboBoxColumnの作成
Dim cbc As New Dobon.Samples.Forms.DataGridComboBoxColumn( _
    comboSorce.DefaultView, "DisplayMember", "ValueMember")
cbc.MappingName = "Column1"
cbc.HeaderText = "数字"

'列スタイルの追加
ts.GridColumnStyles.Add(cbc)

'テーブルスタイルの追加
DataGrid1.TableStyles.Add(ts)
C#
コードを隠すコードを選択
//DataGridに表示するDataTableの作成
DataTable dt = new DataTable("DataTable1");
dt.Columns.Add("Column1", typeof(int));
dt.Rows.Add(new object[] {5});
dt.Rows.Add(new object[] {9});
dt.Rows.Add(new object[] {3});

//DataGridで表示するデータソースに設定
dataGrid1.DataSource = dt;

//DataGridTableStyleの作成
DataGridTableStyle ts = new DataGridTableStyle();
ts.MappingName = "DataTable1";

//DataGridComboBoxColumnで使用するDataTableの作成
//"DisplayMember"列はComboBoxに表示される値
//"ValueMember"列は実際の値
DataTable comboSorce = new DataTable("ComboBox");
comboSorce.Columns.Add("DisplayMember", typeof(string));
comboSorce.Columns.Add("ValueMember", typeof(int));
comboSorce.Rows.Add(new object[] {"一", 1});
comboSorce.Rows.Add(new object[] {"二", 2});
comboSorce.Rows.Add(new object[] {"三", 3});
comboSorce.Rows.Add(new object[] {"四", 4});
comboSorce.Rows.Add(new object[] {"五", 5});
comboSorce.Rows.Add(new object[] {"六", 6});
comboSorce.Rows.Add(new object[] {"七", 7});
comboSorce.Rows.Add(new object[] {"八", 8});
comboSorce.Rows.Add(new object[] {"九", 9});
comboSorce.Rows.Add(new object[] {"十", 10});

//DataGridComboBoxColumnの作成
Dobon.Samples.Forms.DataGridComboBoxColumn cbc =
    new Dobon.Samples.Forms.DataGridComboBoxColumn(
        comboSorce.DefaultView, "DisplayMember", "ValueMember");
//表示される値と実際の値が同じでよければ、次のようにもできる
//Dobon.Samples.Forms.DataGridComboBoxColumn cbc =
//    new Dobon.Samples.Forms.DataGridComboBoxColumn(
//        comboSorce.DefaultView, "ValueMember", "ValueMember");
cbc.MappingName = "Column1";
cbc.HeaderText = "数字";

//列スタイルの追加
ts.GridColumnStyles.Add(cbc);

//テーブルスタイルの追加
dataGrid1.TableStyles.Add(ts);
  • 履歴:
  • 2010/6/30 WndProcにSecurityPermissionAttributeを付けた。
  • 2013/12/9 SecurityAction.LinkDemandの代わりにSecurityAction.Demandを使うようにした。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。