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

11

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

  AddHandler Button.Click, AddressOf myButton_Click

我发现我的代码在Button_Click事件的其他事件处理程序之后才执行。有没有办法将我的事件处理程序插入到其他事件的前面,以便它首先执行?

我将此问题标记为C#和VB,请随意使用任何一种语言提出建议。
谢谢!

5个回答

13

不太容易。

话虽如此,不要这样做。你的代码不应该关心它被调用的顺序——它只应该关心所涉及的按钮是否被点击。所有的处理程序,包括你的处理程序,都将被执行。如果顺序很重要,你应该重新考虑你的设计,并使用其他机制来控制它。


9
单个事件处理程序的执行顺序无法通过内置事件的基本行为进行控制。MulticastDelegates是处理程序的“集合”,它们只逐个获取处理程序。请记住,这是大多数开发人员期望的工作方式,允许依赖于顺序的事件处理程序可能会很危险。事件处理程序通常不应该知道彼此,因为如果它们依赖于在另一个处理程序之前或之后执行,它们首先必须知道另一个处理程序的存在(违反信息隐藏和其他几个设计原则),其次,如果顺序更改,则行为将被破坏。
如果您理解所有这些,并仍然想控制事件处理程序的执行顺序,则以下内容将使您接近目标。
1. 创建事件处理程序类型的有序委托集合MyHandlers。这将是实际事件的MulticastDelegate实现的代理。 2. 创建一个“主”处理程序方法,实际上将附加到内置事件,并将遍历MyHandlers并调用每个处理程序。 3. 定义一些手段向列表中添加和删除处理程序。某些操作可以通过自定义事件“属性”来完成,但这只定义了添加和删除行为,而没有定义插入行为。
代码可能如下所示:
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 类似,只是它具有额外的控制可插入处理程序的功能。整个代码的美妙之处在于,消费者无需将其视为任何其他类型的事件而使用它,只需要像普通的事件一样使用即可。

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

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()"

希望我的回答有所帮助。


0
如果您在这里搜索添加您的回调函数以用于事件处理程序,但是您无法更改(因为它位于您正在使用的组件内部且无法更改),那么这就是方法:
辅助方法:(如果需要,请更改类型ExecutedRoutedEventHandler为您的EventHandler类型)
/// <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);
    }
}

使用示例:

//binding.Executed += BeforeOperationExecute;

// same as this ^^^^^^, but the callback is added as first to invocation list
PrependExecutedRoutedCallbackTo(
    binding,
    nameof(CommandBinding.Executed),
    (b, c) => { b.Executed += c; },
    (b, c) => { b.Executed -= c; },
    BeforeOperationExecute
);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接