防止调用下一个事件处理程序

本文关键字:事件处理 程序 下一个 调用 | 更新日期: 2023-09-27 18:37:14

我有两个事件处理程序连接到Windows窗体中的按钮单击,如下所示:

this.BtnCreate.Click += new System.EventHandler(new RdlcCreator().FirstHandler);
this.BtnCreate.Click += new System.EventHandler(this.BtnCreate_Click);

两者都被正确调用。

但是,是否可以在FirstHandler()内防止BtnCreate_Click()被执行?像这样:

void FirstHandler(object sender, EventArgs e)
{
   if (ConditionSatisfied)
     //Prevent next handler in sequence being executed
}

我知道我可以取消订阅事件,但这可以通过编程方式完成(从方法中)吗?

防止调用下一个事件处理程序

据我所知,没有解决方案。这是因为无法保证事件发生时调用事件处理程序的顺序。

因此,您不应该以任何方式依赖他们的订单。

为什么不将它们替换为一个事件处理程序?像这样:

var rdlc = new RdlcCreator();
this.BtnCreate.Click += (sender, e) => {
    rdlc.FirstHandler(sender, e);
    if (!rdlc.HasHandledStuff) { // <-- You would need some kind of flag 
        this.BtnCreate_Click(sender, e);
    }
};

这样,您还可以保证处理程序的顺序。或者,使用上面的实现,但更改 FirstHandler 的签名以返回指示条件的布尔值(因为在这种情况下,它实际上不再需要具有事件的签名):

    if (!rdlc.FirstHandler(sender, e)) { 
        this.BtnCreate_Click(sender, e);
    }

编辑:或者,您只需将第二个处理程序传递给FirstHandler。
将 FirstHandler 的签名更改为:

void FirstHandler(object sender, EventArgs e, EventHandler nextHandler) {
    if (ConditionSatisfied) {
        // do stuff
    }
    else if (nextHandler != null) {
        nextHandler(sender, e);
    }
}

然后:

this.BtnCreate.Click += 
    (s, e) => new RdlcCreator().Firsthandler(s, e, this.BtnCreate_Click);

System.ComponentModel命名空间包含一个用于此目的的CancelEventHandler委托。它提供的参数之一是包含布尔Cancel属性的CancelEventArgs实例,该属性可以设置为任何处理程序,以指示应停止执行调用列表。

但是,要将其附加到普通EventHandler委托,您需要创建自己的包装器,如下所示:

public static class CancellableEventChain
{
    public static EventHandler CreateFrom(params CancelEventHandler[] chain)
    {
        return (sender, dummy) =>
        {
            var args = new CancelEventArgs(false);
            foreach (var handler in chain)
            {
                handler(sender, args);
                if (args.Cancel)
                    break;
            }
        };
    }
}

对于您的示例,您将像这样使用它:

this.BtnCreate.Click += CancellableEventChain.CreateFrom(
    new RdlcCreator().FirstHandler,
    this.BtnCreate_Click
    /* ... */
);

当然,如果您稍后需要取消订阅(分离)它,则需要在字段中捕获创建的链处理程序。

在此添加以下条件。BtnCreate_Click这是第二个事件

BtnCreate_Click(object sender, EventArgs e)  
{     
   if (!ConditionSatisfied)       //Prevent next handler in sequence being executed 
   {
     // your implementation goes here
   }   
}  

我建议你创建一个类包装器。因此,您可以在那里存储某种事件标志组(例如 16 位整数)和一些设置或取消设置单个位的方法(其中每个表示调用或不特定EventHandler)。您可以轻松地在类中存储Eventhandlers甚至Actions的任何计数,并按所需的任何顺序进行调用。

找到了同一问题的解决方案,但没有运气。所以必须自己解决。可取消事件参数的基类

public class CancelableEventArgs
{
    public bool Cancelled { get; set; }
    public void CancelFutherProcessing()
    {
        Cancelled = true;
    }
}

接下来定义 EventHandler 的扩展方法,请注意,调用列表订阅者以向后顺序调用(在我的例子中,UI 元素在添加到组件时会订阅事件,因此稍后呈现哪个元素具有最明显的优先级)

public static class CommonExtensions
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void SafeInvokeWithCancel<T>(this EventHandler<T> handler, object sender, T args) where T : CancelableEventArgs
    {
        if (handler != null)
        {
            foreach (var d in handler.GetInvocationList().Reverse())
            {
                d.DynamicInvoke(sender, args);
                if (args.Cancelled)
                {
                    break;
                }
            }
        }
    }

这是用法

public class ChessboardEventArgs : CancelableEventArgs
{
    public Vector2 Position { get; set; }
}

因此,如果 UI 元素对事件有一些行为,它可以进一步处理

        game.OnMouseLeftButtonDown += (sender, a) =>
        {
            var xy = GetChessboardPositionByScreenPosition(a.XY);
            if (IsInside(xy))
            {
                var args = new ChessboardEventArgs { Position = xy };
                OnMouseDown.SafeInvokeWithCancel(this, args);
                a.CancelFutherProcessing();
            }
        };