65.9K
CodeProject 正在变化。 阅读更多。
Home

在 DataGridView ComboBox 列中使用 Enum 类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (6投票s)

2011年5月18日

CPOL

8分钟阅读

viewsIcon

63985

downloadIcon

5175

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

Complex enum class column in action

目标

本文的目的是提供一个在 Windows 窗体中使用 C++/CLI 在 DataGridView 的枚举类列中显示数据的可运行示例,数据通过 DataTables 从多个数据源中提取。文章还将展示一种更简化的方法,该方法从单个源绘制数据而不使用 DataTable,以及一些其他不相关的功能。

  • 行条纹
  • 单元格高亮
  • 单个单元格中的错误图标
  • enum 类用作标志集
  • 从多个数据源构建 DataGridView

背景

在网络搜索中,关于 C++/CLI 或 C# 的 DataGridViewenum 类列的示例非常少,但求助的请求却很多。现有的示例通常从单个 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 示例中。

Simple enum class column in action

DataGridView 映射到 DataTable 时,乐趣才真正开始!

使用 DataTables 从多个源获取数据的 DataGridView 上的枚举类列

当使用 DataTable 填充 DataGridView 时,要成功创建基于 enum 类的列,秘诀在于将该 enum 类映射到自己的 DataTable 中,并将其视为任何其他次级 datasource

我们的目标

这就是我们要达到的目标

Complex enum class column in action

在架构上,主 DataTableExpiryMaster 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);

映射到 DataGridViewDataTable 需要此条目来映射到上述 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。在此期间,我们将通过查看获取、显示和检查 DataGridViewenum 类列数据的代码来结束本节。

现在,我们将研究两种从 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.hDataGridEnum4.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 在代码中被设置和重置的示例。这在需要编写更新语句以将更改提交到数据库时特别有用。标志设置决定了需要更新的列。

为错误图标处理定义了两个变量,然后是 DataTablesBindingSourcesDataSets 的定义。

.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 datasourceExpiry Type Enum 类的 Datatable,然后是 expiry datasourcedatatable 定义。

接下来,将网格列映射到数据属性

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

使用此函数在任何需要强制填充的单元格中显示图标。在此示例中,我将其应用于行级别以产生此效果

Error icons in action

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 - 初始提交
© . All rights reserved.