【问题标题】:Editing DataGridView data changes row orders编辑 DataGridView 数据更改行顺序
【发布时间】:2015-06-11 15:55:47
【问题描述】:

我有一个绑定到DataTableDataGridView (dgvVolReport)。表中有一个名为"group" 的列(DataGridView 上的colGroup)。我正在尝试创建一个按钮,可以将所有选定的行组合在一起(即分配相同的组号)。

我有分配组号并正确编辑其他组号的逻辑,并且每行的新组存储在名为groupNumbersAllList<int> 中。

然后我将这些号码转移到DataGridView,如下所示:

for (int r = 0; r < groupNumbersAll.Count; r++)
{
    dgvVolReport.Rows[r].Cells["colGroup"].Value = groupNumbersAll[r];
}

// A breakpoint is set on this line (i.e. values below are BEFORE this line has been run
dgvVolReport.Sort(colGroup, ListSortDirection.Ascending);

不知何故,这似乎改变了行顺序或将数字分配给错误的行。

我已经在那个循环之后进行了调试,groupNumbersAll 的内容是正确的:

请注意,这两个固定的“六”对应于两个选定的行(之前是五行)。现在,colGroup 列的内容如下所示:

您可以从6 行看到它们不匹配。为什么他们不匹配?为什么?

但更奇怪的是,如果我注释掉最后一行 (dgvVolReport.Sort(colGroup, ListSortDirection.Ascending);),它甚至还没有运行,因为那是断点所在的位置,然后它们突然匹配了!!!有谁知道这里发生了什么?

顺便说一句,我也尝试过解除绑定,然后重新绑定我认为有一段时间的 DataSource,但现在我发现它没有。

以下是请求的组按钮的完整代码(来自按钮单击)(注意最终工作代码为here):

private void btnGroup_Click(object sender, EventArgs e)
        {
            //dgvVolReport.CommitEdit(DataGridViewDataErrorContexts.Commit);

            List<int> groupNumbersAll = new List<int>();
            List<int> groupNumbersNotSelected = new List<int>();
            List<int> groupNumbersSelected = new List<int>();
            List<int> rowNumbersSelected = new List<int>();
            List<int> groupNumbersOfEntirelySelectedGroups = new List<int>();

            // Populate groups (All, Selected and NotSelected)
            foreach (DataGridViewRow row in dgvVolReport.Rows)
            {
                groupNumbersAll.Add(Convert.ToInt16(row.Cells["colGroup"].Value));

                if (row.Selected)
                {
                    groupNumbersSelected.Add(Convert.ToInt16(row.Cells["colGroup"].Value));
                    rowNumbersSelected.Add(row.Index);
                }
                else
                {
                    groupNumbersNotSelected.Add(Convert.ToInt16(row.Cells["colGroup"].Value));
                }
            }

            int smallestSelectedGroupNumber = groupNumbersSelected.Min();
            int newGroupNumber = smallestSelectedGroupNumber;
            bool newGroupFlag = false;
            // If the selected rows do not contain all rows with group number equal to the smallest selected group number,
            // then we need to create a new group whose group number is the smallest selected group number plus 1.
            // This then implies that we need to add 1 to the group number of every row with a group number larger than the
            // lowest selected group number (that is the original lowest selected number before we added 1).
            if (groupNumbersNotSelected.Contains(smallestSelectedGroupNumber))
            {
                newGroupNumber++;
                newGroupFlag = true;
            }

            // Find which groups have been selected entirely, but ignore the smallest number. 
            // If a group has been entirely selected it means that that group number will no longer exist. Thus we will have to 
            // subtract 1 from each group number that is larger than a group that has been entirely selected. This process is 
            // cumulative, so if a number is higher than 2 entirely selected groups (excluding the smallest selected group) then
            // we need to subtract 2 from the group number.
            foreach (int group in groupNumbersSelected.Distinct())
            {
                if (!groupNumbersNotSelected.Contains(group) && !(group == smallestSelectedGroupNumber))
                {
                    groupNumbersOfEntirelySelectedGroups.Add(group);
                }
            }

            // Find the new group numbers
            for (int r = 0; r < groupNumbersAll.Count; r++)
            {
                int groupNum = groupNumbersAll[r];

                if (rowNumbersSelected.Contains(r))
                {
                    groupNumbersAll[r] = newGroupNumber;
                }
                else
                {
                    int subtract = groupNumbersOfEntirelySelectedGroups.Where(num => num < groupNum).Count();

                    if (newGroupFlag && groupNum >= newGroupNumber)
                    {
                        groupNum++;
                    }

                    groupNumbersAll[r] = groupNum - subtract;
                }
            }

            //// Unbind the data table because of weird ass sorting error: https://stackoverflow.com/questions/30785736/editing-datagridview-data-changes-row-orders/30799185#30799185
            //DataTable dt = (DataTable)dgvVolReport.DataSource;

            //dgvVolReport.DataSource = null;
            ////Alter the values on the underlying DataTable
            //for (int r = 0; r < groupNumbersAll.Count; r++)
            //{
            //    dt.Rows[r]["Group"] = groupNumbersAll[r];
            //}
            ////Rebind
            //dgvVolReport.DataSource = dt;

            //((DataView)dgvVolReport.DataSource).Sort = null;

            int counter = 0;
            foreach (DataGridViewRow row in dgvVolReport.Rows)
            {
                row.Cells["colGroup"].Value = groupNumbersAll[counter++];
            }

            dgvVolReport.Sort(colGroup, ListSortDirection.Ascending);
        }
    }

【问题讨论】:

  • 不确定我是否理解,这与我使用的代码相同吗?组号不是行号。
  • 我怀疑这与您正在进行的排序有关。 colGroup 是否参与排序?如果你在错误的时间排序,那么你的索引就会不同步。
  • 我在当前设置断点的行上调用 sort。每次值更改时都会自动调用 sort 吗?也许我只需要关闭它...
  • 所以我尝试在循环之前将该列的SortMode 转换为.Programmatic,然后将abck 转换为.Automatic,这确实解决了它。但是,如果我注释掉这一行(发生在循环之后以及在我打破并检查上面显示的值的点之后):dgvVolReport.Sort(colGroup, ListSortDirection.Ascending); 那么它突然就起作用了。 所以代码后面的那一行的存在会在它发生之前改变行为!!!我该如何解决这个问题?

标签: c# .net winforms sorting datagridview


【解决方案1】:

我认为你的groupNumbersAll - 或者它的使用方式 - 是罪魁祸首。很难确定,因为我们看不到它是如何使用的,更重要的是,它为重用做好了准备。然而,这是不正确的:

for (int r = 0; r < groupNumbersAll.Count; r++)
{
    dgvVolReport.Rows[r].Cells["colGroup"].Value = groupNumbersAll[r];
}

如果 DGV 是数据绑定的,则不应更改 DGV 中的值。事实上,如果你在一个新的赋值之后查询一个单元格,你应该会看到它没有改变:

Console.WriteLine(dgvGrouper.Rows[1].Cells[GrpColIndex].Value);
dgvGrouper.Rows[1].Cells[GrpColIndex].Value = 99;
Console.WriteLine(dgvGrouper.Rows[1].Cells[GrpColIndex].Value);

我的打印前后的值相同。我必须认为 Group 值没有显示在网格上,否则您会看到该值没有改变。相反,您应该更改数据源:

for (int n = 0; n < myDT.Rows.Count; n++)
{
    myDT.Rows[n][GrpColIndex] = rList[n];
} 

我的数据如下所示:

Name 和 Value 都代表原始数据顺序。

问题是排序会改变显示顺序,但不会改变底层DataTable - DGV 只是呈现数据的视图(DataTable -> DataView -> DataGridView 控件)。转储已排序 DGV、List 和 DataTable 的 Group 值证明了这一点:

*** SORTED DGV Rows to List and DT  ***
DGV Grp: 1   List Val: 1   DT Val: 1  
DGV Grp: 1   List Val: 2   DT Val: 2  
DGV Grp: 1   List Val: 2   DT Val: 2  
DGV Grp: 1   List Val: 1   DT Val: 1  
DGV Grp: 2   List Val: 1   DT Val: 1  
DGV Grp: 2   List Val: 1   DT Val: 1  

我通过 DataTable 将第 2 行和第 3 行分组。之后 List 和 DT 同步,但 DGV 不同步。下次您为所选行迭代 DGV 时,这些行索引与 List 索引或 DataTable 行索引无关。

由于 DGV 选定行是起点,您需要将 DataGridView 选定行索引转换为 DataTable 行索引。如果 DGV 可能发生其他事情(添加的行、删除的行、用户按列排序等),我会每次都重建列表(无论如何我都会这样做,因为它与控件和数据完全分离) :

// rebuild the List from the Group value in the DataTable
rList = new List<int>();
for (Int32 n = 0; n < myDT.Rows.Count; n++)
{
   rList.Add((int)myDT.Rows[n][GrpColIndex]);
}

// loop thru the selected rows
foreach(DataGridViewRow dgvr in dgvGrouper.SelectedRows)
{
   // get at the underlying data item for this row
   // which is likely at a different index than the DGV row
   DataRowView dr = (DataRowView)dgvr.DataBoundItem;

   // use the DataView.Table to get the index of this DataRowView
   rList[dr.DataView.Table.Rows.IndexOf(dr.Row)] = newGrpVal;
}
newGrpVal += 1;

dr.DataView.Table.Rows.IndexOf(dr.Row) 基本上将 DGV 视觉选定的行索引转换为实际的 DataTable 行索引。只要组值没有内在含义,您就可以使用一些简单的东西,例如所示的计数器。如果您需要从表中选择行的当前值以获得更强大的组方法dr[GrpColIndex] 应该包含您需要的值。

示例:
贝克和查理已经重新组合。 DGV 将它们显示在底部,但我们看到它们仍保留在 DataTable 的插槽 2 和 3 中。现在,在选择 Delta 和 Echo 后,上面的代码运行。应用更改前的索引视图:

DGV Grp: 1   List Val: 1   DT Val: 1
DGV Grp: 1   List Val: 2   DT Val: 2
DGV Grp: 1   List Val: 2   DT Val: 2
DGV Grp: 1   List Val: 3   DT Val: 1
DGV Grp: 2   List Val: 3   DT Val: 1
DGV Grp: 2   List Val: 1   DT Val: 1

计划中的更改是即时的;项目 4 和 5 将被设置为组 #3 并排序到底部。只需将列表复制到数据源:

for (int n = 0; n < myDT.Rows.Count; n++)
{
   myDT.Rows[n][GrpColIndex] = rList[n];
}   

如果像字典或元组这样的东西会很好,也许使用一行中的哈希码可以更好地把事情联系在一起,但我想不出比重新启动列表更好的方法。

一旦有更多关于列表和/或分组的信息可用,我将尝试修改任何重要信息。

【讨论】:

  • 我会在周一发布更多代码。但是我必须重新分配高于所选组的组,因为如果创建了新组,它们可能会上升,如果合并多个组,它们可能会下降。
  • 好的,有道理。但是如果下次列表没有重新加载,它的起点将与 DGV 行的当前顺序不匹配。
  • 我会在星期一发布代码(尽管分组逻辑有点复杂),我确信它与 gridview 排序与数据表不同,但我不能工作出来。当我回到工作岗位时,还会有更多内容。
  • 我现在已经添加了完整的代码。我认为尽管您对 DataView 所说的内容和您的示例听起来确实是解决问题的正确方法,但我现在会尝试一下
  • 谢谢 - 现在看起来可以工作了!如果有人想看到它,我将把完整的工作代码添加到我的旧答案 (stackoverflow.com/a/30799185/1011724) 中
【解决方案2】:

我猜这个值还没有提交到底层数据源

直接修改底层数据源

for (int r = 0; r < groupNumbersAll.Count; r++)
{
    var drv = (DataRowView)dgvVolReport.Rows[r].DataBoundItem;
    drv.Row["colGroup"] = groupNumbersAll[r];
}

【讨论】:

  • 不,给出了同样的错误行为。 (同样,只有当我在代码中有 sortlater 时)
【解决方案3】:

我也会尝试直接编辑DataTable。现在跳过使用 groupNumbersAll 并使用增量值进行测试:

DataTable dt = (DataTable)dgvVolReport.DataSource;
dgvVolReport.DataSource = null;

for (int i = 0; i < dt.Rows.Count; i++) {
    dt.Rows[i]["Group"] = i;
}

dgvVolReport.DataSource = dt;
//dgvVolReport.Sort(colGroup, ListSortDirection.Ascending);

如果DataGridView 中的每一行和每一列"Group" 不显示唯一整数,则问题出在DataGridView 的属性中。您还应该检查"Group"-column 的属性(值类型等)。

【讨论】:

    【解决方案4】:

    基于Plutonix's answer,这是最终的工作代码:

            private void btnGroup_Click(object sender, EventArgs e)
            {
                //See: https://stackoverflow.com/questions/30785736/editing-datagridview-data-changes-row-orders
    
                List<int> groupNumbersAll = new List<int>();
                List<int> groupNumbersNotSelected = new List<int>();
                List<int> groupNumbersSelected = new List<int>();
                List<int> rowNumbersSelected = new List<int>();
                List<int> groupNumbersOfEntirelySelectedGroups = new List<int>();
    
                DataTable dt = (DataTable)dgvVolReport.DataSource;
    
                // Populate groups (All, Selected and NotSelected)
                for (int r = 0; r < dt.Rows.Count; r++)
                {
                    int group = Convert.ToInt16(dt.Rows[r]["Group"]);
                    groupNumbersAll.Add(group);
    
                    bool selected = false;
                    foreach (DataGridViewRow gridrow in dgvVolReport.SelectedRows)
                    {
                        DataRowView dr = (DataRowView)gridrow.DataBoundItem;
                        selected |= dr.DataView.Table.Rows.IndexOf(dr.Row) == r;
                    }
    
                    if (selected)
                    {
                        groupNumbersSelected.Add(group);
                        rowNumbersSelected.Add(r);
                    }
                    else
                    {
                        groupNumbersNotSelected.Add(group);
                    }
                }
    
                int smallestSelectedGroupNumber = groupNumbersSelected.Min();
                int newGroupNumber = smallestSelectedGroupNumber;
                bool newGroupFlag = false;
                // If the selected rows do not contain all rows with group number equal to the smallest selected group number,
                // then we need to create a new group whose group number is the smallest selected group number plus 1.
                // This then implies that we need to add 1 to the group number of every row with a group number larger than the
                // lowest selected group number (that is the original lowest selected number before we added 1).
                if (groupNumbersNotSelected.Contains(smallestSelectedGroupNumber))
                {
                    newGroupNumber++;
                    newGroupFlag = true;
                }
    
                // Find which groups have been selected entirely, but ignore the smallest number. 
                // If a group has been entirely selected it means that that group number will no longer exist. Thus we will have to 
                // subtract 1 from each group number that is larger than a group that has been entirely selected. This process is 
                // cumulative, so if a number is higher than 2 entirely selected groups (excluding the smallest selected group) then
                // we need to subtract 2 from the group number.
                foreach (int group in groupNumbersSelected.Distinct())
                {
                    if (!groupNumbersNotSelected.Contains(group) && !(group == smallestSelectedGroupNumber))
                    {
                        groupNumbersOfEntirelySelectedGroups.Add(group);
                    }
                }
    
                // Find the new group numbers
                for (int r = 0; r < groupNumbersAll.Count; r++)
                {
                    int groupNum = groupNumbersAll[r];
    
                    if (rowNumbersSelected.Contains(r))
                    {
                        groupNumbersAll[r] = newGroupNumber;
                    }
                    else
                    {
                        int subtract = groupNumbersOfEntirelySelectedGroups.Where(num => num < groupNum).Count();
    
                        if (newGroupFlag && groupNum >= newGroupNumber)
                        {
                            groupNum++;
                        }
    
                        groupNumbersAll[r] = groupNum - subtract;
                    }
                }
    
                for (int n = 0; n < dt.Rows.Count; n++)
                {
                    dt.Rows[n]["Group"] = groupNumbersAll[n];
                }   
    
                dgvVolReport.Sort(colGroup, ListSortDirection.Ascending);
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 2010-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-04
      相关资源
      最近更新 更多