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!{:::》(鸟语花香)

浙公网安备 33010602011771号