使用 Visual Studio 2005 C++/CLI 创建和使用 .NET Framework 2.0 用户控件






3.94/5 (8投票s)
2004 年 11 月 17 日
13分钟阅读

87876

2136
本文讨论了如何使用 Visual Studio 2005 C++/CLI 和 .NET Framework 2.0 创建用户控件并将其添加到其他项目中。
引言
使用 C++ 快速创建可重用控件,使应用程序具有一致的外观并提高开发人员的效率,这难道不好吗?借助 .NET Framework 2.0 和 Visual Studio 2005 C++/CLI,您可以做到这一点。我使用 Visual Studio 2005 Beta 1 用 C++/CLI 创建了一个名为 ListNavigator
的用户控件。创建 ListNavigator
控件很容易,并且只需很少的精力就可以在许多不同的项目中使用它。将 ListNavigator
添加到包含 ListView
控件的 Form
中,可以搜索 ListView
控件的每一行和每一列。在 ListNavigator
的 ComboBox
中输入搜索文本,然后使用 ListNavigator
的 Button
来导航列表中找到的项目。当每个 ListView
项目被高亮显示时,将向任何 SelectItem
事件订阅者触发一个事件。可以选择显示在 ListView
中找到的项目数。此外,还可以选择将搜索 ComboBox
列表的内容存储到 XML 文件或从中恢复。
请注意:此源代码只能在 .NET Framework 2.0 及更高版本上运行。它采用了一些 C++/CLI 的新功能。
我将首先讨论如何将 ListNavigator
控件添加到项目中,然后解释 ListNavigator
控件的实现细节,该控件演示了 .NET Framework 2.0 和 C++/CLI 的多项功能,包括委托/事件、属性、异常处理、集合、反射、流式布局、XmlWriter
和 XmlReader
。
在 Form 中使用 ListNavigator 用户控件
在本文中,我们将构建一个示例项目来演示如何使用 ListNavigator
控件。首先,运行 Visual Studio 2005。创建一个新的项目,项目类型为 Windows Forms Application (.NET)。将其命名为 TestLN。生成解决方案。ListNavigator.dll
程序集包含 ListNavigator
控件。将本文下载中包含的 ListNavigator.dll 复制到 TestLN.exe 所在的目录中。在 Visual Studio 2005 中,查看解决方案资源管理器。通过右键单击项目显示项目的上下文菜单。确保选择的是 TestLN 项目,而不是 TestLN 解决方案。显示工具箱视图。通过右键单击“通用”选项卡显示“通用”选项卡的上下文菜单。从菜单中,选择“选择项…”菜单。此时将显示“选择工具箱项”。选择“浏览”按钮。找到并选择 ListNavigator.dll。关闭对话框。现在 ListNavigator
控件可以在工具箱中使用,就像 .NET Framework 类库控件一样。可以将 ListNavigator
拖放到 Form
上,也可以手动修改源代码将 ListNavigator
添加到 Form
中。
对 Form 的更改
在 TestLN 项目中,只需更改一个 Form
属性。在 Form
的 Padding 属性下,将 Top
属性设置为 40。Left
、Right
和 Top
的 Padding 属性应设置为零。这会在 Form
的顶部创建一个空白区域,我们将在此区域放置 ListNavigator
控件。
添加 ListView 控件
接下来,从工具箱将 ListView
拖到 Form
上。按如下方式更改 ListView
属性:
- 将
View
属性设置为List
。 - 将
HideSelection
属性设置为False
。 - 将
FullRowSelect
属性设置为True
。 - 将
GridLines
属性设置为True
。 - 将
MultiSelect
属性设置为False
。 - 将
Dock
属性设置为Fill
。 ListView
应具有一些列,因此向ListView
的Columns
集合属性添加列:Developer、Project、Date 和 Hours,宽度设置为约 100。- 在此示例中,
ListView
还需要行。将以下项添加到ListView
的Items
集合属性中。请勿输入列标题。
添加 ListNavigator 控件
需要将 ListView
控件引用分配给 ListNavigator
的 ListView
控件引用。从“窗体设计器”上下文菜单中,选择“查看代码”菜单项。在 Form
的构造函数中,在 InitializeComponent()
行之后,调用 SetListView(…)
方法。您的代码应如下所示。
Form1(void)
{
InitializeComponent();
listNavigatorControl->SetListView(listView1);
. . .
}
顺便说一下,我曾尝试将 ListView
集合设为 ListNavigator
控件的属性,但每次重新加载项目时 IDE 都会报告错误。我已将此错误报告给 Microsoft,并改用了 SetLiewView
方法。由于我使用的是 Visual Studio 2005 Beta,因此异常并非不可预见。
处理 ListNavigator 事件
ListNavigator
触发 SelectItem
事件。每次在 ListView
控件上通过 ListNavigator
导航时选择一行,都会触发 SelectItem
事件,表明已选择一个列表项。要处理此事件,请在 Form
的构造函数中创建事件处理程序并将其注册到 ListNavigator
的委托。您的代码应如下所示。
public: Form1(void) { InitializeComponent(); . . . listNavigatorControl->SelectItem += gcnew listNavigator_SelectItem(this, & Form1::navigatorControl_SelectItem) . . . protected: System::Void navigatorControl_SelectItem(ListViewItem^ item) { String^ msg = String::Format( "Handling event for item {0}", item->Text); MessageBox::Show(msg); }
测试一下!
生成并运行项目。在 ListNavigator
的 ComboBox
中输入搜索文本。按 Tab 键或单击“|<<”按钮。这将激活搜索。在控件右侧显示列表中找到的搜索项的数量。此时“>>”按钮获得焦点。按空格键或单击“>>”按钮将高亮显示 ListView
中与搜索条件匹配的下一个项。选择“<<”按钮将高亮显示 ListView
中与搜索条件匹配的前一个项。选择“>>|”按钮将高亮显示 ListView
中与搜索条件匹配的最后一项。通过在搜索 ComboBox
中输入不同的文本,然后选择“|<<”按钮激活新搜索,重复几次搜索。
序列化 ComboBox 下拉列表
在之前的步骤中,我们将 ListNavigator
的 SerializeSearches
属性设置为 True
。这意味着当应用程序终止时,ListNavigator
的 ComboBox
下拉列表中的任何项都会保存到由 ListNavigator
的 SerializeFilename
属性指定的 XML 文件中。此属性的默认设置为 ListNavigator.ListNavigatorControl.xml。由于我们没有指定完整的文件路径,因此 ListNavigator.ListNavigatorControl.xml 将位于我们的 TestLN 应用程序的工作目录中。关闭 TestLN 应用程序。再次运行 TestLN 应用程序。ListNavigator
的 ComboBox
列表将填充上次运行应用程序时输入的搜索文本。下面是 ListNavigator
控件生成的 XML 文件内容的一个示例。
创建自定义控件 ListNavigator
下面关于如何创建 ListNavigator
控件的讨论将提供实现细节以及委托/事件、属性、异常处理、集合、XmlWriter
、XmlReader
、反射和流式布局等功能的使用。
设计 ListNavigator 控件
运行 Visual Studio 2005。创建一个新的项目,项目类型为 Windows Control Library (.NET)。将其命名为 ListNavigator。生成解决方案。此时将创建 ListNavigator.dll。在解决方案资源管理器中,查看 ListNavigatorControl.h 文件的源代码。请注意,ListNavigator
控件类派生自 Windows::Forms::UserControl
。UserControl
是其他控件的容器。它可以由一个或多个现有控件组成。在解决方案资源管理器中,在设计器中打开 ListNavigatorControl.h 文件。此时将显示一个 Form
。您可以通过将控件添加到 Form
来创建 ListNavigator
控件。首先,调整 Form
的大小,使 Size
属性的 Width
和 Height
设置为约 400 和 28。选择 Form
的上下文菜单中的“属性”菜单项。在“属性”视图中,选择“事件”。接下来,通过双击添加 Load
、BackColorChanged
和 ForeColorChanged
事件的空实现。稍后我们将填充源代码。
要添加到 Form
的第一个控件是 FlowLayoutPanel
。当 Form
的大小改变时,FlowLayoutPanel
控件会动态地将它包含的任何控件一个接一个地放置。从工具箱将 FlowLayoutPanel
拖放到 Form
上。只需更改 FlowLayoutPanel
的少数属性。
- 将
Name
属性设置为flowLayoutPanelSearch
。 - 将
Dock
属性设置为Fill
。
接下来,我们将添加用于输入搜索文本的 ComboBox
。从工具箱将 ComboBox
拖放到 Form
上。修改以下属性:
- 将
Name
属性设置为comboBoxSearch
。 - 调整
ComboBox
的大小,使Width
和Height
的Size
属性设置为约 220 和 24。 - 将
Anchor
属性设置为Top
。
现在我们将添加用于导航 ListView
搜索的四个按钮。从工具箱将 Button
拖放到 ComboBox
的右侧。
- 将
Name
属性设置为buttonFirst
。 - 删除
Text
属性的任何文本。 - 将
Font
属性设置为 Webdings,9pt。 - 将
TextAlign
属性设置为MiddleLeft
。 - 将
Anchor
属性设置为Top
。 - 将
CausesValidation
属性设置为False
。 - 调整
Button
的大小,使Width
和Height
的Size
属性设置为 28 和 23。
复制 buttonFirst
Button
。将副本粘贴三次到 Form
的最右侧。更改三个新 Button
的 Name
属性。对于紧邻 buttonFirst
按钮右侧的 Button
,将 Name
属性设置为 ButtonPrev
。对于紧邻 buttonPrev
按钮右侧的 Button
,将 Name
属性设置为 buttonNext
。最后,将紧邻 buttonNext
按钮右侧的 Button
的 Name
属性设置为 buttonLast
。对于每个 Button
,将 Text
属性设置为相应的 Webdings 字符。您可以使用位于 system32 目录下的 CharMap 应用程序来选择和复制 Webdings 字符。
必须处理每个 Button
的 Click
事件。选择 Button
的上下文菜单中的“属性”菜单项。在“属性”视图中,选择“事件”。接下来,通过双击添加 Click
事件的空实现。稍后我们将填充源代码。
我们将添加到 ListNavigator
的最后一个控件是 Label
控件,用于显示找到的搜索项的数量。从工具箱将 Label
控件拖放到 buttonLast
按钮的右侧。
- 将
Name
属性设置为labelCount
。 - 删除
Text
属性的任何文本。这将导致Label
变得非常小。这没关系,当Text
属性再次设置时,它会调整大小。 - 将
Anchor
属性设置为Top
。
现在所有控件都已添加到 Form
中,请验证每个控件的 TabIndex
属性是否设置正确。
comboBoxSearch
控件的TabIndex
属性应等于 0。buttonFirst
控件的TabIndex
属性应等于 1。buttonPrev
控件的TabIndex
属性应等于 2。buttonNext
控件的TabIndex
属性应等于 3。buttonLast
控件的TabIndex
属性应等于 4。labelCount
控件的TabIndex
属性应等于 5。
实现 ListNavigator 的属性
关闭窗体设计器视图,因为我们现在将处理 ListNavigatorControl.h 文件中的源代码。当我们向 Form
添加 ListNavigator
控件时,我们设置了几个 ListNavigator
属性。大多数属性都继承自 Windows::Forms::UserControl
,但并非所有属性都如此。接下来,我们将添加 ListNavigator
中未从 Windows::Forms::UserControl
继承的属性。ShowCount
属性决定是否显示找到的搜索项数。CaseSensitive
属性在执行 ListView
搜索时使用。MaxListSearchItems
属性限制 searchComboBox
控件列表中项目的数量。ComboBox
控件允许列表中最多包含 100 个项目。因此,我们还必须强制执行此限制,如果用户尝试将 MaxListSearchItems
属性设置为无效数字,则抛出错误。SerializeSearches
属性影响将搜索 ComboBox
列表的内容存储到 XML 文件或从中恢复。最后,SerializeFilename
属性是存储/恢复搜索的 XML 文件的名称。下面列出了属性的访问器方法。
public: property Boolean ShowCount { Boolean get(){ return showCount; } void set(Boolean value) { showCount = value; } } private: Boolean showCount; public: property Boolean CaseSensitive { Boolean get(){ return caseSensitive; } void set(Boolean value) { caseSensitive = value; } } private: Boolean caseSensitive; public: property Int32 MaxListSearchItems { Int32 get(){ return maxListSearchItems; } void set(Int32 value) { if (value < 0 || value > 100) throw gcnew ArgumentOutOfRangeException("MaxSearchListItems" + " must be between 0 and 100, inclusive"); maxListSearchItems = value; } } private: Int32 maxListSearchItems; public: property Boolean SerializeSearches { Boolean get(){ return serializeSearches; } void set(Boolean value) { serializeSearches = value; } } private: Boolean serializeSearches; public: property String^ SerializeFilename { String^ get() { return serializeFilename; } void set(String^ value) { value->Trim(); serializeFilename = value; } } private: String^ serializeFilename;
在构造函数中初始化这些属性。注意 defaultSerializeFilename
的赋值。它正在使用 CLR 提供的服务来访问程序集的元数据信息。您的代码应如下所示。
using namespace System::Reflection; . . . public: ListNavigatorControl() { showCount = true; maxListSearchItems = 100; defaultSerializeFilename = String::Format( "{0}.xml", this->GetType()->ToString() ); serializeFilename = getDefaultSerializeFilename(); . . . }
重新定义 BackColorChange 和 ForeColorChange 属性
属性 BackColorChange
和 ForeColorChange
在 ListNavigator
的基类 Windows::Forms::UserControl
中定义。它们的实现不适用于 ListNavigator
控件。当 ListNavigator
控件的用户更改 BackColorChange
属性时,ListNavigator
的背景颜色也应随之更改。当 ForeColorChange
属性被修改时,ListNavigator
的 ComboBox
前景颜色应与其相同。早些时候,我们为 Form
的 BackColorChanged
和 ForeColorChanged
事件添加了空实现,以便重新定义这些属性。此源代码实现如下所示。
private: System::Void ListNavigatorControl_BackColorChanged(System::Object^, System::EventArgs^) { this->flowLayoutPanelSearch->BackColor = BackColor; this->comboBoxSearch->BackColor = BackColor; } private: System::Void ListNavigatorControl_ForeColorChanged(System::Object^, System::EventArgs^) { this->comboBoxSearch->ForeColor = ForeColor; }
触发 ListNavigator 控件事件
事件允许一个类在其他类中执行方法。一个类触发事件,另一个类处理被触发的事件。ListNavigator
触发 SelectItem
事件。在定义 ListNavigatorControl
类之前,必须定义一个委托类。事件必须在 ListNavigatorControl
类中指定。您的代码应类似于以下内容。
public delegate Void listNavigator_SelectItem(ListViewItem^ item); . . . public ref class ListNavigatorControl : public Windows::Forms::UserControl { public: event listNavigator_SelectItem^ SelectItem; . . . }
处理 buttonFirst_Click 事件
接下来,我们将填充 firstButton
的 Click
事件的实现。之前添加了一个空实现。buttonFirst
按钮激活搜索。它需要从 ListView
控件生成搜索项目列表,显示更新的搜索计数(如果适用),然后将焦点设置到 buttonNext
。请参阅下面的源代码。
private: Void buttonFirst_Click(Object^, EventArgs^) { GenerateSearchItemsList(); UpdateSearchCount(); if ( SelectListItem(0) ) this->buttonNext->Focus(); }
我将不讨论用于搜索 ListView
的行和列的逻辑,因为这偏离了创建用户控件的讨论,您可以下载源代码并自行查看该逻辑。值得关注的是 ArrayList^ itemsFound
,这是一个用于存储找到的搜索项的容器。它是在 ListNavigator
构造函数中创建的。itemsFound
必须按 ListView
项目索引排序,以确保项目按列表中的显示顺序进行导航。下面显示了生成已排序列表的代码。
using namespace Collections; . . . private: Void GenerateSearchItemsList() { String^ searchText(this->comboBoxSearch->Text); searchText->Trim(); if ( ! searchText->Length) return; AddSearchText(searchText); if ( ! caseSensitive) searchText->ToLower(); itemsFound->Clear(); for (int rowIndex =0; rowIndex < this->searchList->Items->Count; rowIndex++) { SearchListItem^ currentItem = gcnew SearchListItem(searchList->Items->Item[rowIndex]); for (Int32 columnIndex = 0; columnIndex < this->searchList->Columns->Count; ++columnIndex) { if ( (caseSensitive && currentItem->getListViewItem()->SubItems-> Item[columnIndex]->Text->Contains(searchText)) || ( ! caseSensitive && currentItem->getListViewItem()->SubItems-> Item[columnIndex]->Text->ToLower()->Contains(searchText))) { itemsFound->Add(currentItem); columnIndex = this->searchList->Columns->Count; } } } itemsFound->Sort(); }
SearchListItem
类定义了找到的项目。因为 ArrayList^ itemsFound
是 SearchListItem
的容器,所以 SearchListItem
类需要实现 IComparable
接口进行排序。IComparable
接口要求实现 CompareTo(…)
方法来测试一个项目是否小于另一个项目。SearchListItem
类定义在 SearchListItem.h 文件中,并显示如下。
using namespace System; using namespace Windows::Forms; public ref class SearchListItem : public IComparable { public: SearchListItem(ListViewItem^ item) { this->searchItem = item; } public: ListViewItem^ getListViewItem() { return searchItem;} public: int CompareTo(Object^ obj) { ListViewItem^ objItem = dynamic_cast < ListViewItem^ > (obj); if (objItem == nullptr ) return -1; return objItem->Index.CompareTo(searchItem->Index ); } private: ListViewItem^ searchItem; };
处理 Button Prev_Click、buttonNext_Click 和 buttonLast_Click 事件
其他 Button
的 Click
事件的实现包括递增/递减 ListView
找到的项目索引,选择 ListView
中的项目,然后将焦点设置到适当的 Button
。数组可能会抛出异常,因此使用了异常处理。下面显示了选择项目的实现。
private: Boolean SelectListItem(Int32 itemIndex) { Boolean rtn = true; try { SearchListItem^ searchItem = dynamic_cast < SearchListItem^ > (itemsFound->Item[itemIndex]); searchItem->getListViewItem()->Selected = true; currentIndex = itemIndex; SelectItem(searchItem->getListViewItem()); } catch(Exception^) { rtn = false; } return rtn; }
存储到 XML 并从 XML 恢复
如果 ListNavigator
的 SerializeSearches
属性为 True
,则每当应用程序退出时,ListNavigator
的 ComboBox
下拉列表将保存到由 ListNavigator
的 SerializeFilename
属性指定的 XML 文件中。启动时,ListNavigator
的 ComboBox
列表将填充上次运行应用程序时输入的搜索文本项。为此,应处理 ComboBox
事件 HandleCreated
和 HandleDestroyed
。它们被分配给委托发生在 Form
类中组件初始化之后。VS Studio Beta 1 设计器视图不列出这些事件,因此您需要手动输入它们。在查看下面的代码时,请注意 HandleCreated
的赋值是注释掉的。在我使用的版本中,.NET Framework 2.0 beta 中存在一个错误,在处理 HandleCreated
事件时会导致异常。为了解决此错误,必须处理 Form
的 Load
事件,而不是 ComboBox
的 HandleCreated
事件。
public: ListNavigatorControl() { InitializeComponent(); . . . //this->comboBoxSearch->HandleCreated +=gcnew System::EventHandler(this, &ListNavigatorControl::comboBoxSearch_HandleCreated); this->comboBoxSearch->HandleDestroyed += gcnew System::EventHandler(this, &ListNavigatorControl::comboBoxSearch_HandleDestroyed); . . . }
从 ComboBox
下拉列表创建 XML 文件非常简单。创建 XmlWriter
对象。使用 CLR 的服务反射,根元素名称是创建 XML 的类的名称。接下来,遍历 ComboBox
下拉列表,并将其列表项添加为 XML 的元素。下面显示了此实现。上面已经列出了生成的 XML 的示例。
using namespace System::Xml; . . . private: System::Void comboBoxSearch_HandleDestroyed(System::Object^, System::EventArgs^) { if ( ! serializeSearches) return; try { if ( ! SerializeFilename->Length) SerializeFilename = getDefaultSerializeFilename(); XmlWriter^ writer = XmlWriter::Create(SerializeFilename); writer->WriteStartElement(this->GetType()->ToString() ); for ( Int32 index = 0; index < this->comboBoxSearch->Items->Count; ++index) { writer->WriteElementString(serializeSearchElementText, this->comboBoxSearch->Items->Item[index]->ToString() ); } writer->WriteEndElement(); writer->Flush(); writer->Close(); } catch(Exception^ e) { MessageBox::Show(e->Message); } }
读取 XML 文件就像创建它一样简单。首先,确定是否存在序列化文件。如果不存在,则返回调用者,因为没有什么可做的。创建 XmlReader
对象。读取每个元素,验证类型,然后将文本添加到 ComboBox
。请参阅下面的源代码。
using namespace System::Xml; using namespace System::Reflection; using namespace System::IO; . . . private: System::Void ListNavigatorControl_Load(System::Object^, System::EventArgs^) { if ( ! serializeSearches) return; try { if ( ! SerializeFilename->Length) SerializeFilename = getDefaultSerializeFilename(); if ( File::Exists( SerializeFilename ) ) { XmlReader^ reader = XmlReader::Create(SerializeFilename); while (reader->Read()) { if (reader->NodeType == XmlNodeType::Element && reader->Name->Equals(serializeSearchElementText) ) AddSearchText(reader->ReadString()); } reader->Close(); } } catch(Exception^ e) { MessageBox::Show(e->Message); } }
结论
如您所见,借助 .NET Framework 2.0 和 Visual Studio 2005 C++/CLI,创建和使用用户控件非常简单。用户控件可确保您的应用程序外观一致,并且编写代码并不困难。如果您在许多不同的项目中都使用 Navigator 控件,则应将其添加到全局程序集缓存中。