根据 DataGridView 中组合框 B 中的选定值过滤组合框 A 中的值

时间:2021-07-22 11:52:42

标签: c# datatable datagridview combobox dataview

我知道这是一个很受欢迎的问题,但我找不到解决我的问题的方法。 DataGridView comboFrom 中有两个组合框 comboTodgvTransfer - 以下是它们的填充方式:

    private void PopComboFrom(DataGridViewComboBoxColumn combo, string valMember, int parValue1, int parValue2)
    {
        combo.DataSource = null;
        DataTable dt = Helper.ExecuteDataTable("pp_sp_MachineAndOp",
                                           new SqlParameter("@MachineAndOpID", SqlDbType.Int) { Value = parValue1 },
                                           new SqlParameter("@Seq", SqlDbType.Int) { Value = parValue2 });

        //Add field to be displayed
        dt.Columns.Add("ToShow", typeof(string), "'Seq: ' + Seq  + '    ID: ' + MachineAndOpID + ' (' + OperationID + ') ' + Operation + ' + ' + Machine");

        // bind data table into combo box.
        combo.DisplayMember = "ToShow";
        combo.ValueMember = valMember;
        combo.DataSource = dt;
    }

    private void PopComboTo(DataGridViewComboBoxColumn combo, string valMember, int parValue1, int parValue2, string seqFilter)
    {
        //combo.DataSource = null;
        DataTable dt = Helper.ExecuteDataTable("pp_sp_MachineAndOp",
                                       new SqlParameter("@MachineAndOpID", SqlDbType.Int) { Value = parValue1 },
                                       new SqlParameter("@Seq", SqlDbType.Int) { Value = parValue2 });

        //Add field to be displayed
        dt.Columns.Add("ToShow", typeof(string), "'Seq: ' + Seq  + '    ID: ' + MachineAndOpID + ' (' + OperationID + ') ' + Operation + ' + ' + Machine");

        // bind data table into combo box.
        DataView dv = new DataView(dt);
        dv.RowFilter = "Seq > " + seqFilter;
        combo.DisplayMember = "ToShow";
        combo.ValueMember = valMember;
        combo.DataSource = dv;
    }

comboFrom 更改时,comboTo 的数据源在此事件处理程序中被过滤:

    private void dgvTransfers_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
    {
        if(dgvTransfers.CurrentCell.ColumnIndex == dgvTransfers.Columns[controlFrom].Index && e.Control is ComboBox)
        {
            ComboBox comboBox = e.Control as ComboBox;
            comboBox.SelectedIndexChanged -= ComboFromSelectionChanged;
            comboBox.SelectedIndexChanged += ComboFromSelectionChanged;
        }
    }
    private void ComboFromSelectionChanged(object sender, EventArgs e)
    {
        PopComboTo(cmbMachineAndOpIDTo, "MachineAndOpID", 0, 0, "7");
    }

过滤器适用于 comboTo,但问题是过滤的 comboTo 中不可见的值也会从 DataGridView 中消失!

未过滤:

enter image description here

过滤:

enter image description here

对于过滤结果为空的每一行都会引发此错误:

enter image description here

如何在 GridViewData 中显示值并同时在 comboTo 中过滤它们?

1 个答案:

答案 0 :(得分:1)

在这个例子中,我们有两个 DataGridViewComboBoxColumn。每列包含相同的数据源并且显然包含相同的值。目标是,如果用户在特定行的一个组合框中选择一个值,那么当用户选择同一行的另一个组合框时,第一个组合框中的选定值将不可用第二个组合框。换句话说,网格中同一行上的两个组合框不会包含相同的值。

我相信有很多方法可以做到这一点。记住这是指两个 DataGridViewComboBoxColumns 可能会有所帮助。在 ComboBoxes 的列中处理 DataGridView 可能具有挑战性,并且不像常规的 ComboBox 那样宽容。将此与每个单元格中的“每个”组合框可能包含“不同的”DataSource 的想法相结合,只会使情况更加恶化,并使网格更容易抛出其可怕的 DataError

使用组合框列时的一个大问题是,如果(不知何故)组合框单元格的值被设置为不在组合框项目列表中的值。普通的 ComboBox 只是忽略这一点,另一方面,DataGridViewComboBoxCell 会抱怨这一点并导致网格抛出它的 DataError。或者更准确地说……代码崩溃问题。因此,您必须小心并确保没有错误地设置组合框值。

鉴于我们不希望一行中的两个组合框具有重复值,在一种情况下,两个组合框的值可能相同……当两个值都为“空”时,或者……还没有被选中。换句话说,两个组合框可能具有相同的“NON”值。这是一个“特殊”情况,我们会寻找它,但是,我想强调的是,简单地将组合框单元格 null 值用于此“空/非选择”可能会出现问题。所以......为了帮助,我建议你添加一个“空/非选择”值作为每个组合框中的项目之一。在这一点上可能并不明显;然而,项目组合框列表中的这个简单的“非选择”项目将在以后简化事情。

首先,我们需要将其分解为两 (2) 个部分。第一部分 (1) 是处理用户与组合框的交互。如果我们有一个只有一行的“空”网格,那么当用户更改一个组合框时,另一个组合框会过滤其数据源,反之亦然。第二部分 (2) 处理在使用现有数据加载网格后要执行的操作。如果我们简单地加载数据,那么每个组合框都将被正确设置,但是,如果用户单击其中一个组合框,则其他组合框的值将可用于复制。一旦用户更改了组合框的值,第一部分就会启动并过滤另一个组合框。换句话说,我们需要在数据加载到网格后做一些事情来过滤每个加载的组合框单元格。

如果您创建一个新的 winforms 解决方案并将 DataGridView 拖放到表单上,您应该能够按照以下步骤进行演示并逐步进行。首先,我们将创建一些简单的组合框数据以显示在项目的组合框列表中。然后,在两个组合框中使用该数据。在此之后,两个组合框将包含相同的数据,用户将能够在一行中复制值。

组合框数据将是一个 DataTable,具有两个属性/字段……一个 int MachineID 和一个 string MachineOPID。数据表将有 11 行数据。应该注意的是,这里我们还想添加我们的“非”选择项……

dt.Rows.Add(0, "-");

应该注意的是,我没有使用“空”stringnull 作为 MachineOPID 值。我们想要那里的“东西”。在这种情况下,它是一个简单的破折号字符。稍后我们将查找零 (0) 的 MachineID 以了解用户选择了“未选择”值。

private DataTable GetComboData() {
  DataTable dt = new DataTable();
  dt.Columns.Add("MachineID", typeof(int));
  dt.Columns.Add("MachineOPID", typeof(string));
  dt.Rows.Add(0, "-");
  for (int i = 1; i < 11; i++) {
    dt.Rows.Add(i, "Mac_" + i);
  }
  return dt;
}

接下来我们需要将组合框列添加到网格中。为此,创建了一个带有四个参数的辅助方法。列名,colName;标题文本、数据源和最后一个 DataPropertyName,将在第二部分 (2) 中使用。

private DataGridViewComboBoxColumn GetComboCol(string colName, string headerText, DataTable data, string dpn) {
  DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
  col.Name = colName;
  col.HeaderText = headerText;
  col.ValueMember = "MachineID";
  col.DisplayMember = "MachineOPID";
  col.DataPropertyName = dpn;
  col.DataSource = data;
  return col;
}

这应该使组合框如下所示工作。每个组合框都有相同的值,用户可以为任意行的两个组合框选择相同的值。

DataTable ComboData;

public Form1() {
  InitializeComponent();
  dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
}

private void Form1_Load(object sender, EventArgs e) {
  ComboData = GetComboData();
  dataGridView1.Columns.Add(GetComboCol("FromMachine", "From Machine", ComboData, "FromMachine"));
  dataGridView1.Columns.Add(GetComboCol("ToMachine", "To Machine", ComboData, "ToMachine"));
  
}

enter image description here

这显然与检查重复的组合框值无关。在这种情况下,我将使用 grids CellValueChanged 事件来“过滤”另一个组合框。例如,如果用户在“FromMachine”列中选择一个值然后离开单元格,那么我们将过滤“ToMachine”组合框。如果更改的单元格是“ToMachine”单元格,则同样的想法也适用。因此,创建了一个辅助方法,它接受三个参数,一个 int 网格行索引;已更改列的 string 列名称,最后是我们要过滤的单元格列的名称。这样,我们可以对两个组合框单元使用相同的方法。它可能看起来像……

private void SetComboData(int rowIndex, string curColName, string targetColName) {
  if (dataGridView1.Rows[rowIndex].Cells[curColName].Value != null) {
    string selectedValue = dataGridView1.Rows[rowIndex].Cells[curColName].Value.ToString();
    DataGridViewComboBoxCell cell = (DataGridViewComboBoxCell)dataGridView1.Rows[rowIndex].Cells[targetColName];
    DataView dv = new DataView(ComboData);
    // we do not want to filter out the "empty" value - i.e. BOTH From and To can be empty
    if (selectedValue != "0") {
      dv.RowFilter = "MachineID <> '" + selectedValue + "'";
    }
    cell.DataSource = dv;
  }
}

首先简单检查一下单元格是否不是null。然后,获取该单元格值,因为这将是我们要从其他组合框中过滤掉的项目。抓取另一个组合框单元格并将其转换为 DataGridViewComboBoxCell 以便我们可以设置其数据源。创建一个新的DataView,设置行过滤器,除非所选项目是我们不想过滤掉的“非选择”项目。最后设置单元格数据源。

这个辅助方法应该将 grids CellValueChanged 事件中的代码简化为类似……

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  if (dataGridView1.Columns[e.ColumnIndex].Name == "FromMachine") {
    SetComboData(e.RowIndex, "FromMachine", "ToMachine");
  }
  else {
    if (dataGridView1.Columns[e.ColumnIndex].Name == "ToMachine") {
      SetComboData(e.RowIndex, "ToMachine", "FromMachine");
    }
  }
}


// updated constructor to subscribe to the grids `CellValueChanged` event
public Form1() {
  InitializeComponent();
  dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
  dataGridView1.CellValueChanged += new DataGridViewCellEventHandler(dataGridView1_CellValueChanged);
}

此更改应如下所示。同一行上的两个组合框不能设置为相同的值。如果任一组合框发生更改并且组合框都可以设置为相同的“未选择”值,则此值应更新。

enter image description here

这应该完成第一部分用户与组合框的交互。第二部分是将数据加载到网格时要做什么。 IMO,如果您在数据绑定的网格中使用组合框列……“检查”绑定到组合框列的组合框数据是明智的。任何错误的数据都会导致网格抛出其代码崩溃 DataError。即使采用简单的方法,在将数据绑定到网格之前检查网格数据也只是一种 CYA 方法。

所以……问题永远是……

<块引用>

“如果数据中的一行在组合中有违规数据,你会怎么做 盒子……即……一个不存在的值(越界)……或者在这种情况下, 重复 FromMachineToMachine 值?” ...

不幸的是,这是我们不能忽视的。您要么必须删除有问题的数据,要么对其进行更改。这两个想法都是坏主意,但你有什么选择?在此示例中,如果两个值相同,那么我们要做的是将“ToMachine”“更改”为我们的“非选择”值。如果该值越界,我们将其更改为未选中的值。显然,这种“变化”可能是日志文件中的一些需要的信息。然而,在这个例子中……我们只想改变它,记录它并继续。

为了测试这一点,下面是一些我们可以用来绑定到网格的测试数据。它有三 (3) 行具有重复值。此外,它有三行错误数据,使得 MachineID 超出范围 (12, -1)。它可能看起来像……

private DataTable GetGridData() {
  DataTable dt = new DataTable();
  dt.Columns.Add("FromMachine", typeof(int));
  dt.Columns.Add("ToMachine", typeof(int));
  dt.Rows.Add(1, 1);
  dt.Rows.Add(4, 1);
  dt.Rows.Add(5, 5);
  dt.Rows.Add(0, 9);
  dt.Rows.Add(4, 0);
  dt.Rows.Add(0, 0);
  dt.Rows.Add(12, 0);
  dt.Rows.Add(0, -1);
  dt.Rows.Add(-1, 12);
  return dt;
}

如果我们将此表绑定到网格,我们将得到网格 DataError。此外,重复值将显示在网格中。重复值是显而易见的常量 DataError 的次要问题,因为数据中的值越界……即 12 和 -1 值。这就是为什么我们需要在将数据绑定到网格之前检查网格组合框数据值的原因。不要以为你可以简单地捕捉到 DataError 并忽略/吞下错误......你必须做点什么。

为此,我们将创建一个简单的方法,该方法采用原始网格数据表并遍历表的行。我们将检查越界值,因为我们正在这样做,我们可以检查“重复”组合框值并采取相应的行动。在这两种情况下,如果值超出范围,我们会将该值设置为“非选择”值 0。如果同一行上的任何两个值相等,则同样适用。

在我们进行这些更改之后,我们“应该”能够更轻松地知道网格数据错误不会因为组合框值超出范围而被抛出。此外,这将修复具有相同组合框值的任何行。这种修复方法可能看起来像……

private void FixDuplicateComboData(DataTable originalData) {
  int curRowIndex = 0;
  int fromValue;
  int toValue;
  foreach (DataRow row in originalData.Rows) {
    fromValue = (int)row["FromMachine"];
    toValue = (int)row["ToMachine"];
    if (fromValue < 0 || fromValue > 10) {
      Debug.WriteLine("Row: " + curRowIndex + " From: " + row["FromMachine"] + " value is out of range - setting to no-choice");
      row["FromMachine"] = 0;
    }
    if (toValue < 0 || toValue > 10) {
      Debug.WriteLine("Row: " + curRowIndex + " To: " + row["ToMachine"] + " value is out of range - setting to no-choice");
      row["ToMachine"] = 0;
    }
    if (fromValue == toValue) {
      Debug.WriteLine("Row: " + curRowIndex + " From: " + row["FromMachine"] + " and To: " + row["ToMachine"] + " - Machines are the same... Setting to no-choice");
      row["ToMachine"] = 0;
    }
    curRowIndex++;
  }
}

这应该可以消除数据错误,并且已修复重复项,因此加载数据后网格中不会显示重复项。但是,仍然有一个小问题。问题是当数据加载到网格中时,每个组合框单元格都没有被过滤。因此,在加载数据后,如果我们单击具有两个值的组合框行,您将在组合框项目列表中看到“其他”组合框值。此外,如果我们选择该项目……我们的数据错误朋友会开始抱怨。

因此,我们还有最后一步来完成这项工作。将好的数据加载到网格中后,我们需要遍历网格中的所有行,并将每个组合框单元格的数据源设置为正确设置的过滤器。幸运的是,我们之前的 SetComboData 方法应该使这相对简单......就像......

private void SetComboDataAfterDataLoad() {
  foreach (DataGridViewRow row in dataGridView1.Rows) {
    SetComboData(row.Index, "FromMachine", "ToMachine");
    SetComboData(row.Index, "ToMachine", "FromMachine");
  }
}

最终产品应该像下图那样工作......

DataTable ComboData;
DataTable GridTable;

public Form1() {
  InitializeComponent();
  dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
  dataGridView1.CellValueChanged += new DataGridViewCellEventHandler(dataGridView1_CellValueChanged);
  dataGridView1.DataError += new DataGridViewDataErrorEventHandler(dataGridView1_DataError);
}


private void Form1_Load(object sender, EventArgs e) {
  ComboData = GetComboData();
  dataGridView1.Columns.Add(GetComboCol("FromMachine", "From Machine", ComboData, "FromMachine"));
  dataGridView1.Columns.Add(GetComboCol("ToMachine", "To Machine", ComboData, "ToMachine"));
  GridTable = GetGridData();
  FixDuplicateComboData(GridTable);
  dataGridView1.DataSource = GridTable;
  SetComboDataAfterDataLoad();
}


private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) {
  MessageBox.Show("Data Error: " + e.Exception.Message);
  Debug.WriteLine("Data Error: " + e.Exception.Message);
}

enter image description here

这应该完成了示例。我希望这是有道理的。

相关问题