首页 > 解决方案 > CommandBinding 使用自定义 ICommand 实现时未调用已执行的事件处理程序

问题描述

我试图理解 RoutedUICommand 的工作原理。我曾在 Wpf 中使用过 MVVM,并试图了解 ICommand 实现实际上在 wpf 中有效。但是,我无法理解一些事情。

public class RelayCommand : ICommand
{
    private Action<Task> callBack;
    private bool runOnUi;
    private Action<object> whatToExecute;
    private Func<object, bool> whenToExecute;

    public RelayCommand(Func<object, bool> whenToExecute, Action<object> whatToExecute,
        bool runOnUi = true, Action<Task> callBack = null)
    {
        this.whenToExecute = whenToExecute;
        this.whatToExecute = whatToExecute;
        this.runOnUi = runOnUi;
        this.callBack = callBack;
    }

    /// <summary>
    /// This is an event from Interface
    /// </summary>
    public event EventHandler CanExecuteChanged;

    /// <summary>
    /// Decides whether the command can execute or not.
    /// </summary>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public bool CanExecute(object parameter)
    {
        if (this.whenToExecute != null)
        {
            try
            {
                return this.whenToExecute(parameter);
            }
            catch
            {
                return false;
            }
        }
        return true;
    }

    /// <summary>
    /// Called when CanExecute is true and command is fired.
    /// </summary>
    /// <param name="parameter"></param>
    public void Execute(object parameter)
    {
        if (this.whatToExecute != null)
        {
            if (this.runOnUi)
            {
                this.whatToExecute(parameter);
            }
            else
            {
                var parallelTask = Task.Run(() =>
                {
                    this.whatToExecute(parameter);
                });
                if (this.callBack != null)
                    parallelTask.ContinueWith(this.callBack);
            }
        }
    }
}

这是 ICommand 接口的自定义实现。我在标签的上下文菜单中添加了两个菜单项。首先是 ApplicationCommands.Close,其次是我的客户 RelayCommand 类型。

    this.Label1.ContextMenu = new ContextMenu();

    var menuItem1 = new MenuItem();//Source
    menuItem1.Command = ApplicationCommands.Close; //Command

    var commandBindingObject = new CommandBinding(menuItem1.Command);
    commandBindingObject.CanExecute += this.MenuItem1Close_CanExecute;
    commandBindingObject.Executed += this.MenuItem1Close_Executed;
    menuItem1.CommandBindings.Add(commandBindingObject);

    var menuItem2 = new MenuItem();//Source
    menuItem2.Header = "Custom Command";
    menuItem2.Command = new RelayCommand(o =>
    {
        return true;
    },
        o =>
        {
        }); //Command

    var commandBindingObject2 = new CommandBinding(menuItem2.Command);
    commandBindingObject2.CanExecute += this.MenuItem2Close_CanExecute;
    commandBindingObject2.Executed += this.MenuItem2Close_Executed;
    menuItem2.CommandBindings.Add(commandBindingObject2);

    this.Label1.ContextMenu.Items.Add(menuItem1);
    this.Label1.ContextMenu.Items.Add(menuItem2);

每当我单击第一个菜单项处理程序时,都会调用 menuitem1 处理程序,但不会调用 menuitem2。我只是想了解命令模式是如何在 wpf 中实现的,因此除此之外,任何指向此类的链接也会有很大帮助。

标签: c#wpf

解决方案


路由命令

ApplicationCommands.Close命令是一个RoutedUICommand. 路由命令引发遍历元素树直到找到命令绑定的路由事件PreviewExecutedExecuted如果找到一个,ExecutedRoutedEventHandler则调用相应的。如果menuItem1执行的处理程序是MenuItem1Close_Executed. 这同样适用于PreviewCanExecuteandCanExecute事件和MenuItem1Close_CanExecute处理程序 as CanExecuteRoutedEventHandler

中继命令

menuItem2使用RelayCommand. CanExecute这种类型的命令使用and的委托Execute,而不是遍历元素树来搜索命令绑定。

在您的情况下,以下几行是不必要的。

var commandBindingObject2 = new CommandBinding(menuItem2.Command);
commandBindingObject2.CanExecute += this.MenuItem2Close_CanExecute;
commandBindingObject2.Executed += this.MenuItem2Close_Executed;
menuItem2.CommandBindings.Add(commandBindingObject2);

whenToExecute而是为and传递方法或匿名方法whatToExecute

menuItem2.Command = new RelayCommand(CanExecute, Execute);

你不能通过MenuItem1Close_CanExecuteMenuItem1Close_Executed在这里,因为他们有不兼容的签名:

void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)
void ExecutedRoutedEventHandler(object sender, ExecutedRoutedEventArgs e)

CanExecute必须是Func<object, bool>并且Execute必须是Action<object>代表。

bool CanExecute(object obj)
void Execute(object obj)

推荐阅读