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; // 获取参数 CommandParameterMessageBox.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!{:::》(鸟语花香)
