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

时间:2009-04-08 13:05:07

标签: c# winforms events combobox

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

  1. 用户选择不同的 组合框中的项目,或者:
  2. 我自己的代码更新了组合 方框SelectedItem来反映这一点 组合框现在正在显示 不同对象的属性。
  3. 我对案例1的SelectedIndexChanged事件感兴趣,以便我可以更新当前对象的属性。但是在第2种情况下,我不希望触发事件,因为对象的属性没有改变。

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

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

6 个答案:

答案 0 :(得分:7)

我创建了一个名为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<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);
        }
    }
}

答案 1 :(得分:4)

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

bool updatingCheckbox = false;

void updateCheckBox()
{
    updatingCheckBox = true;

    checkbox.Checked = true;

    updatingCheckBox = false;
}

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

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

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

答案 2 :(得分:3)

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

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

private bool lockEvents;

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

比如说你有事件

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

和PerformActions做了一些事情

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

然后在人物内部你会看到一些影响

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

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

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

答案 5 :(得分:0)

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

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

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