3

我有两个事件处理程序连接到 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

}

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

4

6 回答 6

6

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

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

于 2012-06-14T10:57:42.553 回答
2

为什么不直接用一个事件处理程序替换它们?像这样的东西:

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);
于 2012-06-14T11:02:09.700 回答
2

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

但是,要将其附加到普通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
    /* ... */
);

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

于 2012-06-14T11:34:36.607 回答
0

在 this.BtnCreate_Click 中添加以下条件,这是第二个事件

BtnCreate_Click(object sender, EventArgs e)  
{     
   if (!ConditionSatisfied)       //Prevent next handler in sequence being executed 
   {
     // your implementation goes here
   }   
}  
于 2012-06-14T10:59:59.527 回答
0

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

于 2012-06-14T11:03:54.690 回答
0

正在寻找相同问题的解决方案,但没有运气。所以只好自己解决。可取消事件参数的基类

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

接下来定义 EventHandler 的扩展方法,注意 Invocation List 订阅者以倒序调用(在我的例子中,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();
            }
        };
于 2015-06-13T00:28:23.260 回答