区分用户交互引发的事件和我自己的代码

在以下情况下,从combobox中在我的应用程序中触发SelectedIndexChanged事件:

  1. 用户在combobox中选择不同的项目,或者在以下情况下:
  2. 我自己的代码更新了combobox的SelectedItem以反映combobox现在显示不同对象的属性。

我对case 1的SelectedIndexChanged事件感兴趣,这样我就可以更新当前对象的属性。 但是在第2种情况下,我不希望事件触发,因为对象的属性没有改变。

一个例子可能有帮助。 让我们考虑一下,我有一个包含人员列表的列表框,我有一个combobox,表示列表中当前所选人员的国籍。 如果Fred目前在列表中被选中,则可能发生案例1,并且我使用combobox将他的国籍从英语更改为威尔士语。 如果我在列表中选择苏格兰人Bob,则可能发生案例2。 在这里,我的列表更新事件处理程序代码看到Bob现在被选中,并更新combobox,以便苏格兰语现在是所选项目。 这会导致combobox的SelectedIndexChanged事件被触发以将Bob的国籍设置为苏格兰语,即使它已经是苏格兰语。

如何在不导致SelectedIndexChanged事件触发的情况下更新我的combobox的SelectedItem属性? 一种方法是取消注册事件处理程序,设置SelectedItem ,然后重新注册事件处理程序,但这似乎很乏味且容易出错。 肯定有更好的办法。

我创建了一个名为SuspendLatch的类。 欢迎提供更好名称的优惠,但它可以满足您的需求,您可以像这样使用它:

 void Method() { using (suspendLatch.GetToken()) { // Update selected index etc } } void listbox1_SelectedIndexChanged(object sender, EventArgs e) { if (suspendLatch.HasOutstandingTokens) { return; } // Do some work } 

它不漂亮,但它确实有效,与注销事件或布尔标志不同,它支持嵌套操作,有点像TransactionScope。 你继续从锁存器获取令牌,只有当最后一个令牌被处理掉时, HasOutstandingTokens返回false。 很好,很安全。 虽然不是线程安全的……

这是SuspendLatch的代码:

 public class SuspendLatch { private IDictionary tokens = new Dictionary(); public SuspendLatchToken GetToken() { SuspendLatchToken token = new SuspendLatchToken(this); tokens.Add(token.Key, token); return token; } public bool HasOutstandingTokens { get { return tokens.Count > 0; } } public void CancelToken(SuspendLatchToken token) { tokens.Remove(token.Key); } public class SuspendLatchToken : IDisposable { private bool disposed = false; private Guid key = Guid.NewGuid(); private SuspendLatch parent; internal SuspendLatchToken(SuspendLatch parent) { this.parent = parent; } public Guid Key { get { return this.key; } } public override bool Equals(object obj) { SuspendLatchToken other = obj as SuspendLatchToken; if (other != null) { return Key.Equals(other.Key); } else { return false; } } public override int GetHashCode() { return Key.GetHashCode(); } public override string ToString() { return Key.ToString(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Dispose managed resources. parent.CancelToken(this); } // There are no unmanaged resources to release, but // if we add them, they need to be released here. } disposed = true; // If it is available, make the call to the // base class's Dispose(Boolean) method //base.Dispose(disposing); } } } 

我认为最好的方法是使用标志变量:

 bool updatingCheckbox = false; void updateCheckBox() { updatingCheckBox = true; checkbox.Checked = true; updatingCheckBox = false; } void checkbox_CheckedChanged( object sender, EventArgs e ) { if (!updatingCheckBox) PerformActions() } 

[编辑:只发布代码并不是很清楚]

在这种情况下,当通过updateCheckBox()更改复选框时,事件处理程序将不会执行其正常操作。

我总是使用布尔标志变量来防止不需要的事件处理程序。 TaskVision示例应用程序教我如何执行此操作。

您的所有事件的事件处理程序代码如下所示:

 private bool lockEvents; protected void MyEventHandler(object sender, EventArgs e) { if (this.lockEvents) { return; } this.lockEvents = true; //Handle your event... this.lockEvents = false; } 

我让事件开火了。 但是,我在更改索引之前设置了一个标志,然后将其翻转。 在事件处理程序中,我检查是否设置了标志并退出处理程序(如果是)。

我认为你的重点应放在对象而不是发生的事件上。

比如说你有这个事件

 void combobox_Changed( object sender, EventArgs e ) { PerformActions() } 

和PerformActions做了一些事情

 void PerformActions() { (listBox.SelectedItem as IPerson).Nationality = (comboBox.SelectedItem as INationality) } 

然后在Person里面,你会期望看到一些效果

 class Person: IPerson { INationality Nationality { get { return m_nationality; } set { if (m_nationality <> value) { m_nationality = value; this.IsDirty = true; } } } } 

这里的要点是,让对象跟踪自身发生的事情,而不是UI。 这还可以让您跟踪对象上的脏标记跟踪,这对以后的持久性非常有用。

这也可以保持您的UI清洁,并防止它出现很可能容易出错的奇怪事件注册代码。

我终于找到了一个解决方案,以避免被激活的事件发生太多次。

我使用了一个计数器,我只挂钩/解开我想要在不需要时屏蔽的事件,以及何时再次需要它。

下面的示例显示了如何隐藏数据网格的CellValueChanged事件。

 EventMask valueChangedEventMask; // In the class constructor valueChangedEventMask = new EventMask( () => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); }, () => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); } ); // Use push to hide the event and pop to make it available again. The operation can be nested or be used in the event itself. void changeCellOperation() { valueChangedEventMask.Push(); ... cell.Value = myNewCellValue ... valueChangedEventMask.Pop(); } // The class public class EventMask { Action hook; Action unHook; int count = 0; public EventMask(Action hook, Action unHook) { this.hook = hook; this.unHook = unHook; } public void Push() { count++; if (count == 1) unHook(); } public void Pop() { count--; if (count == 0) hook(); } }