【问题标题】:Temporarily stop form events from either being raised or being handled?暂时停止引发或处理表单事件?
【发布时间】:2013-04-18 17:46:08
【问题描述】:

我在表单上有大量控件,并且在某个特定时间我想暂时停止处理我的所有事件。如果我不想处理某些事件,通常我会这样做:

private bool myOpRunning = false;

private void OpFunction()
{
    myOpRunning = true;
    // do stuff
    myOpRunning = false;
}

private void someHandler(object sender, EventArgs e)
{
    if (myOpRunning) return;
    // otherwise, do things
}

但是我有很多处理程序需要更新。只是好奇 .NET 是否有比更新每个处理程序方法更快的方法。

【问题讨论】:

  • 禁用您的控件,以便用户知道您没有响应。将其 Enabled 属性设置为 false。
  • 如果您必须处理过多的事件触发,我会首先考虑重新设计。代码中的错误位置发生了一些事情。你不应该这样。在您解决问题的真正原因之前,做这种管道应该只是一种临时措施。

标签: c# .net winforms events


【解决方案1】:

您必须创建自己的机制来执行此操作。不过还不错。考虑添加另一层抽象。例如,一个名为FilteredEventHandler 的简单类检查 myOpRunning 的状态并调用真正的事件处理程序,或者抑制事件。这个类看起来像这样:

public sealed class FilteredEventHandler
{
    private readonly Func<bool> supressEvent;
    private readonly EventHandler realEvent;

    public FilteredEventHandler(Func<bool> supressEvent, EventHandler eventToRaise)
    {
        this.supressEvent = supressEvent;
        this.realEvent = eventToRaise;
    }

    //Checks the "supress" flag and either call the real event handler, or skip it
    public void FakeEventHandler(object sender, EventArgs e)
    {
        if (!this.supressEvent())
        {
            this.realEvent(sender, e);
        }
    }
}

然后,当您连接事件时,请执行以下操作:

this.Control.WhateverEvent += new FilteredEventHandler(() => myOpRunning, RealEventHandler).FakeEventHandler;

WhateverEvent 被提升时,它将调用FilteredEventHandler.FakeEventHandler method。该方法将检查标志并调用或不调用真实的事件处理程序。这在逻辑上与您已经在做的几乎相同,但是检查 myOpRunning 标志的代码只在一个地方,而不是散布在您的代码中。

编辑以回答 cmets 中的问题:

现在,这个例子有点不完整。完全取消订阅事件有点困难,因为您丢失了对已连接的 FilteredEventHandler 的引用。例如,你不能这样做:

this.Control.WhateverEvent += new FilteredEventHandler(() => myOpRunning, RealEventHandler).FakeEventHandler;
//Some other stuff. . .
this.Control.WhateverEvent -= new FilteredEventHandler(() => myOpRunning, RealEventHandler).FakeEventHandler; //Not gonna work!

因为您连接了一个代表并取消了一个完全不同的代表!当然,这两个委托都是 FakeEventHandler 方法,但这是一个实例方法,它们属于两个完全不同的 FilteredEventHandler 对象。

不知何故,您需要获取对您构造的第一个 FilteredEventHandler 的引用才能解除挂钩。这样的事情会起作用,但它涉及跟踪一堆 FilteredEventHandler 对象,这可能并不比您尝试解决的原始问题更好:

FilteredEventHandler filter1 = new FilteredEventHandler(() => myOpRunning, RealEventHandler);
this.Control.WhateverEvent += filter1.FakeEventHandler;
//Code that does other stuff. . .
this.Control.WhateverEvent -= filter1.FakeEventHandler;

在这种情况下,我要做的是让 FilteredEventHandler.FakeEventHandler 方法将其“this”引用传递给 RealEventHandler。这涉及更改 RealEventHandler 的签名以采用另一个参数:

public void RealEventHandler(object sender, EventArgs e, FilteredEventHandler filter);

或将其更改为采用您创建的 EventArgs 子类,该子类包含对 FilteredEventHandler 的引用。这是更好的方法

public void RealEventHandler(object sender, FilteredEventArgs e);
//Also change the signature of the FilteredEventHandler constructor:
public FilteredEventHandler(Func<bool> supressEvent, EventHandler<FilteredEventArgs> eventToRaise)
{
  //. . .
}
//Finally, change the FakeEventHandler method to call the real event and pass a reference to itself
this.realEvent(sender, new FilteredEventArgs(e, this)); //Pass the original event args + a reference to this specific FilteredEventHandler

现在,被调用的 RealEventHandler 可以自行取消订阅,因为它引用了正确的 FilteredEventHandler 对象,该对象已传入其参数。

我最后的建议是不要这样做! Neolisk 将它钉在了 cmets 中。做这样复杂的事情表明设计存在问题。将来需要维护此代码的任何人(甚至您,令人惊讶!)都很难弄清楚所涉及的非标准管道。

通常当您订阅事件时,您只做一次就忘记了——尤其是在 GUI 程序中。

【讨论】:

  • 我唯一一次明确地取消挂钩事件是事件处理程序将执行可能再次引发相同事件的操作。 GUI 是我 90% 的时间处理事件的地方
  • 现在,如果我要像这样删除处理程序:myControl.SomeEvent -= new FilteredEventHandler(),是否可以在 FilteredEventHandler 的终结器中删除 RealEventHandler?我没有大量编写自己的事件的经验,所以我不知道这意味着什么,或者 EventHandlers 是否以与 .NET 中其他类型相同的方式完成(我假设它们是)
  • @AlexanderMiles 我已经更新了我的原始答案以解决这个特定问题。
  • 第二次您提到通过特殊处理程序过滤事件,我认为这是一个坏主意。我对提供信息的目的更加好奇,现在我知道我需要找到一种更好的方法来创建事件驱动的 GUI。
【解决方案2】:

你可以通过反射来做到这一点......

public static void UnregisterAllEvents(object objectWithEvents)
{
    Type theType = objectWithEvents.GetType();

    //Even though the events are public, the FieldInfo associated with them is private
    foreach (System.Reflection.FieldInfo field in theType.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance))
    {
        //eventInfo will be null if this is a normal field and not an event.
        System.Reflection.EventInfo eventInfo = theType.GetEvent(field.Name);
        if (eventInfo != null)
        {
            MulticastDelegate multicastDelegate = field.GetValue(objectWithEvents) as MulticastDelegate;
            if (multicastDelegate != null)
            {
                foreach (Delegate _delegate in multicastDelegate.GetInvocationList())
                {
                    eventInfo.RemoveEventHandler(objectWithEvents, _delegate);
                }
            }
        }
    }
}

【讨论】:

  • 这对我来说似乎不是很“暂时”。
【解决方案3】:

您可以禁用所有这些控件所在的容器。例如,如果您将它们放在GroupBoxPanel 中,只需使用:groupbox.Enabled = false;panel.Enabled = false;。您还可以禁用From1.Enabled = false; 表单并显示等待光标。您仍然可以将这些控件复制并粘贴到表单以外的容器中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    相关资源
    最近更新 更多