在 DataGridView ComboBox 列中使用 Enum 类






4.30/5 (6投票s)
这是一个在 Windows 窗体中使用 C++/CLI 在 DataGridView 的枚举类列中显示数据的可运行示例,数据来自多个数据源。

目标
本文的目的是提供一个在 Windows 窗体中使用 C++/CLI 在 DataGridView 的枚举类列中显示数据的可运行示例,数据通过 DataTables
从多个数据源中提取。文章还将展示一种更简化的方法,该方法从单个源绘制数据而不使用 DataTable
,以及一些其他不相关的功能。
- 行条纹
- 单元格高亮
- 单个单元格中的错误图标
- 将
enum
类用作标志集 - 从多个数据源构建
DataGridView
背景
在网络搜索中,关于 C++/CLI 或 C# 的 DataGridView
的 enum
类列的示例非常少,但求助的请求却很多。现有的示例通常从单个 datasource
填充网格,并且在执行方面既简单又有效。然而,我试图将这种方法扩展到从两个或多个数据源绘制数据的 DataGridView
,导致反复出现显示错误:“Error Happened: Formatting, Display” 和 “Error Happened: Formatting, Preferred Size”。这是因为 enum
类条目的自然状态是‘Int
’,而 DataGridView
单元格的自然状态是‘String
’,两者之间存在冲突,而且不像简单的类型转换那样容易解决。
示例起源
该示例来自我正在开发的一个系统,该系统严重依赖到期日期。项目可以通过在当前日期基础上添加一定时间单位来计算到期日期,例如,从今天起两周后,或者通过“Fixed
”单位,其到期时间单位结束于指定的某一天,无论开始日期如何,例如,下周五。为简单起见,该示例硬编码了 ExpiryMaster
表作为其主 datasource
,并将 Units 的一部分硬编码作为其次级 datasource
。Fixed intervals 已被省略,因为它们不对示例增加任何额外价值。我选择硬编码 datasource
是因为我猜测许多下载 CodeProject 示例的用户可能没有安装所需的数据库($%^*!%$ (抱歉,是“必需”)数据库)。
呈现单个数据源的数据
这部分内容非常简单,简直可以算作技术技巧。基本上只需要添加两行代码。
- 创建一个新的 Windows 窗体应用程序
- 在窗体上放置一个新的
DataGridView
- 向窗体添加两列,一列是文本列‘
dgName
’,另一列是组合框列‘dgStatus
’ - 在
Form1
类定义之前定义你的enum
类
public enum class Status_Type {Married, Single};
将这两行添加到 Form1
构造函数(或加载事件)中
dgStatus->ValueType = Status_Type::typeid;
dgStatus->DataSource = Enum::GetValues(Status_Type::typeid);
编译并运行应用程序,你就得到了一个可在 DataGridView
中工作的 enum
类列。
以下是一些要加载的数据
dataGridView1->Rows->Clear();
array<Object^>^ itemRec = gcnew array<Object^> {"Sean",Status_Type::Married};
dataGridView1->Rows->Add(itemRec);
array<Object^>^ itemRec2 = gcnew array<Object^> {"Tom",Status_Type::Single};
dataGridView1->Rows->Add(itemRec2);
当通过 Rows?Add 方法逐行添加数据时,这都能很好地工作。这实在没什么传奇色彩。代码包含在附加的 DataGridView1
示例中。
当 DataGridView
映射到 DataTable
时,乐趣才真正开始!
使用 DataTables 从多个源获取数据的 DataGridView 上的枚举类列
当使用 DataTable
填充 DataGridView
时,要成功创建基于 enum
类的列,秘诀在于将该 enum
类映射到自己的 DataTable
中,并将其视为任何其他次级 datasource
。
我们的目标
这就是我们要达到的目标

在架构上,主 DataTable
由 ExpiryMaster DataSource
驱动,并且有两个次级 DataTables
,一个用于 Expiry
Type enum
类,另一个用于 Unit DataSource
。从 Unit DataSource
派生的 DataTable
用于驱动两个组合框列,以方便用户通过 ID 或描述选择一个 Unit,同时更新另一个值以与之对应。
成功的枚举的要素
Enum
类可以定义在 Form
类之前
public enum class Expiry_Type {Units, Fixed_Date};
(虽然在我正在工作的系统中,我将此类定义保存在共享程序集中)。
在你的 Form
类中包含这些定义,以启用将 enum
类映射到 DataTable
DataSet ^dsExpTyp;
BindingSource ^bsExpTyp;
DataTable ^dtExpTyp;
我包含了一个临时变量来检查来自 datagrid
的新值
Expiry_Type empExpType;
你可以使用 IDE 或参考我附加的示例(DataGridEnum4
)来定位它们。
作为我从 form
构造函数调用的 LaunchForm
函数的一部分,我有
用于保存 Enum
类的 DataTable
的定义
dtExpTyp = gcnew DataTable("dtExpTyp");
dtExpTyp->Columns->Add("Expiry_Type", Int32::typeid);
dtExpTyp->Columns->Add("Expiry_Type_Desc", String::typeid);
bsExpTyp = gcnew BindingSource();
dsExpTyp = gcnew DataSet();
dsExpTyp->Tables->Add(dtExpTyp);
映射到 DataGridView
的 DataTable
需要此条目来映射到上述 datastructure
dtViewExpiryData->Columns->Add("Expiry_Type", Int32::typeid);
该列的 datasource
定义如下
gridExpiry->Columns[dgExpiry_Type->Index]->DataPropertyName = "Expiry_Type";
注意:在此示例中,DataGridView
称为‘gridExpiry
’。
以及对将执行映射的函数的调用
Load_Expiry_Enum();
你可以在示例中看到它们的位置。
Load_Expiry_Enum()
的源代码是
{
DataRow ^row;
for each (Expiry_Type^ tmpEx in Enum::GetValues(Expiry_Type::typeid))
{
row = dsExpTyp->Tables["dtExpTyp"]->NewRow();
row["Expiry_Type"] = tmpEx;
row["Expiry_Type_Desc"] = tmpEx->ToString();
dsExpTyp->Tables["dtExpTyp"]->Rows->Add(row);
}
// Set up the UnitDesc binding source
bsExpTyp->DataSource = dsExpTyp;
bsExpTyp->DataMember = "dtExpTyp";
// bind Name
dgExpiry_Type->DataSource = bsExpTyp;
dgExpiry_Type->DisplayMember = "Expiry_Type_Desc";
dgExpiry_Type->ValueMember = "Expiry_Type";
}
紧随本节之后的部分将向你展示这如何以与任何其他次级 datasource
相同的方式融入主 datasource
。在此期间,我们将通过查看获取、显示和检查 DataGridView
上 enum
类列数据的代码来结束本节。
现在,我们将研究两种从 enum
列获取数据的方法。
try
{
if (gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->Value != nullptr
&&gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->
Value->ToString() != "")
{
String^ IntExpType = gridExpiry->Rows[e->RowIndex]->
Cells[dgExpiry_Type->Index]->Value->ToString();
empExpType = safe_cast<Expiry_Type>
(System::Convert::ToInt32(IntExpType));
lblExpiryType->Text = empExpType.ToString();
}
}
catch (...)
{
lblExpiryType->Text = nullptr;
}
并且
array<DataRow^>^ row;
try
{
if (gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->Value != nullptr
&&gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->
Value->ToString() != "")
{
String^ IntExpType = gridExpiry->Rows[e->RowIndex]->
Cells[dgExpiry_Type->Index]->Value->ToString();
row =dtExpTyp->Select(String::Format("Expiry_Type={0}", IntExpType));
if (row->Length > 0)
{
// ItemArray[0] holds the integer representation of the enum,
// alternately
lblExpiryType->Text = row[0]->ItemArray[1]->ToString();
}
}
}
catch (Exception ^e)
{
String ^MessageString =
" Error reading internal enum table for Expiry Type: " + e->Message;
MessageBox::Show(MessageString);
lblExpiryType->Text = nullptr;
}
这些方法中的任何一种都允许你随意使用你的 enum
类。
逐步解析附加代码
附加的 DataGridEnum4
示例是一个完整的 Visual Studio 2008 项目,在压缩前已清理。首先说明我的风格。我不喜欢在我们的 .h 文件中有太多内容,所以当 IDE 将 Windows 窗体函数添加到 .h 文件时,我的做法是在 .cpp 文件中调用一个用户定义的函数,并将参数传递过去。这会带来一些开销,因为我无法强制 IDE 将 Windows 窗体函数直接插入 .cpp 文件,并且当我将它们重新定位到那里时,IDE 会感到困惑——但我更喜欢它所提供的顺序。
这个示例是一个标准的 Windows 窗体应用程序,窗体上放置了一个 DataGridView
和几个标签,这些都是从工具箱中拖放的,如图第二个屏幕截图所示。Expiry Type 列是一个 combobox
列,Unit ID 和 Unit Description 也是如此,其他三列是 textbox
列。所有这些都是通过窗体设计器实现的。
在这里,我们将进一步探讨 Form1.h 和 DataGridEnum4.cpp 模块。
Form1.h
这是我正在引用的程序集的完整列表
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::IO;
using namespace System::Collections::Generic;
我在应用程序命名空间中定义了 Expiry_Type enum
类,但在窗体类之前。
除了 Launchform
函数调用之外,构造函数中还有另一行。这是用于错误图标的。
m_CellInError = gcnew Point(-2, -2);
在……之后
#pragma endregion
……我定义了我的内部变量和函数。这里包含另一个 enum
类,这次加上了 [Flags]
前缀。你将看到标志变量 m_ElementList
在代码中被设置和重置的示例。这在需要编写更新语句以将更改提交到数据库时特别有用。标志设置决定了需要更新的列。
为错误图标处理定义了两个变量,然后是 DataTables
、BindingSources
和 DataSets
的定义。
.h 文件的最后一段是 IDE 在窗体设计器中为 DataGridView
和 Exit 按钮添加的函数定义。它们是
UserAddedRow
(此示例未使用)CellValueChanged
CellValidating
(此示例未使用)ColumnHeaderMouseClick
– 用于消除双击下拉列表RowEnter
RowValidating
CellBeginEdit
– 存储颜色 – 在 .CPP 文件中没有任何内容CellEndEdit
– 恢复颜色,然后调用 .CPP 函数Exit_Click
– 关闭示例CellClick
DataError
DataGridEnum4.cpp
LaunchForm
在这个从构造函数调用的函数中,有一些有趣的“花里胡哨”的功能。
这段代码自定义了网格标题
DataGridViewCellStyle^ headerStyle = gcnew DataGridViewCellStyle;
headerStyle->Font = gcnew System::Drawing::Font("Times New Roman", 12,FontStyle::Bold);
gridExpiry->ColumnHeadersDefaultCellStyle = headerStyle;
个性化选择颜色
gridExpiry->DefaultCellStyle->SelectionBackColor=Color::FromArgb(255,255,128);
gridExpiry->DefaultCellStyle->SelectionForeColor=Color::Black;
列标题上的工具提示文本
for each(DataGridViewColumn^ column in gridExpiry->Columns)
column->ToolTipText = L"Click to\nsort rows";
将颜色条纹应用到网格
gridExpiry->AlternatingRowsDefaultCellStyle->BackColor = Color::LightGray;
如前所述,定义了用于 Units datasource
和 Expiry
Type Enum
类的 Datatable
,然后是 expiry datasource
的 datatable
定义。
接下来,将网格列映射到数据属性
gridExpiry->Columns[dgExpiry_ID->Index]->DataPropertyName = "Expiry_ID";
gridExpiry->Columns[dgExpiry_Type->Index]->DataPropertyName = "Expiry_Type";
gridExpiry->Columns[dgDescription->Index]->DataPropertyName = "Description";
gridExpiry->Columns[dgUnitIDNum->Index]->DataPropertyName = "UnitIDNum";
gridExpiry->Columns[dgUnitDesc->Index]->DataPropertyName = "Unit_ID";
gridExpiry->Columns[Number_Interval_Units->Index]->
DataPropertyName = "Number_Interval_Units";
该函数以调用专用函数结束,这些函数将填充每个数据表并完成网格定义。
List_Setup
这里的关键是将主 datatable
绑定到 DataGridView
gridExpiry->DataSource = dtViewExpiryData;
RowEntered
此函数没有什么特别值得注意的,除了初始化标志和读取组合框列中的数据。我已经提供了两个如何读取组合框列的示例,这里是初始化 Enum
标志
m_ElementList = m_ElementList & ~ m_FlagBits::EXPIRY_ID;
m_ElementList = m_ElementList & ~ m_FlagBits::EXPIRY_TYPE;
m_ElementList = m_ElementList & ~ m_FlagBits::DESCRIPTION;
m_ElementList = m_ElementList & ~ m_FlagBits::UNIT_ID;
m_ElementList = m_ElementList & ~ m_FlagBits::NUMBER_OF_INTERVAL_UNITS;
CellEndEdit
使用此函数在任何需要强制填充的单元格中显示图标。在此示例中,我将其应用于行级别以产生此效果

CellValueChanged
同样,你已经看到如何读取 combobox
列。这里的另一个值得注意的特性是,当检测到单元格值更改时设置一个标志。
m_ElementList = m_ElementList | m_FlagBits::EXPIRY_ID;
CellClick
DataGridView
上的 ComboBox
列有一个恼人的习惯,就是需要点击两次才能打开下拉列表。此函数的作用是,一旦点击单元格,就会立即打开下拉列表。
RowValidating
此函数会在用户尝试离开某行时,在应有值的任何单元格上放置错误图标。
DataError
包含此部分是为了处理 DataGridView
上未预见的显示故障。当 DataGridView
不知道如何显示特定值时,就会发生这些错误。例如,在此解决方案中,如果我们使用了上面更简单的 enum
方法,我们会看到大量的“Error Happened: Formatting Display” 等错误,因为 DataGridView
正在查找字符串值,而 enum
类正在提供一个整数。
话虽如此,即使你不使用 enum
类,包含此函数也是个好习惯,因为它能很好地捕获 DataGridView
的意外显示问题。
历史
- 2011-05-16 - V1.0 - 初始提交