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

时间:2021-03-05 23:12:01

The SelectedIndexChanged event gets fired in my application from a combo box when:

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

  1. the user chooses a different item in the combo box, or when:
  2. 用户在组合框中选择不同的项目,或者在以下情况下:

  3. my own code updates the combo box's SelectedItem to reflect that the combo box is now displaying properties for a different object.
  4. 我自己的代码更新了组合框的SelectedItem,以反映组合框现在显示不同对象的属性。

I am interested in the SelectedIndexChanged event for case 1, so that I can update the current object's properties. But in case 2, I do not want the event to fire, because the object's properties have not changed.

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

An example may help. Let's consider that I have a list box containing a list of people and I have a combo box representing the nationality of the currently selected person in the list. Case 1 could happen if Fred is currently selected in the list, and I use the combo box to change his nationality from English to Welsh. Case 2 could happen if I then select Bob, who is Scottish, in the list. Here, my list update event-handler code sees that Bob is now selected, and updates the combo box so that Scottish is now the selected item. This causes the combo box's SelectedIndexChanged event to be fired to set Bob's nationality to Scottish, even though it already is Scottish.

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

How can I update my combo box's SelectedItem property without causing the SelectedIndexChanged event to fire? One way would be to unregister the event handler, set SelectedItem, then re-register the event handler, but this seems tedious and error prone. There must be a better way.

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

6 个解决方案

#1


I created a class I called SuspendLatch. Offers on a better name are welcome, but it does what you need and you would use it like this:

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

void Method()
{
    using (suspendLatch.GetToken())
    {
        // Update selected index etc
    }
}

void listbox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (suspendLatch.HasOutstandingTokens)
    {
        return;
    }

    // Do some work
}

It's not pretty, but it does work, and unlike unregistering events or boolean flags, it supports nested operations a bit like TransactionScope. You keep taking tokens from the latch and it's only when the last token is disposed that the HasOutstandingTokens returns false. Nice and safe. Not threadsafe, though...

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

Here's the code for SuspendLatch:

这是SuspendLatch的代码:

public class SuspendLatch
{
    private IDictionary<Guid, SuspendLatchToken> tokens = new Dictionary<Guid, SuspendLatchToken>();

    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);
        }
    }
}

#2


I think the best way would be to use a flag variable:

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

bool updatingCheckbox = false;

void updateCheckBox()
{
    updatingCheckBox = true;

    checkbox.Checked = true;

    updatingCheckBox = false;
}

void checkbox_CheckedChanged( object sender, EventArgs e )
{
    if (!updatingCheckBox)
        PerformActions()
}

[Edit: Posting only the code is not really clear]

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

In this case, the event handler wouldn't perform its normal operations when the checkbox is changed through updateCheckBox().

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

#3


I have always used a boolean flag variable to protect against unwanted event handlers. The TaskVision sample application taught me how to do this.

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

Your event handler code for all of your events will look like this:

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

private bool lockEvents;

protected void MyEventHandler(object sender, EventArgs e)
{
    if (this.lockEvents)
    {
        return;
    }

    this.lockEvents = true;
    //Handle your event...
    this.lockEvents = false;
}

#4


I let the event fire. But, I set a flag before changing the index and flip it back after. In the event handler, I check if the flag is set and exit the handler if it is.

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

#5


I think your focus should be on the object and not on the event that's occuring.

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

Say for example you have the event

比如说你有这个事件

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

and PerformActions did something to the effect of

和PerformActions做了一些事情

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

then inside the Person you would expect to see something to the effect of

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

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

the point here is that you let the object keep track of what is happening to itself, not the UI. This also lets you keep track of dirty flag tracking on your objects, which could be useful for persistence later on.

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

This also keeps your UI clean and keeps it from getting odd event registration code that will most likely be error prone.

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

#6


I have finally found a solution to avoid the uncessary event from being fired too many time.

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

I use a counter and I only hook/unhook the events I want to mask once when it is not needed, and when it is needed again.

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

The example below shows how I hide the CellValueChanged event of a datagrid.

下面的示例显示了如何隐藏数据网格的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();
    }
}

#1


I created a class I called SuspendLatch. Offers on a better name are welcome, but it does what you need and you would use it like this:

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

void Method()
{
    using (suspendLatch.GetToken())
    {
        // Update selected index etc
    }
}

void listbox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (suspendLatch.HasOutstandingTokens)
    {
        return;
    }

    // Do some work
}

It's not pretty, but it does work, and unlike unregistering events or boolean flags, it supports nested operations a bit like TransactionScope. You keep taking tokens from the latch and it's only when the last token is disposed that the HasOutstandingTokens returns false. Nice and safe. Not threadsafe, though...

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

Here's the code for SuspendLatch:

这是SuspendLatch的代码:

public class SuspendLatch
{
    private IDictionary<Guid, SuspendLatchToken> tokens = new Dictionary<Guid, SuspendLatchToken>();

    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);
        }
    }
}

#2


I think the best way would be to use a flag variable:

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

bool updatingCheckbox = false;

void updateCheckBox()
{
    updatingCheckBox = true;

    checkbox.Checked = true;

    updatingCheckBox = false;
}

void checkbox_CheckedChanged( object sender, EventArgs e )
{
    if (!updatingCheckBox)
        PerformActions()
}

[Edit: Posting only the code is not really clear]

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

In this case, the event handler wouldn't perform its normal operations when the checkbox is changed through updateCheckBox().

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

#3


I have always used a boolean flag variable to protect against unwanted event handlers. The TaskVision sample application taught me how to do this.

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

Your event handler code for all of your events will look like this:

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

private bool lockEvents;

protected void MyEventHandler(object sender, EventArgs e)
{
    if (this.lockEvents)
    {
        return;
    }

    this.lockEvents = true;
    //Handle your event...
    this.lockEvents = false;
}

#4


I let the event fire. But, I set a flag before changing the index and flip it back after. In the event handler, I check if the flag is set and exit the handler if it is.

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

#5


I think your focus should be on the object and not on the event that's occuring.

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

Say for example you have the event

比如说你有这个事件

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

and PerformActions did something to the effect of

和PerformActions做了一些事情

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

then inside the Person you would expect to see something to the effect of

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

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

the point here is that you let the object keep track of what is happening to itself, not the UI. This also lets you keep track of dirty flag tracking on your objects, which could be useful for persistence later on.

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

This also keeps your UI clean and keeps it from getting odd event registration code that will most likely be error prone.

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

#6


I have finally found a solution to avoid the uncessary event from being fired too many time.

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

I use a counter and I only hook/unhook the events I want to mask once when it is not needed, and when it is needed again.

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

The example below shows how I hide the CellValueChanged event of a datagrid.

下面的示例显示了如何隐藏数据网格的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();
    }
}