注意:ここで紹介している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# Corner の Generating Combo Box in DataGrid Columnsなどがあります。
以下に私の作成した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クラスの仕様は次のようなものです。
DataGridComboBoxColumnクラスを使用したサンプルをこちらでも紹介しています。
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
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を使っています。表示させる文字列は、漢字(一二三...)としています。
'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)
//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);