【问题标题】:Detect column reordering - ColumnDisplayIndexChanged raises multiple times检测列重新排序 - ColumnDisplayIndexChanged 多次引发
【发布时间】:2019-05-19 20:05:27
【问题描述】:

我正在开发一个 .net 4.6.1 C# winforms 项目,该项目有一个 datagridview,用户可以在其中更改列的顺序。

我想将新顺序存储在 db 表中,但无法找到正确的事件来检测用户何时更改了列的顺序。

在这里搜索后,我被指向DataGridView.ColumnDisplayIndexChanged事件in this thread。但是那个并不能解决我的问题。 (它只在填充数据网格视图时提供了多个事件的解决方案,但通过在设置数据源后添加处理程序很容易回答)

这种方法有效,但是当用户更改列的顺序时会被触发多次(看起来就像将 A、B、C、D 列更改为 D、A、B、C 时事件被触发 3 次(可能是 A,B,D,C - A,D,B,C - D,A,B,C)

我很难找出如何检测事件是否是最后一个(因为我不想存储所有这些新订单,只存储最后一个)

我的问题是:

  1. 对于我的案例来说,此事件是“最佳”事件吗?

  2. 如果是,如何检测最终的 ColumnDisplayIndexChanged 事件(D、A、B、C)?

【问题讨论】:

  • 您可以使用Form.Closing,这样您只需提交一次更改,而不是每次用户更改排序时。
  • 感谢@Sinatr 的建议。我已经考虑过了。但这仍然需要大量数据,因为用户会经常关闭表单(并且大多数表单都有多个数据网格)
  • 是的,你的问题是保存不知道何时保存。我无法提出答案,因为它已关闭,但我可以为您指出正确的方法。尝试使用去抖动器并将超时设置为您需要的值。因此,当事件发生变化时,根据您可以接受的情况,让操作说 5 秒或 1 秒。因此,对同一事物的连续更改将停止为最终更改。如果一次更改调用该事件 5 次。 debouncer 只会调用最后一个,所以你只保存一次。去抖功能可以在这里找到
  • 谢谢@npo,我想我通过更改它重新打开了这个问题(因为我认为它不是重复的)

标签: c# .net winforms datagridview


【解决方案1】:

当您对列重新排序时,ColumnDisplayIndexChanged 将针对其显示索引已更改的所有列引发。例如,如果您将列 A 移动到 C 之后的位置,则所有这三列都会引发事件。

有一个解决方案可以抓住最后一个。 DataGridViewColumn 有一个名为 DisplayIndexHasChanged 的内部属性,如果应该为列触发事件,则该属性为 true。引发事件的私有方法,查看列列表,如果该属性为真,则对于每一列,首先将其设置为假,然后引发事件。你可以阅读内部实现here

您可以检查是否没有 DisplayIndexHasChanged 为真值的列,您可以说它是序列中的最后一个事件:

private void dgv_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e)
{
    var g = (DataGridView)sender;
    var property = typeof(DataGridViewColumn).GetProperty("DisplayIndexHasChanged",
        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    if (g.Columns.Cast<DataGridViewColumn>().Any(x => (bool)property.GetValue(x)))
        return;
    else
        MessageBox.Show("Changed");
}

请记住,您应该在添加列时禁用捕获该事件:

private void f_Load(object sender, EventArgs e)
{
    LoadData();
}
void LoadData()
{
    dgv.ColumnDisplayIndexChanged -= dgv_ColumnDisplayIndexChanged;
    dgv.DataSource = null;
    var dt = new DataTable();
    dt.Columns.Add("A");
    dt.Columns.Add("B");
    dt.Columns.Add("C");
    dgv.DataSource = dt;
    dgv.ColumnDisplayIndexChanged += dgv_ColumnDisplayIndexChanged;
}

【讨论】:

  • 很好的答案,清楚地解释了 DataGridView 的列标题的内部工作原理:当用户将一个拖到不同的位置时,不仅会触发一个,还会触发 一系列 事件对于受删除列的新位置影响的每个列标题(即,旧位置和新位置之间的每一列都有一个)。
【解决方案2】:

我的建议是不要做任何自定义逻辑来确定它是最后一个还是类似的东西。最好的方法是在每个事件之后保存,但您可以对其进行去抖动。

使用 debounce 方法,如果新事件在之后立即触发,则可以取消旧事件,具体取决于您希望在调用之间允许的时间量。

例如:仅当在 1 秒或 5 秒后没有新事件时才写入存储,具体取决于您的应用程序可接受的内容

假设我们决定用 1 秒的去抖动来保存

第一个事件发生你触发有 1 秒执行的动作

如果触发另一个事件,则忽略旧操作,新操作现在有 1 秒的时间执行,以此类推其他顺序操作

public static Action Debounce(this Action func, int milliseconds = 300)
{
    var last = 0;
    return arg =>
    {
        var current = Interlocked.Increment(ref last);
        Task.Delay(milliseconds).ContinueWith(task =>
        {
            if (current == last) func(arg);
            task.Dispose();
        });
    };
}

假设以下操作用于保存您的数据 动作 a = (arg) => { 在这里保存我的数据 };

首先将去抖动器分配给您的操作

var debouncedWrapper = a.Debounce(1000); //1 sec debounce

那么你可以如下使用它

public void datagridchangeevent(object sender, Event e)
{
  debouncedWrapper()
}

这将忽略顺序调用,只有在一秒钟内没有调用任何内容时才会执行 aciton

【讨论】:

  • 我会给出这个答案。我仍然怀疑这对我来说是否是“最佳”选择,但时间会证明一切。如果我找到更好的方法(对我来说),我也会在这里发布。
猜你喜欢
  • 1970-01-01
  • 2012-08-11
  • 1970-01-01
  • 2018-10-28
  • 1970-01-01
  • 2019-03-26
  • 2019-02-02
  • 1970-01-01
相关资源
最近更新 更多