在其他事件处理程序前添加自己的事件处理程序

时间:2010-09-24 17:13:49

标签: c# vb.net winforms event-handling

当我在VB中使用AddHandler将我自己的方法添加到Click事件时:

  AddHandler Button.Click, AddressOf myButton_Click

我看到我的代码最后执行 - 在Button_Click事件的其他事件处理程序之后。 有没有办法在其他事件的前面 中插入我的事件处理程序 ,以便先执行它?

我将此问题标记为C#以及VB,如果您有任何建议,请随意使用任何一种语言。
谢谢!

5 个答案:

答案 0 :(得分:13)

不容易。

话虽如此,不要这样做。您的代码不应该关心它的调用顺序 - 它应该只关心有问题的按钮被点击了。所有处理程序(包括您的处理程序)都将执行。如果订单很重要,您应该重新考虑您的设计,并使用其他一些机制来控制它。

答案 1 :(得分:7)

单个事件的处理程序的执行顺序无法通过内置事件本身的基本行为来控制。 MulticastDelegates是处理程序的“包”,他们只是一次抓取一个。请记住,这是大多数开发人员期望这样做的方式,允许依赖于订单的事件处理程序可能会很危险。事件处理程序通常不应该彼此了解,因为如果它们依赖于在另一个处理程序之前或之后执行,它们首先必须知道另一个处理程序的存在(违反信息隐藏和其他几个设计原则),其次,如果该顺序发生变化,行为将被破坏。

如果你理解了这一切,并且仍然希望控制事件处理程序的执行顺序,那么下面的内容将会让你关闭。

  1. 创建名为MyHandlers的事件处理程序类型的有序委托集合。这将是实际事件的MulticastDelegate实现的替代。
  2. 创建一个“主”处理程序方法,该方法实际上将附加到内置事件,并将遍历MyHandlers并调用每个方法。
  3. 定义一些从列表中添加和删除处理程序的方法。其中一些可以使用自定义事件“属性”来完成,但这将仅定义添加和删除行为,而不是插入。
  4. 代码可能如下所示:

    private List<EventHandler> MyHandlers = new List<EventHandler>();
    
    private void MasterClickHandler(object sender, EventArgs e)
    {
       foreach(var handler in MyHandlers)
          handler(sender, e); 
    }
    
    public event EventHandler MyControlButtonClick
    {
       add { MyHandlers.Add(value); }
       remove { MyHandlers.Remove(value); }
    }
    
    public void InsertButtonClickHandler(EventHandler handler)
    {
       MyHandlers.Insert(handler,0); //calling this to add a handler puts the handler up front
    }
    
    ...
    
    myForm.MyControl.Click += MasterClickHandler;
    

    请注意,您不再将MasterClickHandler以外的处理程序附加到实际事件中;你不能拥有自己的蛋糕,也不能吃它,无论是重写还是保持基本的事件行为。事件“属性”中也没有内置“插入”行为;你必须定义一个允许这个的方法。最后,你永远不应该直接引发事件MyControlButtonClick(虽然你的控件是唯一可以通过代码检查强制执行的控件)。

    现在,当您单击按钮时,按钮的内置Click事件将触发MasterEventHandler,它将按照它们附加到MyControlButtonClick的相同顺序执行MyHandlers中的委托(任何先插入的代码都会以相反的顺序执行)他们被插入)。如果您将此代码放在带有Button的自定义用户控件中,您甚至可以在控件Click上命名自定义事件,并且控件的外观和工作方式与它包含的Button非常相似,不同之处在于它可以对插入进行额外控制处理程序。整个事情的美妙之处在于,这个代码没有任何东西可以强迫消费者使用它作为普通的“香草事件”以外的任何东西。

答案 2 :(得分:4)

它更像是VB.NET的一个实现细节,它有另一种使用WithEvents和Handles关键字处理事件的方法。使用Handles的事件处理程序由表单构造函数中的自动生成的代码订阅。此代码将在您的任何代码之前运行,包括InitializeComponent或您的自定义AddHandler语句。因此将始终首先运行。

让您的代码保证首先运行。从Button派生自己的类并覆盖OnClick方法:

Public Class MyButton
    Inherits Button

    Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
        '' Do your stuff here
        ''....

        '' Other event handlers will run now:
        MyBase.OnClick(e)
    End Sub
End Class

答案 3 :(得分:2)

我遇到了类似的问题。

我有一个事件:关闭两个不同的对象听。我必须确保第一个对象的事件将在第二个对象之前被调用。

这是他的解决方案(在C#中):

public class Screen
{
    public event EventHandler Closed;
    public event EventHandler ScreenRemoved;


    protected virtual void OnClosed()
    {
        if (Closed != null)
        {
            Closed.Invoke(this, EventArgs.Empty);
        }

        OnScreenRemoved();
    }

    protected virtual void OnScreenRemoved()
    {
        if (ScreenRemoved != null)
        {
            ScreenRemoved.Invoke(this, EventArgs.Empty);
        }
    }
}

这种方式适用于我想要首先调用的所有事件:

m_Screen.Closed += Screen_Closed;

对于我想要最后调用的所有事件:

m_Screen.ScreenRemoved += new EventHandler(Screen_Closed);

*添加事件的方式都是相同的,有和没有“new EventHandler()”

希望我能提供帮助。

答案 4 :(得分:0)

如果您是来这里为事件处理程序添加您的回调的,则您无法更改(因为它位于您正在使用的组件内部并且不能更改),所以这是:

Helper方法:(如果需要,将EventHandler类型更改为git fetch类型)

ExecutedRoutedEventHandler

用法示例:

/// <summary>
/// Method, that can add delegate callback on the start of invocation list. So when the event is fired, this callback is called first.
/// </summary>
/// <param name="instance">instance of object with desired event</param>
/// <param name="eventName">name of the event on <see cref="instance"/></param>
/// <param name="addHandler">delegate for adding event callback</param>
/// <param name="removeHandler">delegate for removing event callback</param>
/// <param name="handler">desired callback, that will be called first</param>
private static void PrependExecutedRoutedCallbackTo<T>(
    T instance,
    string eventName,
    Action<T, ExecutedRoutedEventHandler> addHandler,
    Action<T, ExecutedRoutedEventHandler> removeHandler,
    ExecutedRoutedEventHandler handler
)
    where T : class
{
    var classType = typeof(T);

    // compiler automaticly creates backing field with event name - searching for it
    FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
    ExecutedRoutedEventHandler eventDelegate = (ExecutedRoutedEventHandler)eventField.GetValue(instance);

    // if the field is null, it means no callbacks are registered - handler can be easily added
    if (eventDelegate == null)
    {
        addHandler(instance, handler);
        return;
    }

    // Here is some magic - removing all callbacks, adding desired callback on the begining and adding callbacks back

    // getting current registered callbacks
    var delegates = eventDelegate.GetInvocationList().OfType<ExecutedRoutedEventHandler>().ToList();
    // removing all of them
    foreach (var del in delegates)
    {
        removeHandler(instance, del);
    }
    // adding my special callback
    addHandler(instance, handler);
    // returning back all original callbacks
    foreach (var del in delegates)
    {
        addHandler(instance, del);
    }
}