如何避免成千上万不必要的列表视图。SelectedIndexChanged的活动?
-
01-07-2019 - |
题
如果一个用户选择的所有项目中的一个。网2.0列表视图,列表视图将火 SelectedIndexChanged 事件对每一个项目,而不是射击事件表明, 选择 已经改变。
如果用户然后点击选择只有一个项目的列表,列表视图将火 SelectedIndexChanged 事件 每 项目得到未被选中的,然后一个 SelectedIndexChanged 事件为单一的新选择的项目,而不是射击事件表明,选择已改变。
如果你有的代码 SelectedIndexChanged 事件处理程序,该程序将成为相当响应的时候你开始有几百/千个列表中的项目。
我已经想过 停留的计时器, 等等。
但没有任何人有一个很好的解决方案,以避免成千上万不必要的列表视图。SelectedIndexChange 事件,当真的 一个事件 会做什么?
解决方案
好的解决方案从伊恩。我把那,使它成为一个可重复使用的类,确保处置计时器正常。我也降低间隔时间,获得一个更加反应灵敏的应用程序。这种控制还doublebuffers减少闪烁。
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());
}
}
不要让我知道如果这可以改进。
其他提示
这是停留定时器的解决方案我用现在(驻留只是意味着"等等一点点").这个代码可能遭受竞争条件,也许是一个空引用的例外。
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
}
计时器是最佳的总体解决方案。
一个问题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 Y选择"。
一个标志,作为加载事件的windows窗/网络的形式/移动的形式。在单个选择列表视图,不多的选择,以下代码是简单的实施,并防止多次射击的事件。
作为该列表视图de-选择的第一个项目,第二个项目就是你需要和收集应该只包含一个项目。
同样,下文中使用移动应用程序,因此一些集合名称可能不同,因为它是使用小型框架,但是同一原则适用。
注:确保加载和填充的列表视图设置的第一个项目被选中。
// ################ 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 ################
理想的是,该代码应该放入一个用户控件,便于重新使用和分销在一个单一的选择列表视图。这种代码不会有多大用在多选择,作为事件作品的,因为它应该为这种行为。
我希望这有所帮助。
亲切的问候,
安东尼-N.格雷格*厄温 http://www.manatix.com
老问题,我知道,但这似乎仍然是一个问题。
这里是我的解决方案不能使用定时器。
它等待MouseUp或候,把事件的发射前的SelectionChanged事件。如果你改变选择通过程序,那么这将不会的工作,该事件就不会着火,但你可以很容易地加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);
}
}
你可以使用 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;
}
我会尝试将该回发一个按钮以允许用户提出它们的改变和解开事件的处理程序。
我只是试图解决这个问题,昨天。我不知道到底是什么你的意思是"居住"定时器,但我试图执行我自己版本的等待,直到所有的变化。不幸的是我唯一能想到做这是在一个单独的线和事实证明,在创建一个独立的螺纹,你UI元素是无法访问的在线。.NET抛出的一个例外,说明该用户界面元素只可在线程的元素是创建了!因此,我找到了一种方法来优化我的响应SelectedIndexChanged,并使它足够快的速度它是可以忍受-它不是一个可扩展的解决方案。让我们希望有人有一个聪明的主意来解决这个问题在一个单一的线。
也许这可以帮助你完成你需要什么,而不使用定时器:
http://www.dotjem.com/archive/2009/06/19/20.aspx
我不喜欢在用户的计时器等。因为我还在后...
希望它能帮助...
哦,我忘了说,它的。净3.5和我使用一些特点在皇宫来完成"选择的变化的评价"如果你可以叫它,o。O...
不管怎么说,如果你是在一个旧版本,这种评价必须做的更多一点的代码...>.<...
我建议虚拟化您的名单来看,如果它有几百或几千项。
Maylon>>>
目的是从未来的工作与清单上的几百项目,但是...我已经测试过的整体用户的经验有10,000个项目,并选择的1000-5000个项目一次(与变化为1000-3000的项目在两种选择和取消选择的)...
整个持续时间计算从来没有超过0.1秒,最高的一些测量的0.04秒,我发现,完全可以接受与这许多项目。
并在10,000个项目,只需初始化列出需要超过10秒钟,因此在这一点上,我会想其他的事情已在发挥,如虚拟化为乔Chung指出。
这就是说,它应该是清楚的,该代码不是最佳解决方案在于它是如何计算出的差异在选择如果需要,这可以改善很多,并以各种方式,我侧重于理解的概念与代码,而不是业绩。
但是,如果你遇到的性能下降,我非常感兴趣的一些如下:
- 有多少中的项目清单?
- 有多少选择/取消选择的因素在一段时间?
- 多长时间大致采取的活动,以提高?
- 硬件平台?
- 更多有关情况下使用?
- 其他相关信息,你能想到吗?
否则这是不容易的,以帮助改善解决方案。
离开 ListView
和所有老的控制。
让 DataGridView
你的朋友,一切都会好起来:)
雷蒙德*陈 有个博客的员额(可能)解释了 为什么 有成千上万的改变事件, ,而不是只有一个:
为什么会有一个LVN_ODSTATECHANGED通知时,有的已经完全好LVN_ITEMCHANGED通知?
...
的LVN_ODSTATECHANGED
的通知 告诉你们,国家的所有项目 在规定的范围已经改变。这是一个简写为发送一 个人LVN_ITEMCHANGED
对所有 项目的范围[iFrom..iTo]
.如果 你有一个ownerdata名单视图 500,000项目和别人不一 选择-所有,你会很高兴你 得到一个单一的LVN_ODSTATECHANGED
的通知iFrom=0
和iTo=499999
而不是一百万的一半, 各个小小的LVN_ITEMCHANGED
通知。
我说 大概 解释了为什么,因为没有保证。净清单视为一个包围绕共同的列表视图控制-这是实施细节,是免费的,在任何时候更改(虽然几乎可以肯定永远不会)。
所暗示的解决办法是使用。净列表视图在虚拟模式,使得控制一个数量级更加难以使用。
我可能有一个更好的解决方案。
我的情况:
- 单选择名单视图(而不是多选择)
- 我想要避免处理事件时,它激发进取消先前选定的项目。
我的解决方案:
- 记录项目的用户击MouseDown
- 忽略SelectedIndexChanged事件,如果这个项目的不是空和SelectedIndexes.Count==0
代码:
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();
}