WPF新手村教程(六)— 新手村BOSS战前准备(命令) 一.概念我们先来看看命令这两个字在汉语上的词意命令一种权威性的指示通常指上级对下级的口头或书面指示要求其执行某项任务或采取某种行动命令往往具有明确的目标和要求并带有一定的强制性然后我们再来看看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 CommandApplicationCommands.New ExecutedCommandBinding_Executed/ /Window.CommandBindings下面大致列举了一部分常用的预定义命令随用随查别背没用顶多记一下ApplicationCommands通用类的几个毕竟大多数都用不上1ApplicationCommands应用通用类命令作用常见场景Copy复制选中内容TextBox / 数据编辑Cut剪切选中内容文本编辑Paste粘贴剪贴板内容输入框Save保存数据文件/表单SaveAs另存为文件系统Open打开文件文件操作New新建内容编辑器Undo撤销操作编辑器Redo重做操作编辑器Delete删除选中项列表/文本Print打印内容报表/文档2NavigationCommands导航(页面跳转类命令作用常见场景Back返回上一页页面导航Forward前进页面导航Refresh刷新当前页面Web/数据页BrowseHome返回首页应用首页BrowseStop停止加载浏览器3EditingCommands文本编辑类命令作用常见场景Delete删除内容文本编辑Backspace删除前一个字符输入框ToggleBold加粗文本富文本ToggleItalic斜体文本富文本IncreaseFontSize增大字体编辑器DecreaseFontSize减小字体编辑器4ComponentCommands组件类命令作用常见场景MoveUp向上移动项列表MoveDown向下移动项列表MoveLeft向左移动UI操作MoveRight向右移动UI操作ExtendSelection扩展选区多选控件5MediaCommands媒体控制类命令作用常见场景Play播放媒体视频/音频Pause暂停播放媒体控制Stop停止播放媒体控制Record开始录制录音NextTrack下一首音乐播放器PreviousTrack上一首音乐播放器VolumeUp音量增加媒体控制VolumeDown音量减少媒体控制2.命令源 —— 继承ICommandSource调用命令的对象通常是UI控件直白点说就是谁触发了命令那个谁就是命令源# 这里的命令源就是Button Button Command{Binding SaveCommand} /命令源相关属性一般有3个上面那个是最常用的下面两个也了解一下第三个命令目标详细请见下一个小点属性作用备注Command指定要触发的命令必须有否则不会触发命令CommandParameter传递给命令的参数可选用于区分或传递数据CommandTarget命令目标指定命令作用对象可选不写默认作用于焦点控件自身1Command作用指定触发哪个命令类型ICommand常用值ApplicationCommands.New、ApplicationCommands.Copy等预定义命令或者自定义RoutedCommand/RelayCommand点击按钮就触发 New 命令Button CommandApplicationCommands.New Content新建/2CommandParameter作用给命令传递参数类型object常见场景同一个命令绑定到多个按钮用参数区分不同操作传递数据给命令执行逻辑示例Button CommandApplicationCommands.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 CommandApplicationCommands.Copy CommandTarget{Binding ElementNametextBox} /4.命令绑定CommandBindings命令绑定将某个命令和执行逻辑进行绑定命令的逻辑在哪里执行的在哪里写的# Executed执行逻辑 函数OnCopy要自己写 # CanExecute能不能执行 Window.CommandBindings CommandBinding CommandApplicationCommands.Copy ExecutedOnCopy CanExecuteOnCanCopy/ /Window.CommandBindings三.自定义命令自定义命令有3种RouteCommand路由命令RouteUICommand路由命令UI增强版和完全自定义命令我们了解一下即可重点关注第三种1.RouteCommand路由命令示例代码点击按钮弹出提示框Test.xamlWindow x:ClassCommand_Demo.Test xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:localclr-namespace:Command_Demo xmlns:cmdclr-namespace:Command_Demo.Command mc:Ignorabled TitleTest Height450 Width800 Window.CommandBindings CommandBinding Commandcmd:CustCmdByRouteCmd.Query ExecutedCommandBinding_Executed/ /Window.CommandBindings Grid Button Height100 Width100 Commandcmd:CustCmdByRouteCmd.Query/ /Grid /WindowTest.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.xamlWindow x:ClassCommand_Demo.Cust_Test xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:localclr-namespace:Command_Demo xmlns:custclr-namespace:Command_Demo.Command mc:Ignorabled TitleCust_Test Height450 Width800 Window.Resources cust:CustCmd x:KeycustCmd/ /Window.Resources Grid Button Width150 Height150 Content我不是按钮 Command{StaticResource custCmd} CommandParameterWWWWW/ /Grid /WindowCustCmd.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 nameparameter/param public bool CanExecute(object? parameter) { if (parameter null) { // 如果参数为空这个控件(按钮)你往死里点也不会发生什么事(给你ban了) return false; } return true; } /// summary /// 如何执行(命令状态) /// /summary /// param nameparameter/param public void Execute(object? parameter) { MessageBox.Show($( •̀ ω •́ )ก็็็็็็็็็็็็็ {parameter}); } } }3.完全自定义命令2.0 —— 委托升级版[!IMPORTANT]在此之前我们来回顾一下委托的使用与其说是怎么讲自定义命令是怎么使用的不如说是讲委托的实际运用既然你都学到MVVM了那委托肯定也是学过了的这边我们简单回顾一下即可如果不会可以看我之前的随笔九成九新自用C#入门文档 - 假设狐狸有信箱 - 博客园委托类型用途示例Action执行操作无返回值Action report cal.Report;FuncT1, T2, TResultT1,T2... : 参数1,参数2...Tesult:函数返回值执行操作有返回值Funcint,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.xamlWindow x:ClassCommand_Demo.CustCmd_NotBus xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:localclr-namespace:Command_Demo xmlns:custCmdclr-namespace:Command_Demo.Command mc:Ignorabled Title完全自定义命令-委托升级版 Height450 Width800 Window.Resources !--custCmd:CustCmd_NoBusiness x:KeycustCmd CmdActionTestCmd/-- custCmd:CustCmd_NoBusiness x:KeycustCmd CmdFuncTestCmd/ /Window.Resources Grid Button Content这是一个按钮 Height150 Width150 Command{StaticResource custCmd} CommandParameter\// /Grid /WindowCustCmd_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 Actionstring CmdAction { get; set; } /// 3.有返回值无参数 //public Funcint CmdFunc { get; set; } // 4.有返回值有参数 // FuncA1, A2..., B 中A是参数B是返回值 public Funcstring, int CmdFunc { get; set; } /// summary /// 能不能执行(执行业务) /// /summary /// param nameparameter/param public bool CanExecute(object? parameter) { if (parameter null) { // 如果参数为空这个控件(按钮)你往死里点也不会发生什么事 return false; } return true; } /// summary /// 如何执行(命令状态) /// /summary /// param nameparameter/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 / CanExecuteICommandCommandSource触发者谁发起命令UI交互入口Button / MenuCommandBinding逻辑绑定在哪里执行Executed / CanExecuteWindow / 控件CommandTarget作用对象对谁生效默认是焦点控件TextBox / ListBoxCommandParameter输入参数传递数据object类型SelectedItem 等类型本质是否路由是否常用场景ICommand接口❌⭐⭐⭐⭐⭐MVVM / 业务逻辑RoutedCommand路由命令✔⭐⭐多入口 / 复杂UIRoutedUICommandUI增强版✔⭐⭐菜单 / 本地化预定义命令官方命令✔⭐⭐⭐Copy / Paste / New命令的本质⚠命令不是事件的上位替代命令不是事件的上位替代命令不是事件的上位替代重要的事情说三遍命令实际上是对行为的对象化抽象重要作用是前后端解耦核心作用解耦 UI触发 → 执行逻辑 → 作用对象同时通过CanExecute实现状态驱动的交互控制命令系统的四个核心概念命令源 → 命令 → 命令绑定 → 命令目标 # 命令源谁触发 # 命令做什么 # 命令绑定在哪里执行 # 命令目标对谁生效命令源三大核心属性Command指定执行哪个命令必须CommandParameter传递执行参数数据驱动行为CommandTarget指定作用对象默认是焦点控件