WPF 路由命令(Command)

1.WPF 命令
四个概念,牢记概念哦!

  • 命令是要执行的操作。

  • 命令源是调用命令的对象。

  • 命令目标是在其上执行命令的对象。

  • 命令绑定是将命令逻辑映射到命令的对象。

2.命令源
WPF 中的命令源通常实现 ICommandSource 接口,例如 Button 实现了 ICommandSource,KeyGesture 接口。因此才有这种栗子:

    <Grid>
        <Button Command="{Binding CustomCommand}"></Button>
    </Grid>

因为 Button 实现了 ICommandSource 接口,所以我们才能使用 Command 属性来绑定我们自己的命令(如果你不理解xml里是什么意思,暂且不用计较,我细细道来)。

在了解 ICommandSource 之前,我们看一下 ICommand 接口,如下:

namespace System.Windows.Input
{
    public interface ICommand
    {
        // 能否执行状态 发生改变时被调用,一般少用。
        event EventHandler? CanExecuteChanged;

        // 如果该方法返回 true,则 Execute 将被调用,否则不调用。
        bool CanExecute(object? parameter);

        // 如果一个命令可以执行,则该方法将被调用。
        void Execute(object? parameter);
    }
}

在上述代码中,ICommand 接口定义了命令执行时的一些流程规范方法。

知道 ICommand 接口之后,我们可以接着看 ICommandSource 接口,如下:

namespace System.Windows.Input
{
    // 定义一些知道如何调用一个命令的对象。
    public interface ICommandSource
    {
        // 当命令源被调用时将执行这个命令。
        ICommand Command
        {
            get;
        }

        // 调用命令时将传递的参数。
        object CommandParameter
        {
            get;
        }

        // 正在其上执行命令的对象。
        // 值得注意的是,在 WPF 中,ICommandSource 上的 CommandTarget 属性仅在 Icommand 为 RoutedCommand 时使用,否则忽略命令目标。
        // 如果未设置 CommandTarget,则具有键盘焦点的元素将成为命令目标。
        IInputElement CommandTarget
        {
            get;
        }
    }
}

在 WPF 中,有这么一段代码
public class UIElement : Visual, IInputElement, IAnimatable
UIElement 实现了 IInputElement,而 UIElement 几乎是所有元素的基类,所以通常 System.Windows.Controls 下的控件都可以作为命令源中的目标。

为什么是几乎呢?因为有些元素可以只是继承自 Visual 的一个对象,并且可以将 Visual 派生的子元素添加到视觉树中。(如果这里不懂,跳过就行)

3.使用命令
如何将命令使用到我们的控件中呢?
WPF 框架已经帮我们写好了如何将 WPF 命令应用的元素中,UIElement 元素有以下定义:
UIElement.CommandBindings
CommandBindings 是一个 CommandBinding 类型的集合,它的作用是将命令与实现该命令的事件处理程序相关联。

CommandBinding 类型的事件如下:

namespace System.Windows.Input
{

    public class CommandBinding
    {
        // 需要执行的命令,最好 RoutedCommand 的派生类,否则 CommandTarget 是无效的。
        public ICommand Command;
 
        // 这两个是冒泡路由策略。
        public event CanExecuteRoutedEventHandler CanExecute;
        public event ExecutedRoutedEventHandler Executed;

        // 这两个是隧道路由策略。
        public event CanExecuteRoutedEventHandler PreviewCanExecute;
        public event ExecutedRoutedEventHandler PreviewExecuted;
    }
}

如果上面的你仔细看了 ICommand,那么对这其中的这几个事件,就很容易理解。

  1. 如何使用命令?
        /// <summary>
        ///     Event to execute a command
        /// </summary>
        public static readonly RoutedEvent ExecutedEvent =
                EventManager.RegisterRoutedEvent("Executed",
                                                 RoutingStrategy.Bubble,
                                                 typeof(ExecutedRoutedEventHandler),
                                                 typeof(CommandManager));

        /// <summary>
        ///     Event before a command is executed
        /// </summary>
        public static readonly RoutedEvent PreviewExecutedEvent =
               EventManager.RegisterRoutedEvent("PreviewExecuted",
                                                RoutingStrategy.Tunnel,
                                                typeof(ExecutedRoutedEventHandler),
                                                typeof(CommandManager));                      

命令其实只是对类 CommandManager 中的 ExecutedEventPreviewExecutedEvent 这两个路由事件进行了封装,所以我们可以使用 ComandManager, 并且像使用路由事件一样来使用命令。

上代码:

    // 自己定义一个命令。
    internal class MyRoutedCommands
    {
        // 显示一个对话框。
        public static readonly RoutedUICommand ShowMessageBox = new();
    }

    // 我们随便定义一个用户控件类,用于接收并执行命令。
    internal sealed class MyUserControl : UserControl
    {
        public MyUserControl()
        {
            // 这种方式也能响应命令。
            //this.CommandBindings.Add(new CommandBinding(
            //    MyRoutedCommands.ShowMessageBox, 
            //    Binding_Executed 
            //    ));

            // 像订阅路由事件一样去使用命令。
            CommandManager.AddPreviewCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_PreviewCanExecute));
            CommandManager.AddPreviewExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_PreviewExecuted));
            CommandManager.AddCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_CanExecute));
            CommandManager.AddExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_Executed));
        }

        private void Binding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"Excute Executed method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }

        private void Binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            MessageBox.Show($"Excute CanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }

        private void Binding_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"Excute PreviewExecuted method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }

        private void Binding_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            MessageBox.Show($"Excute PreviewCanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }
    }

    // 主窗口类,点击按钮之后,引发路由触发命令,此时将隧道(预览)路由到 _control 元素上,并执行响应的命令。
    internal class MainWindow : Window
    {
        private readonly MyUserControl _contol = new MyUserControl();

        public MainWindow()
        {
            Width = 400;
            Height = 647;
            WindowStartupLocation = WindowStartupLocation.CenterScreen;
            this.Loaded += MainWindow_Loaded;
        }
       
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var grid = new Grid();
            this.AddChild(grid);

            var myButton = new Button
            {
                Content = "Toggle Event",
                Width = 200,
                Height = 20
            };
            myButton.Click += MyButton_Click;

            grid.Children.Add(_contol);
            grid.Children.Add(myButton);
        }

        private void MyButton_Click(object sender, RoutedEventArgs e)
        {
            // 引发路由事件,因为 MainWindow 是上层元素,所以需要指定预览到具体的子元素,这里指定为 _contol。
            MyRoutedCommands.ShowMessageBox.CanExecute("Parameter", _contol);
            MyRoutedCommands.ShowMessageBox.Execute("Parameter", _contol);
        }
    }

方法执行顺序为:

  • Binding_PreviewCanExecute
  • Binding_CanExecute
  • Binding_PreviewExecuted
  • Binding_Executed

当然,我们也可以使用以下方法去接收并执行命令

    internal sealed class MyUserControl : UserControl
    {
        public MyUserControl()
        {
            // 这种方式也能响应命令。
            this.CommandBindings.Add(new CommandBinding(
                MyRoutedCommands.ShowMessageBox,
                Binding_Executed
                ));

            //// 像订阅路由事件一样去使用命令。
            //CommandManager.AddPreviewCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_PreviewCanExecute));
            //CommandManager.AddPreviewExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_PreviewExecuted));
            //CommandManager.AddCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_CanExecute));
            //CommandManager.AddExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_Executed));
        }

        private void Binding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"Excute Executed method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }

        private void Binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            MessageBox.Show($"Excute CanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }

        private void Binding_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show($"Excute PreviewExecuted method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }

        private void Binding_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            MessageBox.Show($"Excute PreviewCanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
        }
    }

这个栗子中,当我们在 MainWindow 中触发命令后,Binding_Executed 方法将会被执行。

  1. 忽略命令目标使用命令
    上述有提到过,在 WPF 中,ICommandSource 上的 CommandTarget 属性仅在 Icommand 为 RoutedCommand 时使用,否则忽略命令目标。如果未设置 CommandTarget,则具有键盘焦点的元素将成为命令目标,所以平时我们的 Button.Command 使用的命令,直接继承实现 ICommand 即可,因为点击按钮之后,按钮本身就获取到了焦点。
posted @   RafaelLxf  阅读(636)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示