17

如果用户选择 .NET 2.0 ListView 中的所有项目,ListView 将为每个项目触发SelectedIndexChanged事件,而不是触发事件以指示选择已更改。

如果用户随后单击以选择列表中的一个项目,则 ListView 将为每个未选择的项目触发SelectedIndexChanged事件,然后为单个新选择的项目触发SelectedIndexChanged事件,而不是触发一个事件来指示选择已经改变。

如果您在SelectedIndexChanged事件处理程序中有代码,则当您开始在列表中有数百/数千个项目时,程序将变得非常无响应。

我考虑过驻留计时器等。

但是有没有人有一个很好的解决方案来避免数千个不必要的 ListView。SelectedIndexChange事件,什么时候真的会发生一个事件

4

14 回答 14

12

伊恩的好解决方案。我把它做成了一个可重用的类,确保正确处理计时器。我还缩短了获取响应速度更快的应用程序的时间间隔。该控件还具有双缓冲以减少闪烁。

  public class DoublebufferedListView : System.Windows.Forms.ListView
  {
     private Timer m_changeDelayTimer = null;
     public DoublebufferedListView()
        : base()
     {
        // Set common properties for our listviews
        if (!SystemInformation.TerminalServerSession)
        {
           DoubleBuffered = true;
           SetStyle(ControlStyles.ResizeRedraw, true);
        }
     }

     /// <summary>
     /// Make sure to properly dispose of the timer
     /// </summary>
     /// <param name="disposing"></param>
     protected override void Dispose(bool disposing)
     {
        if (disposing && m_changeDelayTimer != null)
        {
           m_changeDelayTimer.Tick -= ChangeDelayTimerTick;
           m_changeDelayTimer.Dispose();
        }
        base.Dispose(disposing);
     }

     /// <summary>
     /// Hack to avoid lots of unnecessary change events by marshaling with a timer:
     /// http://stackoverflow.com/questions/86793/how-to-avoid-thousands-of-needless-listview-selectedindexchanged-events
     /// </summary>
     /// <param name="e"></param>
     protected override void OnSelectedIndexChanged(EventArgs e)
     {
        if (m_changeDelayTimer == null)
        {
           m_changeDelayTimer = new Timer();
           m_changeDelayTimer.Tick += ChangeDelayTimerTick;
           m_changeDelayTimer.Interval = 40;
        }
        // When a new SelectedIndexChanged event arrives, disable, then enable the
        // timer, effectively resetting it, so that after the last one in a batch
        // arrives, there is at least 40 ms before we react, plenty of time 
        // to wait any other selection events in the same batch.
        m_changeDelayTimer.Enabled = false;
        m_changeDelayTimer.Enabled = true;
     }

     private void ChangeDelayTimerTick(object sender, EventArgs e)
     {
        m_changeDelayTimer.Enabled = false;
        base.OnSelectedIndexChanged(new EventArgs());
     }
  }

请让我知道这是否可以改进。

于 2009-06-11T13:11:47.173 回答
3

这是我现在使用的停留计时器解决方案(停留只是意味着“等待一点”)。此代码可能会受到竞争条件的影响,并且可能会出现空引用异常。

Timer changeDelayTimer = null;

private void lvResults_SelectedIndexChanged(object sender, EventArgs e)
{
        if (this.changeDelayTimer == null)
        {
            this.changeDelayTimer = new Timer();
            this.changeDelayTimer.Tick += ChangeDelayTimerTick;
            this.changeDelayTimer.Interval = 200; //200ms is what Explorer uses
        }
        this.changeDelayTimer.Enabled = false;
        this.changeDelayTimer.Enabled = true;
}

private void ChangeDelayTimerTick(object sender, EventArgs e)
{
    this.changeDelayTimer.Enabled = false;
    this.changeDelayTimer.Dispose();
    this.changeDelayTimer = null;

    //Add original SelectedIndexChanged event handler code here
    //todo
}
于 2008-09-17T20:21:45.457 回答
2

我知道的老问题,但这似乎仍然是一个问题。

这是我不使用计时器的解决方案。

它在触发 SelectionChanged 事件之前等待 MouseUp 或 KeyUp 事件。如果您以编程方式更改选择,那么这将不起作用,事件不会触发,但您可以轻松添加 FinishedChanging 事件或其他东西来触发事件。

(它还有一些与这个问题无关的东西来停止闪烁)。

public class ListViewNF : ListView
{
    bool SelectedIndexChanging = false;

    public ListViewNF()
    {
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        this.SetStyle(ControlStyles.EnableNotifyMessage, true);
    }

    protected override void OnNotifyMessage(Message m)
    {
        if(m.Msg != 0x14)
            base.OnNotifyMessage(m);
    }

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        SelectedIndexChanging = true;
        //base.OnSelectedIndexChanged(e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (SelectedIndexChanging)
        {
            base.OnSelectedIndexChanged(EventArgs.Empty);
            SelectedIndexChanging = false;
        }

        base.OnMouseUp(e);
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        if (SelectedIndexChanging)
        {
            base.OnSelectedIndexChanged(EventArgs.Empty);
            SelectedIndexChanging = false;
        }

        base.OnKeyUp(e);
    }
}
于 2014-01-15T11:25:18.840 回答
1

计时器是最好的整体解决方案。

Jens 的建议的一个问题是,一旦列表中有很多选定项目(数千个或更多),获取选定项目的列表就开始需要很长时间。

与其每次发生 SelectedIndexChanged 事件时都创建一个计时器对象,不如将一个永久性的对象放在设计器的表单上,并让它检查类中的布尔变量以查看它是否应该调用更新函数。

例如:

bool timer_event_should_call_update_controls = false;

private void lvwMyListView_SelectedIndexChanged(object sender, EventArgs e) {

  timer_event_should_call_update_controls = true;
}

private void UpdateControlsTimer_Tick(object sender, EventArgs e) {

  if (timer_event_should_call_update_controls) {
    timer_event_should_call_update_controls = false;

    update_controls();
  }
}

如果您只是将信息用于显示目的,例如更新状态栏以显示“X out of Y selected”,则此方法可以正常工作。

于 2009-07-07T02:06:32.610 回答
1

标志适用于 Windows 窗体/Web 窗体/移动窗体的 OnLoad 事件。在单选Listview中,不是多选,下面的代码实现起来很简单,并且可以防止多次触发事件。

当 ListView 取消选择第一个项目时,第二个项目是您需要的,并且集合应该只包含一个项目。

在移动应用程序中使用了以下相同的内容,因此某些集合名称可能与使用紧凑框架时有所不同,但适用相同的原则。

注意:确保 OnLoad 并填充您设置要选择的第一个项目的列表视图。

// ################ CODE STARTS HERE ################
//Flag  to create at the form level
System.Boolean lsvLoadFlag = true;

//Make sure to set the flag to true at the begin of the form load and after
private void frmMain_Load(object sender, EventArgs e)
{
    //Prevent the listview from firing crazy in a single click NOT multislect environment
    lsvLoadFlag = true;

    //DO SOME CODE....

    //Enable the listview to process events
    lsvLoadFlag = false;
}

//Populate First then this line of code
lsvMain.Items[0].Selected = true;

//SelectedIndexChanged Event
 private void lsvMain_SelectedIndexChanged(object sender, EventArgs e)
{
    ListViewItem lvi = null;

    if (!lsvLoadFlag)
    {
        if (this.lsvMain.SelectedIndices != null)
        {
            if (this.lsvMain.SelectedIndices.Count == 1)
            {
                lvi = this.lsvMain.Items[this.lsvMain.SelectedIndices[0]];
            }
        }
    }
}
################ CODE END HERE    ################

理想情况下,应将此代码放入 UserControl 中,以便在单个选择 ListView 中轻松重用和分发。这段代码在多选中用处不大,因为该事件按其应有的行为工作。

我希望这会有所帮助。

亲切的问候,

Anthony N. Urwin http://www.manatix.com

于 2010-09-06T11:53:58.740 回答
1

您可以使用async& await

private bool waitForUpdateControls = false;

private async void listView_SelectedIndexChanged(object sender, EventArgs e)
{
    // To avoid thousands of needless ListView.SelectedIndexChanged events.

    if (waitForUpdateControls)
    {
        return;
    }

    waitForUpdateControls = true;

    await Task.Delay(100);

    waitForUpdateControls = false;

    UpdateControls();

    return;
}
于 2017-02-21T18:44:54.853 回答
0

我会尝试将回发绑定到一个按钮,以允许用户提交他们的更改并取消挂钩事件处理程序。

于 2008-09-17T19:47:53.310 回答
0

我昨天只是想解决这个问题。我不知道您所说的“驻留”计时器究竟是什么意思,但我尝试实现我自己的版本,等待所有更改完成。不幸的是,我能想到的唯一方法是在一个单独的线程中,事实证明,当您创建一个单独的线程时,您的 UI 元素在该线程中是不可访问的。.NET 引发异常,说明 UI 元素只能在创建元素的线程中访问!因此,我找到了一种方法来优化我对 SelectedIndexChanged 的​​响应,并使其足够快到可以忍受的地方——尽管它不是一个可扩展的解决方案。让我们希望有人有一个聪明的主意来在一个线程中解决这个问题。

于 2008-09-17T19:48:10.187 回答
0

也许这可以帮助您在不使用计时器的情况下完成所需的工作:

http://www.dotjem.com/archive/2009/06/19/20.aspx

我不喜欢计时器等的用户。正如我在帖子中所说的那样......

希望能帮助到你...

哦,我忘了说,它是 .NET 3.5,我正在使用 linq 中的一些功能来完成“选择更改评估”,如果你可以称之为 oO..

无论如何,如果您使用的是旧版本,则必须使用更多代码来完成此评估... >.<...

于 2009-06-19T16:51:41.050 回答
0

如果列表视图有几百或几千个项目,我建议虚拟化它。

于 2009-07-07T02:14:11.993 回答
0

梅隆 >>>

我们的目标是永远不会处理数百个项目以上的列表,但是......我已经测试了 10.000 个项目的整体用户体验,并且一次选择了 1000-5000 个项目(以及两个 Selected 中的 1000-3000 个项目的变化)并取消选择)...

计算的总持续时间从未超过 0.1 秒,一些最高测量值为 0.04 秒,我发现这么多项目完全可以接受。

在 10.000 个项目中,仅初始化列表需要 10 多秒,所以在这一点上,我会认为其他事情已经开始发挥作用,正如 Joe Chung 所指出的虚拟化。

也就是说,应该清楚的是,代码并不是计算选择差异的最佳解决方案,如果需要,这可以通过各种方式进行很多改进,我专注于用代码理解概念而不是比性能。

但是,如果您遇到性能下降的问题,我对以下一些非常感兴趣:

  • 列表中有多少项?
  • 一次选择/取消选择多少个元素?
  • 事件筹集大约需要多长时间?
  • 硬件平台?
  • 更多关于使用案例?
  • 你能想到的其他相关信息?

否则,帮助改进解决方案并不容易。

于 2009-07-07T08:27:50.520 回答
0

保留ListView所有旧控件。

结交DataGridView你的朋友,一切都会好起来的:)

于 2009-07-07T08:32:09.060 回答
0

Raymond Chen有一篇博文(可能)解释了为什么会有数千个变更事件,而不仅仅是一个:

当已经有一个非常好的 LVN_ITEMCHANGED 通知时,为什么还有一个 LVN_ODSTATECHANGED 通知?

...通知告诉您指定范围内所有项目的状态已更改
。这是为 range 中的所有项目LVN_ODSTATECHANGED发送个人的简写。如果您有一个包含 500,000 个项目的 ownerdata 列表视图,并且有人选择了全部,您会很高兴收到一个 通知,而 不是 50 万个单独的小 通知。LVN_ITEMCHANGED[iFrom..iTo]LVN_ODSTATECHANGEDiFrom=0iTo=499999LVN_ITEMCHANGED

我说可能解释了原因,因为不能保证 .NET 列表视图是 Listview 公共控件的包装器——这是一个可以随时更改的实现细节(尽管几乎可以肯定永远不会更改)。

提示的解决方案是在虚拟模式下使用 .NET 列表视图,使控件更难使用一个数量级。

于 2010-10-28T18:10:59.370 回答
0

我可能有更好的解决方案。

我的情况:

  • 单选列表视图(而不是多选)
  • 我想避免在触发取消选择先前选择的项目时处理该事件。

我的解决方案:

  • 记录用户在 MouseDown 上单击的项目
  • 如果此项不为空且 SelectedIndexes.Count == 0,则忽略 SelectedIndexChanged 事件

代码:

ListViewItem ItemOnMouseDown = null;
private void lvTransactions_MouseDown(object sender, MouseEventArgs e)
{
    ItemOnMouseDown = lvTransactions.GetItemAt(e.X, e.Y);
}
private void lvTransactions_SelectedIndexChanged(object sender, EventArgs e)
{
    if (ItemOnMouseDown != null && lvTransactions.SelectedIndices.Count == 0)
        return;

    SelectedIndexDidReallyChange();

}
于 2011-08-10T03:49:02.787 回答