如何从数据库中填充单个 ComboBoxCell?

问题描述

我使用此代码是为了填充我的组合框。

MysqLDataAdapter sqlDa = new MysqLDataAdapter(query,bd);
DataTable dtbl = new DataTable();
sqlDa.Fill(dtbl);
Box.MinimumWidth = 200;
Box.ValueMember = idFromTable;
Box.displayMember = "Nome";
Box.DataSource = dtbl;

我使用这个代码所以我可以用我在MysqL中拥有的所有数据填充我的dataGridView

MysqLDataAdapter sqlDa = new MysqLDataAdapter("SELECT * FROM fosseis",bd);
DataTable dtbl = new DataTable();
sqlDa.Fill(dtbl);
FosseisDtGridVw.DataSource = dtbl;

代码有效,但问题是我无法在填充 DataGridView 后填充组合框,而且我也无法在整个 DataGridView 中仅填充一个组合框(在特定行中,如 if 语句)。

在我再次填充之前,我曾尝试将数据源从 comboBoxColumn 设为 null,尝试进行类似的操作

datagridview.Row[1].Cells[1].DataGridView.DataSource = dtbl;

还有其他我觉得不够明智的东西来解决这个问题。大多数情况下,当我试图解决这个问题时,它会从另一个代码中得到一个异常

“此对象未设置为实例”

解决方法

我相信可能有不止一种方法可以做到这一点。在下面的示例中,我有两个 DataGridViewComboBoxColumns. 一个用于“州”列表,另一个用于“城市”列表。城市组合框根据选择的州进行填充。测试很少,我猜不同的事件也可能是更好的方法。但这可能有效,在我的测试中它没有错误。

我猜您可能遇到的一个问题是代码“加载”数据的时间。正如您所说,您需要记住,在您知道“状态”(第一个组合框)的值之前,您无法真正“设置”每个组合框单元格。在数据加载之后,您将不知道该行具有什么“状态”。

鉴于此,很明显,如果您希望在最初加载数据时正确设置组合框……那么,您最好确保“State/City”组合框具有正确的“State/City” “ 项目。要么是这样,要么一次向网格添加一行,出于多种原因,这不是一个好主意。

此外,与常规组合框不同,DataGridViewComboBoxCell/Column 是一个不同的怪物,当网格的组合单元格中设置了无效项目时,它会抛出 DataError。只要代码尝试将单元格值设置为不在组合框/列项目列表中的值,网格就会抛出 DataError。示例...加载数据时。发生这种情况时,除非被发现……这是一个应用程序崩溃事件,应该避免。

因此,当使用 DataSource 为网格加载数据时,我们必须拥有“城市”的“完整”列表,因为我们不知道“州”在哪一行.因此,城市的“完整”列表将在“加载”数据时解决这个问题,我们可以在“加载”数据后执行一些操作来过滤新添加的行。

为了设置单个组合框,我们只需“过滤”带有所有“城市”的原始列表,以仅包含来自该选定状态的“城市”。如果我们“过滤”原始列表而不是创建一个新列表,那么获得 DataError 的机会会显着下降。

换句话说,组合列数据源包含完整列表……那么该列中的每个组合框单元格都可以是原始列表的“子集”。如果只显示列表中的某些项目,网格不会抛出错误,它只关心它是否不在其列表中。

您尚未描述的另一个主要问题是数据的“组织方式”。如果数据以不同的方式存储,那么您可能需要做一些调整,但是,主要思想仍然适用。因此,让我们使用六 (6) 个步骤来看看这是如何工作的。

六 (6) 个步骤的一般方法...

  1. 首先是加载两个组合框的数据(州数据和城市数据)。在此示例中,有两个 DataTables,分别称为“States”和“Cities”。此数据“严格”用于组合框数据源……而不是网格本身。
  2. 创建两 (2) 个 DataGridViewComboBoxColumns,分别称为“州”和“城市”。使用步骤 1 中的 States 表作为 States 组合框的数据源,显然 Cities 表作为“Cities”组合框列的数据源。还要设置组合框必要的属性值,以将每列与网格数据源配对。
  3. 将步骤 2 中的列添加到您设置网格数据源的“BEFORE”网格中。
  4. 设置网格数据源。如果设置正确,组合框列应与加载的数据匹配。但是,城市组合框将显示所有城市。
  5. 加载数据后,我们需要调用一个方法,循环遍历网格中的每一行,并将每个“城市”组合框列表“过滤”为所选状态的值。
  6. 在这之后,就是所有的 UI。为此,我们需要订阅一些网格事件来维护每个组合框的“过滤”状态,例如当用户更改“状态”值或添加新行时。

下面是一个完整的例子。创建一个新的 winforms 解决方案,将 DataGridView 拖放到表单上并继续操作。我们最终应该看起来像……

enter image description here

首先,让我们看一下示例数据。将有两个 DataTables,分别称为“States”和“Cities”。

“States”模式将是两 (2) 列......

  1. 唯一的 int ID“StateID”
  2. 一个 string “StateName”

“城市”表将包含三 (3) 列......

  1. 唯一的 int ID“CityID”
  2. 一个int“StateID”和
  3. 一个string“城市名称”

示例…使用 CSV 的状态数据…我粘贴了这个,以便您可以在下面的代码中将其用于测试数据。该代码从一个简单的 CSV 文件中读取这些数据。

1,Alaska
2,California
3,Texas
4,Colorado

使用 CSV 的城市数据

1,1,Anchorage
2,Bethel
3,Fairbanks
4,Soldotna
5,2,Los Angeles
6,San Francisco
7,San Diego
8,Sacramento
9,3,San Antonio
10,Austin
11,Dallas
12,Houston
13,4,Denver
14,Colorado Springs
15,Aurora
16,El Paso
17,Boulder
18,Pleasanton
19,Lubbock
20,Ketchikan

第一步

使用此数据,代码 FillDataSet 将读取每个“States”和“Cities”文件并生成一个 DataSet,其中包含两个名为 StatesCities. 的表,下面是将读取上述文件并返回带有两个表的 DataSet 的代码。

DataSet StateCityDS;

private void FillDataSet() {
  DataTable dt1 = new DataTable();
  dt1.TableName = "States";
  dt1.Columns.Add("StateID",typeof(int));
  dt1.Columns.Add("StateName",typeof(string));
  DataTable dt2 = new DataTable();
  dt2.TableName = "Cities";
  dt2.Columns.Add("CityID",typeof(int));
  dt2.Columns.Add("StateID",typeof(int));
  dt2.Columns.Add("CityName",typeof(string));
  string line;
  string[] splitArray;
  using (StreamReader sr = new StreamReader(@"D:\Test\CSV\StatesDemo_1.txt")) {
    while ((line = sr.ReadLine()) != null) {
      splitArray = line.Split(',');
      if (splitArray.Length >= 2) {
        dt1.Rows.Add(splitArray[0],splitArray[1]);
      }
    }
  }
  using (StreamReader sr = new StreamReader(@"D:\Test\CSV\CitiesDemo_1.txt")) {
    while ((line = sr.ReadLine()) != null) {
      splitArray = line.Split(',');
      if (splitArray.Length >= 3) {
        dt2.Rows.Add(splitArray[0],splitArray[1],splitArray[2]);
      }
    }
  }
  StateCityDS = new DataSet();
  StateCityDS.Tables.Add(dt1);
  StateCityDS.Tables.Add(dt2);
}

第 2 步和第 3 步

现在我们有了一些测试数据。接下来,我们需要一个方法来添加下面的两(2)个DataGridViewComboBoxColumns.,需要注意的是每列的DataPropertyName设置为与“GRIDS”数据源表中的适当列匹配。每列的 DataSource 都设置为上面返回的 DataSet 中的表之一。请注意,DisplayMemberValueMember. 我们要显示州/城市名称,我们希望值是州/城市 ID。

private void AddCascadeComboBoxes() {
  DataGridViewComboBoxColumn col1 = new DataGridViewComboBoxColumn();
  col1.Name = "States";
  col1.HeaderText = "State";
  col1.DataSource = StateCityDS.Tables["States"];
  col1.DisplayMember = "StateName";
  col1.ValueMember = "StateID";
  col1.DataPropertyName = "States";
  dataGridView1.Columns.Add(col1);

  col1 = new DataGridViewComboBoxColumn();
  col1.Name = "Cities";
  col1.HeaderText = "City";
  col1.DataSource = StateCityDS.Tables["Cities"];
  col1.DisplayMember = "CityName";
  col1.ValueMember = "CityID";
  col1.DataPropertyName = "Cities";
  dataGridView1.Columns.Add(col1);
}

这应该处理组合框列。如果我们调用 FillDataSet 然后 AddCascadeComboBoxes,从表单加载事件中,网格中应该有两个组合框列。第一个将列出四 (4) 个州,第二个组合框将列出所有州的所有城市。使用此配置,将数据加载到网格中现在应该可以成功工作。因此,让我们为网格制作一些测试数据并对其进行测试。

第 4 步

下面,GetGridDataSource 模拟将加载到网格本身的数据。在此示例中,它是一个简单的 DataTable,包含三列,分别称为“Name”、“States”和“Cities”。每一行都有不同的名称和不同的州和城市 ID。示例:州 1 = 阿拉斯加,城市 1 = 安克雷奇。这种方法可能看起来像……

private DataTable GetGridDataSource() {
  DataTable dt = new DataTable();
  dt.Columns.Add("Name",typeof(string));
  dt.Columns.Add("States",typeof(int));
  dt.Columns.Add("Cities",typeof(int));
  dt.Rows.Add("John",1);
  dt.Rows.Add("Sally",5);
  dt.Rows.Add("Mary",10);
  dt.Rows.Add("Dean",13);
  dt.Rows.Add("Charlie",8);
  dt.Rows.Add("Nancy",12);
  return dt;
}

如果我们将上面的 DataTable 设置为网格的 DataSource,那么每个组合框列都应该设置正确的“State”和“City”值以匹配原始数据源。此外,您应该会看到额外的“名称”列,如上图所示。如果您看到带有数字的州或城市等额外列,则说明 DataGridViewComboBoxColumn 设置不正确。

步骤 5

这似乎解决了“加载”问题,但是,当我们单击任何“城市”组合框时,列表不会被过滤,我们仍然可以看到所有城市。因此,我们需要从上面的一般描述中添加第 5 步。加载数据后,我们需要一个额外的步骤来“过滤”每个“现有”行上的每个“城市”组合框。这不应该改变单元格的当前值,而且相对简单……就像……

private void SetCityComboBoxesDataSource() {
  foreach (DataGridViewRow row in dataGridView1.Rows) {
    if (!row.IsNewRow) {
      DataRowView dr = (DataRowView)row.DataBoundItem;
      int stateID = (int)dr["States"];
      DataView dv = new DataView(StateCityDS.Tables["Cities"]);
      dv.RowFilter = string.Format("StateID = {0}",stateID);
      DataGridViewComboBoxCell cityCell = (DataGridViewComboBoxCell)(row.Cells["Cities"]);
      cityCell.DataSource = dv;
      cityCell.DisplayMember = "CityName";
      cityCell.ValueMember = "CityID";
    }
  }
}

上面,代码循环遍历每一行,从“States”单元格中获取值并“过滤”城市组合框列表。我们只需要在网格数据加载后调用此代码一次。

如果我们按原样运行此代码,那么组合框将正确显示,如果您单击“城市”组合框,它应该显示“过滤”列表。然而,这只会让我们到达一般描述中的第 5 步。这是“加载”数据而不会出错并过滤添加的行所需要的全部内容,但是我们仍然需要 UI 部分。数据已加载,现在我们需要订阅几个事件,以便在用户与网格 (UI) 交互时正确管理组合框“过滤”。

步骤 6

我打赌有不止一种方法可以做到这一点,而且可能有更好的方法。但是,在此示例中,我连接(订阅)了两 (2) 个网格事件。

grids CellEndEdit 事件将在用户完成“编辑”一个单元格并试图离开该单元格时触发。当它触发时,我们将检查“已编辑”单元格是否在“状态”列中。如果是,那么我们将过滤该行上的“城市”单元格以显示该州的城市。

private void dataGridView1_CellEndEdit(object sender,DataGridViewCellEventArgs e) {
  if (e.RowIndex >= 0 && e.ColumnIndex >= 0) {
    if (dataGridView1.Columns[e.ColumnIndex].Name == "States") {
      if (dataGridView1.Rows[e.RowIndex].Cells["States"].Value != null) {
        if (int.TryParse(dataGridView1.Rows[e.RowIndex].Cells["States"].Value.ToString(),out int stateID)) {
          DataView dv = new DataView(StateCityDS.Tables["Cities"]);
          dv.RowFilter = string.Format("StateID = {0}",stateID);
          DataGridViewComboBoxCell cityCell = (DataGridViewComboBoxCell)(dataGridView1.Rows[e.RowIndex].Cells["Cities"]);
          cityCell.Value = DBNull.Value;
          cityCell.DataSource = dv;
          cityCell.DisplayMember = "CityName";
        }
      }
    }
  }
}

grids EditingControlShowing 事件用于检查用户是否更改了已选择的“状态”值。这会将该行的“城市”单元格设置为“空”。这是为了防止“城市”组合框的值变为“无效”,如果用户在两者都已设置的情况下更改“州”值。如果不进行此检查并将单元格设置为空,则用户可以选择不同的州,而城市将保留之前的州。这将防止数据出现不一致的状态。

private void dataGridView1_EditingControlShowing(object sender,DataGridViewEditingControlShowingEventArgs e) {
  int colIndex = dataGridView1.CurrentCell.ColumnIndex;
  int rowIndex = dataGridView1.CurrentCell.RowIndex;
  if (dataGridView1.Columns[colIndex].Name == "States") {
    dataGridView1.Rows[rowIndex].Cells["Cities"].Value = DBNull.Value;
  }
}

在调试时连接网格 DataError 会派上用场。

private void dataGridView1_DataError(object sender,DataGridViewDataErrorEventArgs e) {
  MessageBox.Show("Error: R" + e.RowIndex + "C" + e.ColumnIndex + " --> " + e.Exception.Message);
}

将所有这些放在一起,下面是其余的代码。请注意,您需要将上述两个 CSV 文件保存到所需位置才能获取测试数据。

DataSet StateCityDS;
DataTable GridDT;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender,EventArgs e) {
  GridDT = GetGridDataSource();
  FillDataSet();
  AddCascadeComboBoxes();
  dataGridView1.DataSource = GridDT;
  SetCityComboBoxesDataSource();
}

最后,应该注意的是,如果用户从网格“新建”行中选择“城市”组合框,则将显示所有城市。允许用户“可以”选择任何城市,但是,一旦用户点击“州”组合框,所选城市值将被删除并应用过滤器。

关于上面第 1 步和设置初始组合框值的特别说明。如果您已正确设置组合框,并且在加载数据时,您会收到数据错误,指出该项目不属于项目的组合框列表……那么……保证数据包含不属于该项目的内容在项目的组合框列表中。

如果是这种情况,那么要调试并找出违规值的位置,您需要遍历数据并检查每个组合框值。如果您发现一个不在组合框项目列表中的项目,那么这就是引发错误的违规项目之一。

在这种情况下您会遇到的问题是如何处理违规值……您不能简单地忽略它。您要么必须从数据本身中删除该行,要么将该新项目添加到项目的组合框列表中。你必须以某种方式处理的毒丸。

抱歉,帖子太长了,我希望这是有道理的。

,

检查 DataGridViewComboBoxColumnDataPropertyName 属性 是否包含指定的字段中,这是datagridview的数据源