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

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.94/5 (8投票s)

2004 年 11 月 17 日

13分钟阅读

viewsIcon

87876

downloadIcon

2136

本文讨论了如何使用 Visual Studio 2005 C++/CLI 和 .NET Framework 2.0 创建用户控件并将其添加到其他项目中。

Sample Image - LN1.jpg

引言

使用 C++ 快速创建可重用控件,使应用程序具有一致的外观并提高开发人员的效率,这难道不好吗?借助 .NET Framework 2.0 和 Visual Studio 2005 C++/CLI,您可以做到这一点。我使用 Visual Studio 2005 Beta 1 用 C++/CLI 创建了一个名为 ListNavigator 的用户控件。创建 ListNavigator 控件很容易,并且只需很少的精力就可以在许多不同的项目中使用它。将 ListNavigator 添加到包含 ListView 控件的 Form 中,可以搜索 ListView 控件的每一行和每一列。在 ListNavigatorComboBox 中输入搜索文本,然后使用 ListNavigatorButton 来导航列表中找到的项目。当每个 ListView 项目被高亮显示时,将向任何 SelectItem 事件订阅者触发一个事件。可以选择显示在 ListView 中找到的项目数。此外,还可以选择将搜索 ComboBox 列表的内容存储到 XML 文件或从中恢复。

请注意:此源代码只能在 .NET Framework 2.0 及更高版本上运行。它采用了一些 C++/CLI 的新功能。

我将首先讨论如何将 ListNavigator 控件添加到项目中,然后解释 ListNavigator 控件的实现细节,该控件演示了 .NET Framework 2.0 和 C++/CLI 的多项功能,包括委托/事件、属性、异常处理、集合、反射、流式布局、XmlWriterXmlReader

在 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。LeftRightTop 的 Padding 属性应设置为零。这会在 Form 的顶部创建一个空白区域,我们将在此区域放置 ListNavigator 控件。

添加 ListView 控件

接下来,从工具箱将 ListView 拖到 Form 上。按如下方式更改 ListView 属性:

  1. View 属性设置为 List
  2. HideSelection 属性设置为 False
  3. FullRowSelect 属性设置为 True
  4. GridLines 属性设置为 True
  5. MultiSelect 属性设置为 False
  6. Dock 属性设置为 Fill
  7. ListView 应具有一些列,因此向 ListViewColumns 集合属性添加列:Developer、Project、Date 和 Hours,宽度设置为约 100。
  8. 在此示例中,ListView 还需要行。将以下项添加到 ListViewItems 集合属性中。请勿输入列标题。

添加 ListNavigator 控件

需要将 ListView 控件引用分配给 ListNavigatorListView 控件引用。从“窗体设计器”上下文菜单中,选择“查看代码”菜单项。在 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);
}

测试一下!

生成并运行项目。在 ListNavigatorComboBox 中输入搜索文本。按 Tab 键或单击“|<<”按钮。这将激活搜索。在控件右侧显示列表中找到的搜索项的数量。此时“>>”按钮获得焦点。按空格键或单击“>>”按钮将高亮显示 ListView 中与搜索条件匹配的下一个项。选择“<<”按钮将高亮显示 ListView 中与搜索条件匹配的前一个项。选择“>>|”按钮将高亮显示 ListView 中与搜索条件匹配的最后一项。通过在搜索 ComboBox 中输入不同的文本,然后选择“|<<”按钮激活新搜索,重复几次搜索。

序列化 ComboBox 下拉列表

在之前的步骤中,我们将 ListNavigatorSerializeSearches 属性设置为 True。这意味着当应用程序终止时,ListNavigatorComboBox 下拉列表中的任何项都会保存到由 ListNavigatorSerializeFilename 属性指定的 XML 文件中。此属性的默认设置为 ListNavigator.ListNavigatorControl.xml。由于我们没有指定完整的文件路径,因此 ListNavigator.ListNavigatorControl.xml 将位于我们的 TestLN 应用程序的工作目录中。关闭 TestLN 应用程序。再次运行 TestLN 应用程序。ListNavigatorComboBox 列表将填充上次运行应用程序时输入的搜索文本。下面是 ListNavigator 控件生成的 XML 文件内容的一个示例。

创建自定义控件 ListNavigator

下面关于如何创建 ListNavigator 控件的讨论将提供实现细节以及委托/事件、属性、异常处理、集合、XmlWriterXmlReader、反射和流式布局等功能的使用。

设计 ListNavigator 控件

运行 Visual Studio 2005。创建一个新的项目,项目类型为 Windows Control Library (.NET)。将其命名为 ListNavigator。生成解决方案。此时将创建 ListNavigator.dll。在解决方案资源管理器中,查看 ListNavigatorControl.h 文件的源代码。请注意,ListNavigator 控件类派生自 Windows::Forms::UserControlUserControl 是其他控件的容器。它可以由一个或多个现有控件组成。在解决方案资源管理器中,在设计器中打开 ListNavigatorControl.h 文件。此时将显示一个 Form。您可以通过将控件添加到 Form 来创建 ListNavigator 控件。首先,调整 Form 的大小,使 Size 属性的 WidthHeight 设置为约 400 和 28。选择 Form 的上下文菜单中的“属性”菜单项。在“属性”视图中,选择“事件”。接下来,通过双击添加 LoadBackColorChangedForeColorChanged 事件的空实现。稍后我们将填充源代码。

要添加到 Form 的第一个控件是 FlowLayoutPanel。当 Form 的大小改变时,FlowLayoutPanel 控件会动态地将它包含的任何控件一个接一个地放置。从工具箱将 FlowLayoutPanel 拖放到 Form 上。只需更改 FlowLayoutPanel 的少数属性。

  1. Name 属性设置为 flowLayoutPanelSearch
  2. Dock 属性设置为 Fill

接下来,我们将添加用于输入搜索文本的 ComboBox。从工具箱将 ComboBox 拖放到 Form 上。修改以下属性:

  1. Name 属性设置为 comboBoxSearch
  2. 调整 ComboBox 的大小,使 WidthHeightSize 属性设置为约 220 和 24。
  3. Anchor 属性设置为 Top

现在我们将添加用于导航 ListView 搜索的四个按钮。从工具箱将 Button 拖放到 ComboBox 的右侧。

  1. Name 属性设置为 buttonFirst
  2. 删除 Text 属性的任何文本。
  3. Font 属性设置为 Webdings,9pt。
  4. TextAlign 属性设置为 MiddleLeft
  5. Anchor 属性设置为 Top
  6. CausesValidation 属性设置为 False
  7. 调整 Button 的大小,使 WidthHeightSize 属性设置为 28 和 23。

复制 buttonFirst Button。将副本粘贴三次到 Form 的最右侧。更改三个新 ButtonName 属性。对于紧邻 buttonFirst 按钮右侧的 Button,将 Name 属性设置为 ButtonPrev。对于紧邻 buttonPrev 按钮右侧的 Button,将 Name 属性设置为 buttonNext。最后,将紧邻 buttonNext 按钮右侧的 ButtonName 属性设置为 buttonLast。对于每个 Button,将 Text 属性设置为相应的 Webdings 字符。您可以使用位于 system32 目录下的 CharMap 应用程序来选择和复制 Webdings 字符。

必须处理每个 ButtonClick 事件。选择 Button 的上下文菜单中的“属性”菜单项。在“属性”视图中,选择“事件”。接下来,通过双击添加 Click 事件的空实现。稍后我们将填充源代码。

我们将添加到 ListNavigator 的最后一个控件是 Label 控件,用于显示找到的搜索项的数量。从工具箱将 Label 控件拖放到 buttonLast 按钮的右侧。

  1. Name 属性设置为 labelCount
  2. 删除 Text 属性的任何文本。这将导致 Label 变得非常小。这没关系,当 Text 属性再次设置时,它会调整大小。
  3. Anchor 属性设置为 Top

现在所有控件都已添加到 Form 中,请验证每个控件的 TabIndex 属性是否设置正确。

  1. comboBoxSearch 控件的 TabIndex 属性应等于 0。
  2. buttonFirst 控件的 TabIndex 属性应等于 1。
  3. buttonPrev 控件的 TabIndex 属性应等于 2。
  4. buttonNext 控件的 TabIndex 属性应等于 3。
  5. buttonLast 控件的 TabIndex 属性应等于 4。
  6. 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 属性

属性 BackColorChangeForeColorChangeListNavigator 的基类 Windows::Forms::UserControl 中定义。它们的实现不适用于 ListNavigator 控件。当 ListNavigator 控件的用户更改 BackColorChange 属性时,ListNavigator 的背景颜色也应随之更改。当 ForeColorChange 属性被修改时,ListNavigatorComboBox 前景颜色应与其相同。早些时候,我们为 FormBackColorChangedForeColorChanged 事件添加了空实现,以便重新定义这些属性。此源代码实现如下所示。

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 事件

接下来,我们将填充 firstButtonClick 事件的实现。之前添加了一个空实现。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^ itemsFoundSearchListItem 的容器,所以 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 事件

其他 ButtonClick 事件的实现包括递增/递减 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 恢复

如果 ListNavigatorSerializeSearches 属性为 True,则每当应用程序退出时,ListNavigatorComboBox 下拉列表将保存到由 ListNavigatorSerializeFilename 属性指定的 XML 文件中。启动时,ListNavigatorComboBox 列表将填充上次运行应用程序时输入的搜索文本项。为此,应处理 ComboBox 事件 HandleCreatedHandleDestroyed。它们被分配给委托发生在 Form 类中组件初始化之后。VS Studio Beta 1 设计器视图不列出这些事件,因此您需要手动输入它们。在查看下面的代码时,请注意 HandleCreated 的赋值是注释掉的。在我使用的版本中,.NET Framework 2.0 beta 中存在一个错误,在处理 HandleCreated 事件时会导致异常。为了解决此错误,必须处理 FormLoad 事件,而不是 ComboBoxHandleCreated 事件。

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 控件,则应将其添加到全局程序集缓存中。

© . All rights reserved.