一.概念
我们先来看看命令这两个字,在汉语上的词意
- 命令:一种权威性的指示,通常指上级对下级的口头或书面指示,要求其执行某项任务或采取某种行动
命令往往具有明确的目标和要求,并带有一定的强制性
- 命令:一种权威性的指示,通常指上级对下级的口头或书面指示,要求其执行某项任务或采取某种行动
然后我们再来看看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.csnamespace 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.csusing 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.csCust_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.csusing 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.csCustCmd_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.csusing 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.csusing 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:指定作用对象(默认是焦点控件)