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

在运行时更改 DataGridView 组合框列的下拉列表子集

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (5投票s)

2011年8月25日

CPOL

11分钟阅读

viewsIcon

62096

downloadIcon

3232

这是一个在 Windows 窗体上的 DataGridView 的工作示例,其中组合框列中的下拉列表在执行过程中会发生变化,使用 C++/CLI 编写。

First set of values on stage list

Second set of values on stage list

目标

本文的目的是提供一个 C++/CLI 的工作示例,展示 DataGridView 中的组合框列,其下拉列表根据另一组合框列中选择的值使用不同的子集。

背景

我发现很难找到这样的例子,但最终找到了两个很好的 C# 来源,一个来自 MSDN,另一个是 DataGridViewFAQ 文档。它们涉及到内部表、数据集和绑定源的令人困惑的混合。这是我第一次遇到这些功能。因为我的数据集很小,我预先加载了第二个组合框的所有可能值,并在代码中进行内部过滤。这减少了数据库访问,但在更大的数据集上可能会导致内存使用效率低下。我曾尝试在第一个组合框每次更改时重新加载第二个组合框列,但未能成功。在 C# 中看起来是可能的,因为如果我的理解正确的话,这就是 DataGridViewFaq 的做法。

示例来源

该示例来自我正在开发的票务系统,其中您选择的阶段取决于您选择的路线。

这样做的目的是让乘客可以配置默认的路线/阶段组合,以加快他们在购买新票时的交易过程。

系统中包含所有路线、所有阶段以及路线上的阶段的表。在此系统中,存根将模拟 RouteMaster(用于所有路线)和 RouteStage(用于每条路线上的阶段)。我们将根据为每个乘客选择的路线来过滤 RouteStage

用户界面

用户界面是使用 Visual Studio 中新的 Windows 窗体应用程序项目设计的。窗体上的控件是一个小的 DataGridView,称为 gridPassenger,从工具箱拖放到窗体上,一对 Label,以及“退出”按钮。DataGridView 有三列,一个文本字段 dgNumber,以及随后的两列都是 DataGridViewComboBoxColumn 类型,dgRoutedgStageLabellblRoutelblStage。我将使用它们来显示更改组合框字段的影响。最后一块是为 DataGridView 添加一个 EditingControlShowing 事件。

所需声明

这是需要添加到 form1.h 中的特定声明列表,以完成此任务。

DataTable ^dtRoute;
DataTable ^dtStages;
DataSet ^dsRouteStages;
BindingSource ^bsRoute;
BindingSource ^bsRouteStages;
BindingSource ^filteredStages;
DataTable ^dtViewPassengerData;
  • ^dtRoute 是一个包含路线 ID 及其描述的内部表。在此示例中,I/O 模块 DB_RouteManager 中的获取函数将硬编码一组示例值。
  • ^dtStages 是一个包含阶段 ID、其描述以及所使用的路线 ID 的内部表。一个阶段可能在此表中有多条记录。I/O 模块 DB_RouteStageManager 中的获取函数将硬编码一组示例值。
  • ^dsRouteStages 将包含 Routes 和 Stages 数据表作为其组成部分。
  • ^bsRoute 有助于将 dsRouteStages 数据集与 dtRoute 数据表关联起来。它在我们过滤过程中起着根源作用。
  • ^bsRouteStages 有助于将 dsRouteStages 数据集与 dtStages 数据表关联起来。它应被视为 bsRoute 的子项。
  • ^filteredStages 也与 dtStages 相关联。它将保存每个路线选择的过滤后的阶段集。
  • ^dtViewPassengerData 绑定到 DataGridView (gridPassenger) 本身。在您的工作示例中,这将是您主要数据源的接口。就我而言,这是我的 PassengerMaster 表。

实现功能

本节将引导您了解上述声明如何应用于我已概述的用户界面,以在 DataGridView 上实现过滤的组合框列。

初始化

LaunchForm 函数在构建窗体后、加载任何数据之前执行关键的初始化操作。

首先,我们有用于分配上述每个数据结构的内存的代码。

dtRoute = gcnew DataTable("dtRoute");
dtStages= gcnew DataTable("dtStages");
dsRouteStages = gcnew DataSet();
bsRoute = gcnew BindingSource();
bsRouteStages = gcnew BindingSource();
filteredStages = gcnew BindingSource();

接下来,我们将列添加到我们两个数据表的每一项中。这些应该反映您从数据源读取的列。名称不必匹配,但如果匹配,则维护会更容易。

DataColumn ^col = gcnew DataColumn("RouteID", int::typeid);
dtRoute->Columns->Add(col);
col = gcnew DataColumn("RouteDescription");
dtRoute->Columns->Add(col);

col = gcnew DataColumn("StageID", int::typeid);
dtStages->Columns->Add(col);
col = gcnew DataColumn("StageDescription");
dtStages->Columns->Add(col);
col = gcnew DataColumn("RouteID", int::typeid);
dtStages->Columns->Add(col);

现在我们的表已完全创建,我们将它们添加到 RouteStages 数据集中。

dsRouteStages->Tables->Add(dtRoute);
dsRouteStages->Tables->Add(dtStages);

最后的设置步骤是定义并添加列到绑定到 DataGridViewDataTable。我将其单独保留,因为它不是过滤过程的一部分,但由于我无法在不将网格绑定到 DataTable 的情况下实现过滤,因此它在此处。

dtViewPassengerData = gcnew DataTable("ViewPassengerData");
dtViewPassengerData->Columns->Add("Number", Int32::typeid); 
dtViewPassengerData->Columns->Add("RouteID", Int32::typeid);
dtViewPassengerData->Columns->Add("StageID", Int32::typeid);

现在我们已准备好处理数据,我们调用一些函数来加载路线、阶段和乘客,然后完成列表设置。但所有这些只会得到您主数据表(在本例中为 PassengerMaster)的表示。真正的魔法发生在 Process_EditingControlShowing 函数中,该函数包含 EditingControlShowing 事件的代码。

Load_Routes

这是我的双组合框配置中的主要表。存储的数据被读入一个列表,该列表用于填充 dtRoute 数据表,如下所示。

DataRow ^row; 
for each(CRouteMaster^ candidate in RouteList)
{
    row = dsRouteStages->Tables["dtRoute"]->NewRow();
    row[0] = candidate->p_Route_ID;
    row[1] = candidate->p_Route_Name;
    dsRouteStages->Tables["dtRoute"]->Rows->Add(row);
}

请注意,我们在 dsRouteStages 数据集内使用了 dtRoute 数据表。

然后我们将其绑定到 bsRoute 绑定源。

bsRoute->DataSource = dsRouteStages; 
bsRoute->DataMember = "dtRoute";

// bind the dgRoute to the bsRoute
dgRoute->DataSource = bsRoute;
dgRoute->DisplayMember = "RouteDescription";
dgRoute->ValueMember = "RouteID";

请特别注意上面的最后三行。如果出错,您将在组合框中看不到任何内容,无论是否过滤。DataSourceDataTable 和我们期望看到值列表的列之间的链接,通过使用 BindingSource 变量 bsRouteDisplayMember 是您将在组合框下拉列表中看到的内容,而 ValueMember 是索引回提供组合框的 DataTable 的内容。

Load_All_RouteStages

现在轮到加载路线阶段了。

DataRow ^row;
for each(CRouteStage^ candidate in RouteStageList)
{
    row = dsRouteStages->Tables["dtStages"]->NewRow();
    row[2] = candidate->p_Route_ID;
    row[0] = candidate->p_Stage_ID;
    row[1] = candidate->p_StageName;
    dsRouteStages->Tables["dtStages"]->Rows->Add(row);
}

bsRouteStages->DataSource = dsRouteStages;
bsRouteStages->DataMember = "dtStages";
filteredStages->DataSource = dsRouteStages;
filteredStages->DataMember = "dtStages";

 // bind the dgStage to the bsRouteStages
this->dgStage->DataSource = bsRouteStages;
this->dgStage->DisplayMember = "StageDescription";
this->dgStage->ValueMember = "StageID";

与我们处理路线时非常相似,除了我们有第三列,即路线 ID,我们将在此基础上进行过滤,以及第二个绑定源 filteredStages,它将处理我们的过滤结果。

Load_Passengers

此函数加载绑定到 DataGridView gridPassengerDataTable dtViewPassengerData。将其视为过滤组合框的催化剂,因为虽然它在过滤过程中没有作用,但直接将我的数据读取到 DataGridView 并不成功,因此必须将此结构作为中间件引入。这是关键代码。

DataRow ^dr;

for each(CPassengerMaster^ candidate in PassengerList)
{
    dr = dtViewPassengerData->NewRow();
    dr["Number"] = candidate->p_Person_ID;
    if (candidate->p_Route_ID.HasValue)
        dr["RouteID"] = candidate->p_Route_ID;
    else
        dr["RouteID"] = DBNull::Value;
    if (candidate->p_Stage_ID.HasValue)
        dr["StageID"] = candidate->p_Stage_ID;
    else
        dr["StageID"] = DBNull::Value;
    dtViewPassengerData->Rows->Add(dr);

List_SetUp

List_SetUp 完成了 DataGridView 的填充,其中包含

gridPassenger->AutoGenerateColumns = false;
gridPassenger->DataSource = dtViewPassengerData;

gridPassenger->Columns[dgNumber->Index]->DataPropertyName = "Number";
gridPassenger->Columns[dgRoute->Index]->DataPropertyName = "RouteID";
gridPassenger->Columns[dgStage->Index]->DataPropertyName = "StageID";

AutoGenerateColumns 仅设置为 false,因为我使用 IDE 创建了列。DataSource 是一个关键属性,因为它将 DataGridView 绑定到保存网格中要显示的信息的 DataTable。最后,三个 DataPropertyName 实例将网格列映射到 DataTable 列。

Process_EditingControlShowing

Process_EditingControlShowing 仅针对 dgStage 列进行操作。首先,定义了一个新的 DataGridViewComboBoxEditingControl 类型的变量 ^control,并将其强制转换为 e->Control,或者说,来自 DataGridViewdgStage 列。

DataGridViewComboBoxEditingControl ^control = 
   dynamic_cast<DataGridViewComboBoxEditingControl^>(e->Control);

然后我们创建一个新的 BindingSource 变量 ^bs,其数据源来自 control

BindingSource ^bs = dynamic_cast<BindingSource^>(control->DataSource);

最后一段代码仅在 bs 不为空时执行,并且在我们加载路线和阶段的所有工作完成后,它不会为空 - 但是,该函数确实会进行测试,以防万一。所以继续,我们将 filteredStages 设置为编辑控件的 DataSource

(dynamic_cast<ComboBox^>(e->Control))->DataSource = filteredStages;

接下来,我们声明一个名为 ^RoutevalueObject,并使用它来收集在 DataGridView 的当前行上设置的路线 ID。

Object ^Routevalue = this->gridPassenger->Rows[
  this->gridPassenger->CurrentCell->RowIndex]->Cells[dgRoute->Index]->Value;

如果此值为 null,我们将 filteredStages 的过滤器设置为一个名义上的路线 ID -1,这将在 Stage 组合框中产生一个空的下拉列表,但如果它有值,则会用于设置一个过滤字符串,该字符串的功能与 SQL 语句的 Where 子句类似。

这是导致您的第二个(在此情况下为 dgStage)组合框列具有过滤列表的语句。

if (Routevalue == DBNull::Value || Routevalue == nullptr)
{
    filteredStages->Filter = "RouteID=-1";
}
else
{
    filteredStages->Filter = "RouteID=" + Routevalue->ToString();
}

在更改组合框的内容后,我重置了其 SelectedValue 属性,如下所示。

if (this->gridPassenger->CurrentCell->Value != DBNull::Value 
        && this->gridPassenger->CurrentCell->Value != nullptr)
    {
        control->SelectedValue = this->gridPassenger->CurrentCell->Value;
    }
}

使用过滤的组合框列

现在第二个组合框将根据第一个组合框的值进行过滤。本节将引导您了解查询列的函数。

Process_RowEntered

Process_RowEntered 函数用于处理 DataGridView 上的 RowEnter 事件。它的目的是执行特定任务 - 在本例中,在用户输入行后立即读取当前行。我主要使用它来记录当前行的内容,以便在完成该行后与最终状态进行比较,从而将要写入数据库的字段数量降至最低。本质上,我希望避免“Update TableA set X=1 where X=1”。这是从 dgRoute 列提取当前选定的路线 ID 的代码。

array<DataRow^>^ row;
try
{
    if (gridPassenger->Rows[e->RowIndex]->Cells[dgRoute->Name]->Value != nullptr
        && gridPassenger->Rows[e->RowIndex]->Cells[dgRoute->Name]->Value->ToString() != "")
    {
        String^ IndRoute = 
          gridPassenger->Rows[e->RowIndex]->Cells[dgRoute->Name]->Value->ToString();
        row =dtRoute->Select(String::Format("RouteID={0}", IndRoute));
        if (row->Length > 0)
            lblRoute->Text = row[0]->ItemArray[1]->ToString();
    }
    else
        lblRoute->Text = "";
}
catch (Exception ^e)
{.<
        String ^MessageString = " Error reading internal Route table: " + e->Message;
        MessageBox::Show(MessageString);
}

这里没什么内容,但当前条目的值被读入一个字符串,并用于构成一个“Where 子句”,这非常类似于编辑控件中的。然后将其用作 dtRoute DataTable 的 Select 事件的参数。所产生的 row 被查询以提取路线名称,以便在网格之外的标签框中显示,这样您就可以看到组合框的当前状态。相同的代码应用于 dgStage 列以产生相同的效果。

Process_CellValueChanged

Process_CellValueChanged 函数用于处理 DataGridView 上的 CellValueChanged 事件。当其中一个组合框单元格的值发生更改时,它使用与 RowEntered 事件中显示的相同代码来捕获新值。

此示例中的其他有趣函数

除了本示例中的主要目标之外,其他活动仅包括“单击”组合框箭头、错误图标以及改进的错误处理。

Process_CellClick

Process_CellClick 包含处理 DataGridViewCellClick 事件的代码。它不是必需的,但如果没有它,组合框单元格将需要单击两次才能激活下拉列表。这是函数引擎。

gridPassenger->BeginEdit(true);
(safe_cast<ComboBox^>(gridPassenger->EditingControl))->DroppedDown = true;

Process_CellEndEdit

Process_CellEndEdit 函数用于处理 DataGridView 上的 CellEndEdit 事件。如果您在不填写必需单元格的情况下离开该行,它将在任何需要图标的单元格中放置一个小的红色图标。它与 RowValidating 事件协调提供此功能。

The Error Icon

Process_RowValidating

Process_RowValidating 处理 DataGridView 上的 RowValidating 事件。它使用 if 语句来决定要检查错误的列。在这种情况下,我选择禁止 Number 列为空。它为单元格中的图标设置了填充。

Process_DataError

在我整理枚举示例时,我引入了 DataError 事件来处理 DataGridView 上未预见的显示故障。您可以不使用它,但如果您意外遗漏了某些内容,则可能会调用它,并且它提供了有关出现问题的信息,因为它可以很好地捕获 DataGridView 意外的显示问题。

代码演练

这次我认真考虑过省略这一部分,因为这个例子更侧重于其核心目标,因此您已经看到了所有重要的代码。但是,我决定坚持下去,但会保持简短,只是为了给不同的组件提供一些额外的上下文。该解决方案围绕 DataGridViewComb5.cpp 及其关联的 Form1.h 创建。这些被编译成 EXE。其他模块分别用于形成 DB_PassengerManager、DB_RouteManagerDB_RouteStageManager 程序集。所有这些都包含在同一个解决方案中以简化事务。

Form1.h

Form1.h 以命名空间开头。这些被添加到标准集合中。

using namespace System::Collections::Generic;//Needed for list containers
using namespace DB_RouteManager;
using namespace DB_RouteStageManager;
using namespace DB_PassengerManager;

接下来是构造函数,它包含几行用于错误图标的设置,以及一个调用 LaunchForm 函数以启动所有内容的调用。之后是 IDE 添加的代码以构建窗体。然后是用户定义的函数和变量,您上面已经见过大部分,最后是事件处理函数,所有这些都在本文前面已经介绍过。

DataGridViewComb5.cpp

包含所有函数代码,包括事件处理函数的代码,这些函数默认情况下在 .h 文件中,但通过用户定义的函数重定向到此处。例如,Process_RowEnteredgridPassenger_RowEnter 的主力。我们还没有看过的唯一方面是 LaunchForm 中完成的其他网格美化以及 Load... 函数中的获取调用,但这些是标准代码。

DB_PassengerManager.h

DB_PassengerManager.h 定义了 Passenger 类,它的 Get 属性,以及一个用于返回值的通信类。DB_RouteManager.hDB_RouteStageManager.h 为它们各自的表执行类似的任务。

DB_PassengerManager.cpp

DB_PassengerManager.cpp 被精简到只有一个获取函数,一些值被硬编码到一个列表中,该列表被返回给调用模块。DB_RouteManager.cppDB_RouteStageManager.cpp 为它们各自的表执行类似的任务。

历史

  • 2011-08-24 - V1.0 - 首次提交。
© . All rights reserved.