If a user select all items in a .NET 2.0 ListView, the ListView will fire a SelectedIndexChanged event for every item, rather than firing an event to indicate that the selection has changed.
If the user then clicks to select just one item in the list, the ListView will fire a SelectedIndexChanged event for every item that is getting unselected, and then an SelectedIndexChanged event for the single newly selected item, rather than firing an event to indicate that the selection has changed.
If you have code in the SelectedIndexChanged event handler, the program will become pretty unresponsive when you begin to have a few hundred/thousand items in the list.
I've thought about dwell timers, etc.
But does anyone have a good solution to avoid thousands of needless ListView.SelectedIndexChange events, when really one event will do?
Good solution from Ian. I took that and made it into a reusable class, making sure to dispose of the timer properly. I also reduced the interval to get a more responsive app. This control also doublebuffers to reduce flicker.
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());
}
}
Do let me know if this can be improved.
This is the dwell timer solution i'm using for now (dwell just means "wait for a little bit"). This code might suffer from a race condition, and perhaps a null reference exception.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With