WPF新手村教程(六)— 新手村BOSS战前准备(命令)

WPF个人文档(六)—— 命令


命令最大的作用就是前后端解耦,它并不是事件的上位替代品!!!


一.概念

  • 我们先来看看命令这两个字,在汉语上的词意

    • 命令:一种权威性的指示,通常指上级对下级的口头或书面指示,要求其执行某项任务或采取某种行动
      命令往往具有明确的目标和要求,并带有一定的强制性
  • 然后我们再来看看WPF中命令的概念

    • 命令:代指一种明确的指令或者要求,用于向某个目标传递指定的操作或者行为
      • 因此,只从效果上来看,命令和事件的效果视乎一样,但是,命令 ≠ 事件
    • WPF中的命令是一种行为抽象机制,它将UI触发、执行逻辑和作用目标解耦,并通过CanExecute实现状态驱动的交互控制
      • 命令是对"行为"的对象化封装
      • 它将“触发者”、“执行逻辑”和“作用对象”解耦(前后端解耦),并提供“是否可执行”的状态控制机制(行为约束)
      • 命令(Command)= 把 "一个动作" 抽象成 一个对象
  • 在现实生活中,假设你在对一个AI发起了一个命令,那么我们的流程是

    • 编辑执行命令内容  ->  选择命令目标  ->  告诉AI执行什么操作  ->  行为约束
      
      # 其中涉及到
          命令发出者	
          命令接收者	
          命令内容
          命令执行者
          开始执行命令
          完成执行内容
          汇报执行结果
      
    • 而我们使用的命令,和这个流程其实非常相似(参考小结中的流程图)


二.命令的四个概念

  • 命令源(Command Source)
            ↓ # 触发
    	命令(Command)
            ↓ # 查找
    命令绑定(Command Binding)
            ↓ # 执行
    命令目标(Command Target)
    

1.0 命令(Command) —— 继承ICommand接口

  • 本质上其实就是实现了一个ICommand对象

  • 主要负责2件事情

    • Execute(执行逻辑)
    • CanExecute(能不能执行)

1.1 预定义命令库✳

  • 预定义命令库:WPF内置的一组标准命令(已经实现好的 ICommand)

    • 不用写 RelayCommand了,开袋即食,别造轮子了
    • 预定义命令 = 官方帮你定义好的“行为规范”,而不是具体实现
  • 常用的命令库(预定义)有五个静态类

    • ApplicationCommands		# 应用通用
      NavigationCommands		# 导航(页面跳转)
      EditingCommands			# 文本编辑
      ComponentCommands		# 组件
      MediaCommands			# 媒体控制
      
      # 使用时,请随用随查,而且除了第一个,基本上不怎么使用,了解即可
      # 使用频率大概是:
      ApplicationCommands >>> EditingCommands > NavigationCommands > MediaCommands > ComponentCommands
      
  • 示例代码:

    • 新建一个操作,这个操作绑定到逻辑CommandBinding_Executed上

    • <Window.CommandBindings>
          <CommandBinding Command="ApplicationCommands.New" Executed="CommandBinding_Executed"/>
      </Window.CommandBindings>
      
  • 下面大致列举了一部分常用的预定义命令:

    • 随用随查,别背,没用,顶多记一下ApplicationCommands通用类的几个,毕竟大多数都用不上

(1)ApplicationCommands应用通用类

  • 命令 作用 常见场景
    Copy 复制选中内容 TextBox / 数据编辑
    Cut 剪切选中内容 文本编辑
    Paste 粘贴剪贴板内容 输入框
    Save 保存数据 文件/表单
    SaveAs 另存为 文件系统
    Open 打开文件 文件操作
    New 新建内容 编辑器
    Undo 撤销操作 编辑器
    Redo 重做操作 编辑器
    Delete 删除选中项 列表/文本
    Print 打印内容 报表/文档

(2)NavigationCommands导航(页面跳转)类

  • 命令 作用 常见场景
    Back 返回上一页 页面导航
    Forward 前进 页面导航
    Refresh 刷新当前页面 Web/数据页
    BrowseHome 返回首页 应用首页
    BrowseStop 停止加载 浏览器

(3)EditingCommands文本编辑类

  • 命令 作用 常见场景
    Delete 删除内容 文本编辑
    Backspace 删除前一个字符 输入框
    ToggleBold 加粗文本 富文本
    ToggleItalic 斜体文本 富文本
    IncreaseFontSize 增大字体 编辑器
    DecreaseFontSize 减小字体 编辑器

(4)ComponentCommands组件类

  • 命令 作用 常见场景
    MoveUp 向上移动项 列表
    MoveDown 向下移动项 列表
    MoveLeft 向左移动 UI操作
    MoveRight 向右移动 UI操作
    ExtendSelection 扩展选区 多选控件

(5)MediaCommands媒体控制类

  • 命令 作用 常见场景
    Play 播放媒体 视频/音频
    Pause 暂停播放 媒体控制
    Stop 停止播放 媒体控制
    Record 开始录制 录音
    NextTrack 下一首 音乐播放器
    PreviousTrack 上一首 音乐播放器
    VolumeUp 音量增加 媒体控制
    VolumeDown 音量减少 媒体控制

2.命令源 —— 继承ICommandSource

  • 调用命令的对象,通常是UI控件

    • 直白点说,就是,谁触发了命令,那个谁就是命令源

      • # 这里的命令源就是Button
        <Button Command="{Binding SaveCommand}" />
        
  • 命令源相关属性一般有3个,上面那个是最常用的,下面两个也了解一下

    • 第三个命令目标详细请见下一个小点

    • 属性 作用 备注
      🌱Command 指定要触发的命令 必须有,否则不会触发命令
      🌱CommandParameter 传递给命令的参数 可选,用于区分或传递数据
      🌱CommandTarget(命令目标) 指定命令作用对象 可选,不写默认作用于焦点控件(自身)

(1)Command

  • 作用:指定触发哪个命令

  • 类型:ICommand

  • 常用值:ApplicationCommands.New、ApplicationCommands.Copy 等预定义命令,或者自定义 RoutedCommand/RelayCommand

    • 点击按钮就触发 New 命令

    • <Button Command="ApplicationCommands.New" Content="新建"/>
      
      

(2)CommandParameter

  • 作用:给命令传递参数

  • 类型:object

  • 常见场景:

    • 同一个命令绑定到多个按钮,用参数区分不同操作
    • 传递数据给命令执行逻辑
  • 示例:

    • <Button Command="ApplicationCommands.Copy"
              CommandParameter="{Binding SelectedText}"
              Content="复制选中文本"/>
      
    • // 假设下面这个是我们绑定的逻辑Executed
      private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
      {
          var text = e.Parameter; // 获取参数 CommandParameter
          MessageBox.Show($"复制内容: {text}");
      }
      
      

3.命令目标CommandTarget

  • 在其目标上执行命令的对象,继承IInputElement即可

    • 一般情况下,命令源和命令目标是同一个
    • 直白点说就是,命令作用在谁身上,那个谁就是命令目标
      • 即:命令目标就是命令最终影响的对象
  • # 命令 = Copy
    # 命令源 = Button
    # 命令目标 = textBox
    <Button Command="ApplicationCommands.Copy"
            CommandTarget="{Binding ElementName=textBox}" />
    
    

4.命令绑定CommandBindings

  • 命令绑定:将某个命令和执行逻辑进行绑定

    • 命令的逻辑在哪里执行的?在哪里写的
  • # Executed:执行逻辑	 =>  函数OnCopy要自己写
    # CanExecute:能不能执行
    
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Copy"
                        Executed="OnCopy"
                        CanExecute="OnCanCopy"/>
    </Window.CommandBindings>
    
    

三.自定义命令

  • 自定义命令有3种:RouteCommand(路由命令),RouteUICommand(路由命令UI增强版)和完全自定义命令
    • 我们了解一下即可,重点关注第三种

1.RouteCommand(路由命令)

  • 示例代码:点击按钮,弹出提示框

  • Test.xaml

    • <Window x:Class="Command_Demo.Test"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:local="clr-namespace:Command_Demo"
              xmlns:cmd="clr-namespace:Command_Demo.Command"
              mc:Ignorable="d"
              Title="Test" Height="450" Width="800">
      
          <Window.CommandBindings>
              <CommandBinding Command="cmd:CustCmdByRouteCmd.Query" Executed="CommandBinding_Executed"/>
          </Window.CommandBindings>
          
          <Grid>
              <Button Height="100" Width="100" Command="cmd:CustCmdByRouteCmd.Query"/>
          </Grid>
      </Window>
      
  • Test.xaml.cs

    • namespace Command_Demo
      {
          /// <summary>
          /// Test.xaml 的交互逻辑
          /// </summary>
          public partial class Test : Window
          {
              public Test()
              {
                  InitializeComponent();
              }
      
              private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
              {
                  MessageBox.Show("自定义命令执行完成!");
              }
          }
      }
      
      
  • CustCmdByRouteCmd.cs

    • using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using System.Windows.Input;
      
      namespace Command_Demo.Command
      {
          /// <summary>
          /// 通过RouteCommand / RoutedUICommand 自定义命令
          /// </summary>
          public class CustCmdByRouteCmd
          {
              private static RoutedCommand query;
      
              static CustCmdByRouteCmd()
              {
                  query = new RoutedCommand("Query", typeof(CustCmdByRouteCmd));
              }
      
              public static RoutedCommand Query
              {
                  get { return query; }
              }
          }
      }
      
      

2.完全自定义命令1.0 —— 简易制作版

  • 示例代码:

  • ├─Command
    │  └─CustCmd.cs
    │
    ├─Cust_Test.xaml
    └─Cust_Test.xaml.cs
    
    • Cust_Test.xaml

      • <Window x:Class="Command_Demo.Cust_Test"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:local="clr-namespace:Command_Demo"
                xmlns:cust="clr-namespace:Command_Demo.Command"
                mc:Ignorable="d"
                Title="Cust_Test" Height="450" Width="800">
            <Window.Resources>
                <cust:CustCmd x:Key="custCmd"/>
            </Window.Resources>
            
            <Grid>
                <Button Width="150" Height="150" Content="我不是按钮"
                        Command="{StaticResource custCmd}" CommandParameter="WWWWW"/>
        
            </Grid>
        </Window>
        
    • CustCmd.cs

      • using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks;
        using System.Windows;
        using System.Windows.Input;
        
        namespace Command_Demo.Command
        {
            /// <summary>
            /// 自定义命令
            /// </summary>
            public class CustCmd : ICommand
            {
                /// <summary>
                /// 命令状态发生改变的事件(一般可能还用不上)
                /// </summary>
                public event EventHandler? CanExecuteChanged;
        
                /// <summary>
                /// 能不能执行(执行业务)
                /// </summary>
                /// <param name="parameter"></param>
                public bool CanExecute(object? parameter)
                {
                    if (parameter == null)
                    {
                        // 如果参数为空,这个控件(按钮)你往死里点也不会发生什么事(给你ban了)
                        return false;
                    }
                    return true;
                }
        
                /// <summary>
                /// 如何执行(命令状态)
                /// </summary>
                /// <param name="parameter"></param>
                public void Execute(object? parameter)
                {
                    
                    MessageBox.Show($"( •̀ ω •́ )ก็็็็็็็็็็็็็  {parameter}");
                }
            }
        }
        
        

3.完全自定义命令2.0 —— 委托升级版

  • [!IMPORTANT]

    在此之前,我们来回顾一下委托的使用
    与其说是怎么讲自定义命令是怎么使用的,不如说是讲委托的实际运用

    既然你都学到MVVM了,那委托肯定也是学过了的,这边我们简单回顾一下即可
    如果不会,可以看我之前的随笔
    九成九新自用C#入门文档 - 假设狐狸有信箱 - 博客园

    • 委托类型 用途 示例
      Action 执行操作,无返回值 Action report = cal.Report;
      Func<T1, T2, TResult>
      T1,T2... : 参数1,参数2...
      Tesult:函数返回值
      执行操作,有返回值 Func<int,int,int> add = cal.Add;
      delegate 自定义委托类型 delegate int Demo(int a, int b);
      Demo demo = cal.Add;
    • // 委托是如何使用的:声明 -> 实例化 ->  赋值 -> 调用
      // 这里使用自定义委托类型delegate为例
      using System;
      using static System.Console;
      
      namespace Program
      {
          // 1.声明
          // 你可以在命名空间中定义一个类,接口或者委托
          // 但是不能定义一个变量和函数
          
          // 定义一个委托
          delegate void Help();
          // 定义一个类
          public class Person { }
      
          public class Program
          {
              public static void Main()
              {
                  // 2.实例
                  Help h;
                  Person p;
      
                  // 3.赋值
                  // 将一个函数赋值给一个委托实例
                  h = SayHello;
      
                  // 4.调用
                  h();
                  h();
      
                  void SayHello()
                  {
                      WriteLine("哇哇哇哇");
                  }
              }
          }
      }
      

  • 命令示例代码:

    • ├─Command
      │  └─CustCmd_NoBusiness.cs
      │
      ├─CustCmd_NotBus.xaml
      └─CustCmd_NotBus.xaml.cs
      
      
    • CustCmd_NotBus.xaml

      • <Window x:Class="Command_Demo.CustCmd_NotBus"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:local="clr-namespace:Command_Demo"
                xmlns:custCmd="clr-namespace:Command_Demo.Command"
                mc:Ignorable="d"
                Title="完全自定义命令-委托升级版" Height="450" Width="800">
        
            <Window.Resources>
                <!--<custCmd:CustCmd_NoBusiness x:Key="custCmd" CmdAction="TestCmd"/>-->
                <custCmd:CustCmd_NoBusiness x:Key="custCmd" CmdFunc="TestCmd"/>
            </Window.Resources>
            
            <Grid>
                <Button Content="这是一个按钮" Height="150" Width="150"
                        Command="{StaticResource custCmd}" CommandParameter="\/"/>
            </Grid>
        </Window>
        
    • CustCmd_NoBusiness.cs

      • using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks;
        using System.Windows;
        using System.Windows.Input;
        
        namespace Command_Demo.Command
        {
            /// <summary>
            /// 自定义命令:将业务从命令中分离 => 将处理业务函数传递给命令(系统委托)
            /// </summary>
            public class CustCmd_NoBusiness : ICommand
            {
                /// <summary>
                /// 命令状态发生改变的事件(一般可能还用不上)
                /// </summary>
                public event EventHandler? CanExecuteChanged;
        
                /* Action委托一定没有返回值,Func有返回值 */
                // 1.无返回值,无参数
                //public Action CmdAction { get; set; }
                // 2.没返回值,有参数
                //public Action<string> CmdAction {  get; set; }
        
                /// 3.有返回值,无参数
                //public Func<int> CmdFunc { get; set; }
                // 4.有返回值,有参数
                    // Func<A1, A2..., B> 中A是参数,B是返回值
                public Func<string, int> CmdFunc { get; set; }
        
        
        
                /// <summary>
                /// 能不能执行(执行业务)
                /// </summary>
                /// <param name="parameter"></param>
                public bool CanExecute(object? parameter)
                {
                    if (parameter == null)
                    {
                        // 如果参数为空,这个控件(按钮)你往死里点也不会发生什么事
                        return false;
                    }
                    return true;
                }
        
                /// <summary>
                /// 如何执行(命令状态)
                /// </summary>
                /// <param name="parameter"></param>
                public void Execute(object? parameter)
                {
                    //if (CmdAction != null)
                    //{
                    //    /* 执行委托 */
        
                    //    // 1.无返回值+无参数
                    //    //CmdAction.Invoke();
                    //    // 2.无返回值 + 有参数
                    //    CmdAction.Invoke(parameter.ToString());
                    //}
        
                    if (CmdFunc != null)
                    {
                        /* 执行委托 */
        
                        //// 3.有返回值+无参数
                        //int num = CmdFunc.Invoke();
        
                        // 4.有返回值+无参数
                        int num = CmdFunc.Invoke(parameter.ToString());
        
                        MessageBox.Show($"{num}");
                    }
        
                    //MessageBox.Show($"( •̀ ω •́ )ก็็็็็็็็็็็็็  {parameter}");
                }
            }
        }
        
        
    • CustCmd_NotBus.xaml.cs

      • using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Data;
        using System.Windows.Documents;
        using System.Windows.Input;
        using System.Windows.Media;
        using System.Windows.Media.Imaging;
        using System.Windows.Shapes;
        
        namespace Command_Demo
        {
            /// <summary>
            /// CustCmd_NotBus.xaml 的交互逻辑
            /// </summary>
            public partial class CustCmd_NotBus : Window
            {
                public CustCmd_NotBus()
                {
                    InitializeComponent();
                }
        
                /// 1.测试 无返回值+无参数 的Action委托
                //public void TestCmd() => MessageBox.Show("测试!!!");
        
                /// 2.测试 无返回值+有参数 的Action委托
                //public void TestCmd(string str) => MessageBox.Show($"测试参数:{str}");
        
                //// 3.测试 有返回值+无参数 的Func委托
                //public int TestCmd()
                //{
                //     MessageBox.Show("测试成功");
                //    return 1;
                //}
        
                // 4.测试 有返回值+有参数 的Func委托
                public int TestCmd(string str)
                {
                    MessageBox.Show($"测试参数:{str}");
                    return 1;
                }
            }
        }
        
        

四.本章小结

  • 命令执行过程流程图

    • 用户操作(点击按钮 / 菜单 / 快捷键)
                      │
                      ▼
              命令源(Command Source)
              Button / MenuItem / KeyBinding
                      │
                      │ # 触发 Command(命令)
                      ▼
                  Command(继承ICommand)
                      │
                      │ # 传递 CommandParameter(参数)
                      ▼
            查找命令绑定(路由机制)	# 从当前控件 → 向上冒泡查找
                      │
                      ▼
           CommandBinding(命令绑定)
              │                 │
              │                 │
              ▼                 ▼
       CanExecute()        Executed()
      # 能不能执行           执行逻辑
              │
              │ 控制UI是否可用(按钮是否禁用)
              ▼
           CommandTarget(命令目标)
              (命令作用对象)
      

  • 概念 本质 作用 关键点 常见对象
    Command 行为对象 定义“做什么” Execute / CanExecute ICommand
    CommandSource 触发者 谁发起命令 UI交互入口 Button / Menu
    CommandBinding 逻辑绑定 在哪里执行 Executed / CanExecute Window / 控件
    CommandTarget 作用对象 对谁生效 默认是焦点控件 TextBox / ListBox
    CommandParameter 输入参数 传递数据 object类型 SelectedItem 等

  • 类型 本质 是否路由 是否常用 场景
    ICommand 接口 ❌ ⭐⭐⭐⭐⭐ MVVM / 业务逻辑
    RoutedCommand 路由命令 ✔ ⭐⭐ 多入口 / 复杂UI
    RoutedUICommand UI增强版 ✔ ⭐⭐ 菜单 / 本地化
    预定义命令 官方命令 ✔ ⭐⭐⭐ Copy / Paste / New

  • 命令的本质

    • ⚠命令不是事件的上位替代,命令不是事件的上位替代,命令不是事件的上位替代,重要的事情说三遍
    • 命令实际上是对""行为"的对象化抽象,重要作用是前后端解耦
    • 核心作用:解耦 UI触发 → 执行逻辑 → 作用对象
    • 同时通过 CanExecute 实现状态驱动的交互控制

  • 命令系统的四个核心概念

    命令源 → 命令 → 命令绑定 → 命令目标
    
    # 命令源:谁触发
    # 命令:做什么
    # 命令绑定:在哪里执行
    # 命令目标:对谁生效
    

  • 命令源三大核心属性
    • Command:指定执行哪个命令(必须)
    • CommandParameter:传递执行参数(数据驱动行为)
    • CommandTarget:指定作用对象(默认是焦点控件)

这里几乎都是根据龙马老师的教学视频学习的,是我个人目前为止找到的,
感觉关于命令的最清晰的教学,所以这篇随笔是根据龙马老师教学框架写的笔记
建议结合视频观看,随笔根据其内容做出了一些补充
up主页:龙马008的个人空间-龙马008个人主页-哔哩哔哩视频

随笔参考:
1.WPF 自定义快捷键命令(Command) - Gnie - 博客园
2.《深入浅出WPF》笔记——命令篇 - haiziguo - 博客园
3.64.第9章_命令概念_哔哩哔哩_bilibili
4.65.第9章_命令相关的4个概念_哔哩哔哩_bilibili
5.67.第9章_简单的自定义命令_哔哩哔哩_bilibili
6.68.第9章_自定义命令(委托)_哔哩哔哩_bilibili
7.69.第9章_自定义命令(委托2)_哔哩哔哩_bilibili

给同事看了一下我的博客,他吐槽说,有一些地方AI味是不是有点浓,
例如那个什么1.0,2.0,还有代码里面的
我说那就是我的风格,几年前的笔记就开始这样了
同事:原来你是个AI
我:&!@*HHO!!#DOUI!UI!{:::》(鸟语花香)

posted @ 2026-03-23 15:59  假设狐狸有信箱  阅读(154)  评论(1)    收藏  举报