MC++ 中的 DataGridSample






3.18/5 (2投票s)
2004 年 9 月 7 日
7分钟阅读

57148

900
关于在 Windows 窗体中实现绑定到嵌套 ArrayList 的 DataGrid 控件的文章。
图 1. 带有 DataGrid 控件的用户输入表单 – 记录已展开
引言
本文旨在提供一个在托管 C++ (MC++) 编程的 Windows 窗体中使用 DataGrid
控件的示例。我希望 DataGrid
的 DataSource
是对象的 2 个嵌套 ArrayList
。附带的示例项目是我第一次尝试使用 DataGrid
控件。我在网上找到的大部分文章和示例都是用 C# 或 VB .Net 编写的,所以我觉得有必要分享一些我在熟悉 DataGrid
控件时学到的经验,来帮助其他 C++ 程序员。
用户界面必须允许向父 ArrayList
添加新的 Positions
,并使用其他数据输入控件添加相关的 ScaleFactors
。它还必须允许删除选定的子 ScaleFactor
或从 DataGrid
控件中删除选定的父 Position。最后,UI 必须允许删除给定 Position 中的所有 ScaleFactors
或删除所有 Positions。
DataGrid
控件可能是从 System.Windows.Forms.Control
类派生的最强大的类。WinForm 中的 DataGrid
对象通常绑定到相关数据表的 数据源。任何时候,DataGrid
中只有一个表被显示。如果数据表之间建立了父子关系,并且启用了 DataGrid
的导航,则用户可以通过单击记录的行标题中的展开器来导航相关表,该展开器会生成一个类似 Web 的链接到子表。参见图 1。单击链接时,会显示与单个父记录相关的子表中的记录。参见图 2。当 DataGrid
显示子表中的记录时,DataGrid 的标题中会提供一个后退按钮,允许用户导航回父表。
图 2. 带有 2 个 ScaleFactor 对象输入的 DataGrid 控件
工作原理
图 1 显示的 WinForm 显示了示例项目在用户输入一些数据后的最终表单。所有文本框在设计时都设置了初始值,以提供预期的此类数据的示例。当父 ArrayList
(即 DataGrid
的 DataSource
)中至少有一个父 Position 对象时,“ScaleFactor
”组将被启用。每次添加新记录后,所有初始值都会被清除。通过选择一个 Position 记录,然后在“ScaleFactor
”组框的文本框中输入数据来将 ScaleFactors
添加到给定的 Position。当前记录指示器所在的记录将是接收输入的 ScaleFactors
的当前记录。
要删除选定的 Position 对象,请单击所选记录的 RowHeader
(最左边的灰色列),然后单击“Remove Selected”按钮。删除 Position 对象时,还会删除所有相关的子 ScaleFactor
对象。要删除选定的 ScaleFactor
对象,请先单击所选 Position 记录的展开器(RowHeader
中的 + 按钮),然后单击类似 Web 的链接以显示相关子 ScaleFactors
的列表。然后,选择一个 ScaleFactor
RowHeader
,然后单击“ScaleFactor
”组框中的“Remove Selected”。
要清除与给定 Position 对象相关的所有 ScaleFactors
,请选择一个 Position 行并单击“ScaleFactor
”组框中的“Clear All”按钮。要清除所有 Position 记录,请单击主窗体的“Clear All”按钮,该按钮会清空整个 DataGrid
,甚至会删除与底层 Position ArrayList
的数据绑定。
除了不必要的情况(例如清除所有 Positions 的情况)外,我在项目中的几乎每个方法中都刷新了 DataGrid
。这看起来可能有些过度,但它可以确保在每次操作后 DataGrid
的显示都保持一致的状态。
用户可以通过单击单元格编辑 Position 或 ScaleFactor
的任何数据成员,然后单击另一个记录或按 CTRL + Enter。事实上,Visual Studio .Net 帮助中有一个完整的键盘快捷键列表,恰当地命名为Windows Forms DataGrid
控件的键盘快捷键。
要将 DataGrid
控件绑定到对象数组,对象类必须包含公共属性。父 Position ArrayList
包含一个数据成员,该成员本身就是一个 ScaleFactor
对象组成的 ArrayList
,如以下代码片段所示。这建立了 DataGrid 的 DataSource
的数据成员之间的父子关系。
// Parent class containing the ScaleFactor class
public: __gc class Position {
private:
float x;
float y;
float z;
myArrayList * scaleFactors;
public:
Position( void ) {
// Allocate memory on the heap for each child ArrayList
// when a parent is created.
scaleFactors = new myArrayList;
};
__property float get_X( void ) { return x; };
__property void set_X( float input ) { x = input; };
__property float get_Y( void ) { return y; };
__property void set_Y( float input ) { y = input; };
__property float get_Z( void ) { return z; };
__property void set_Z( float input ) { z = input; };
__property myArrayList * get_ScaleFactors( void ) { return scaleFactors; };
__property void set_ScaleFactors( myArrayList * input )
{ scaleFactors = input; };
};
绑定到 ArrayLists
父对象和子对象通过 DataGrid
使用 CurrencyManager
对象删除,该对象会更新 DataGrid 中显示的记录。同时,对象会从父对象或给定父对象的子 ArrayList 中删除。
DataGrid
用于显示嵌套 ArrayLists 的当前状态,允许用户修改任何记录中的字段值,并允许删除选定的父或子 ArrayList
项。Position 和 ScaleFactor
类的公共属性映射到 DataGrid
中的数据列。
System.Collections.ArrayList
类使用一个数组来实现 IList
接口,该数组的大小会根据需要动态增加。Visual Studio .Net 帮助中的几个类讨论了使用 ArrayList
作为 DataGrid
的绑定数据源,在大多数情况下,帮助会警告说“……ArrayList
在绑定时必须包含项。空的 ArrayList
将导致网格为空。”Microsoft 建议并像下一个代码片段所示,在运行时使用 SetDataBinding
方法设置 DataGrid
的 DataSource
和 DataMember
。绑定发生在添加到父 ArrayList
(称为 positionList
)的每个 Position 时。这种绑定方式是必要的,因为 ArrayList
没有关联的 DataAdapter
,并且 DataGrid
的 DataSource
必须在添加到数据源的每个新 ArrayList
项时进行更新。
Position * newParentObj = new Position;
newParentObj->X = Single::Parse( tboX->Text );
newParentObj->Y = Single::Parse( tboY->Text );
newParentObj->Z = Single::Parse( tboZ->Text );
int addIndex = positionList->Add( newParentObj );
// Bind the DataGrid to positionList
dataGrid2->SetDataBinding( positionList, S"" );
通过 DataGridTableStyle
,您可以控制 DataGrid
中显示的数据的外观。所有 DataGridTableStyles
和 DataGridColumnStyles
都必须以编程方式创建,因为在设计时不存在 DataSource
。此外,在创建 DataGridColumnStyles
和 DataGridTableStyles
时,Visual Studio .Net 帮助会警告:
"始终先创建 DataGridColumnStyle
对象并将它们添加到 GridColumnStylesCollection
,然后再将 DataGridTableStyle
对象添加到 GridTableStylesCollection
。当您将一个空的 DataGridTableStyle
添加到集合中时,系统会自动为您生成 DataGridColumnStyle
对象。因此,如果您尝试将 [您的] 新 DataGridColumnStyle
对象添加具有重复 MappingName
值的对象到 GridColumnStylesCollection
中,将会引发异常。"
Visual Studio .Net 帮助还规定,当将 DataGrid
控件绑定到 ArrayList
时,您应该将 DataGridTableStyle
的 MappingName
设置为“ArrayList
”(类型名称)。每个 DataGridTableStyle
的 MappingName
也必须是唯一的。通过创建一个与 ArrayList
相同的子类并赋予不同的名称,可以像下一个代码片段所示那样将 PositionObjectsTable
和 ScaleFactorsTable
都映射到 ArrayLists
。
// Giving ArrayList a new name to keep unique MappingNames
public: __gc class myArrayList : public ArrayList { };
.
.
.
PositionsStyle->MappingName = S"ArrayList";
.
.
.
ScaleFactorsStyle->MappingName = S"myArrayList";
DataGrid
在添加、删除或清除 DataSource
中的记录的几乎每个方法中都会被刷新。父 ArrayList
的 CurrencyManager
也会调用 Refresh 方法。
删除记录
确定用户在 DataGrid
中选择了哪一行,然后确定所选行是来自父表还是子表的记录,是本项目中最难逻辑化的部分。Visual Studio .Net 帮助中的 DataGrid
类概述提供了解决第一个难题的方法:
"要确定用户单击了控件的哪个部分,请在 DataGrid
控件的 MouseDown
事件中使用 HitTest
方法。HitTest
方法返回一个 DataGrid.HitTestInfo
对象,该对象包含单击区域的行和列。"
要确定选择的是父记录还是子记录,我们必须知道 DataGrid
控件正在显示 DataSource
中的哪个列表。DataMember
属性为我们获取了这个列表。由于 DataSource
是 ArrayList
,最顶层的 DataMember
是一个空字符串,这带来了一个小小的复杂性。下一个代码片段显示了我如何确定正在显示的表,然后将父或子 CurrencyManager
的 Position 属性设置为选定的记录,这使得该记录成为当前或活动记录。
if( parentCurrencyMgr != 0 ) {
if( dataGrid2->DataMember->ToString()->Equals(String::Empty) ) {
parentCurrencyMgr->Position = hti->Row;
}
if( dataGrid2->DataMember->ToString()->Equals(S"ScaleFactors") ) {
Position * currentParentObj =
dynamic_cast(parentCurrencyMgr->Current);
// get rid of any pre-existing CM pointing to a child ArrayList
if( childCurrencyMgr != 0 ) childCurrencyMgr = 0;
childCurrencyMgr = dynamic_cast<CurrencyManager *>
(dataGrid2->BindingContext->get_Item( currentParentObj->
ScaleFactors, S"" ));
if( 0 <= hti->Row && hti->Row < childCurrencyMgr->Count )
childCurrencyMgr->Position = hti->Row;
摘要
这个 MC++ 原型项目是我第一次尝试使用 DataGrid
控件,它可能是 Control 类中最强大的控件。在经历了处理这个控件的挫折和收获之后,我希望继续使用类型化 DataSets 作为 DataGrid
的 DataSource
。如有问题或善意的评论,请发送电子邮件给我。