Wpf基础入门——命令(Command)
本篇文章学习于: 刘铁猛老师《深入浅出WPF》
命令是什么?#
你可能会问:“有了路由事件为什么还需要命令系统呢?”事件的作用是发布、传播一些消息,消息送达接收者,事件的使命也就完成了,至于如何响应事件送来的消息事件并不做规定,每个接收者可以使用自己的行为来响应事件。也就是说,事件不具有约束力。命令与事件的区别就在于命令是具有约束力的。
的确,实际编程工作中就算只使用事件、不使用命令,程序的逻辑也一样可以被驱动得很好,但我们不能阻止程序员按自己的习惯去编写代码。比如保存事件的处理器,程序员们可以写Save()/SaveHandler()/SaveDocument(),这些都符合代码规范,但迟早有一天整个项目会变得无法被读懂,新来的程序员或修改 bug 的程序员会很抓狂。
如果使用命令,情况会好很多—当Save命令到达某个组件时,命令会主动去调用组件的Save()方法,而这个方法可能被定义在基类或者接口里(即保证了这个方法一定是存在的),这就在代码结构和命名上做了约束。不但如此,命令还可控制接收者“先做校验、再保存、然后关闭”,也就是说,命令除了可以约束代码,还可以约束步骤逻辑,这让新来的程序员想犯错都难,也让修改bug 的程序员很快能找到规律、容易上手。
命令系统的基本元素和关系#
WPF的命令系统由几个基本要素构成,它们是:
- 命令(Command):WPF 的命令实际上就是实现了ICommand 接口的类,平时使用最多的是RoutedCommand类。我们还会学习使用自定义命令。
- 命令源(Command Source):即命令的发送者,是实现了ICommandSource接口的类。很多界面元素都实现了这个接口,其中包括 Button、Menultem、ListBoxltem等。
- 命令目标(Command Target):即命令将发送给谁,或者说命令将作用在谁身上。命令目标必须是实现了IInputElement接口的类。
- 命令关联(Command Binding):负责把一些外围逻辑与命令关联起来,比如执行之前对命令是否可以执行进行判断、命令执行之后还有哪些后续工作等。
小试命令——使用 RoutedCommand#
<Window x:Class="Demo5.Wpf命令.MainWindow" 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:Demo5.Wpf命令" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel x:Name="stackPanel"> <TextBox x:Name="tbTest" Background="AliceBlue" FontSize="50"/> <Button x:Name="btnCommand" Content="发送命令"/> </StackPanel> </Window>
namespace Demo5.Wpf命令 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); InitCommand(); } // 声明并定义命令 private RoutedCommand clrCmd = new RoutedCommand("Clear", typeof(Window)); private void InitCommand() { // 把命令赋值给命令源,并指定快捷键 “Alt+C” this.btnCommand.Command = clrCmd; this.clrCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt)); // 指定命令目标 this.btnCommand.CommandTarget = this.tbTest; // 创建命令关联 CommandBinding cb=new CommandBinding(); cb.Command = this.clrCmd;// 只关注与clrCmd相关的事件 cb.CanExecute += Cb_CanExecute; cb.Executed += Cb_Executed; // 把命令关联安置在外围控件上 this.stackPanel.CommandBindings.Add(cb); } // 当探测 命令是否可以执行时,此方法被调用 private void Cb_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if(string.IsNullOrEmpty(this.tbTest.Text)) { e.CanExecute = false; } else { e.CanExecute= true; } e.Handled = true;// 避免继续向上传而降低程序性能 } // 当命令送达目标后,此方法被调用 private void Cb_Executed(object sender, ExecutedRoutedEventArgs e) { this.tbTest.Clear(); e.Handled = true; } } }
命令库和命令参数#
微软在WPF类库里准备了一些便捷的命令库,这些命令库包括:
- ApplicationCommands
- ComponentCommands
- NavigationCommands
- MediaCommands
- EditingCommands
它们都是静态类,而命令就是用这些类的静态只读属性以单件模式暴露出来的。
前面提到命令库里有很多WPF 预制的命令,如 New、Open、Copy、Cut、Paste等。这些命令都是ApplicationCommands类的静态属性,所以它们的实例永远只有一个,
这就引出一个问题:如果界面上有两个按钮,一个用来新建Teacher 的档案,另一个用来新建Student 的档案,都使用New命令的话,程序应该如何区别新建的是什么档案呢?
答案是使用CommandPrameter。
近观命令#
WPF的命令是实现了ICommand 接口的类。ICommand 接口非常简单,只包含两个方法和一个事件:
- Execute方法:命令执行,或者说命令作用于命令目标之上。需要注意的是,现实世界中的命令是不会自己“执行”的,它只能“被执行”,而在这里,执行变成了命令的方法,颇有点儿拟人化的味道。
- CanExecute方法:在执行之前用来探知命令是否可被执行。
- CanExecuteChanged事件:当命令可执行状态发生改变时,可激发此事件来通知其他对象。
RoutedCommand 就是这样一个实现了ICommand接口的类。RoutedCommand在实现ICommand接口时,并未向Execute和 CanExecute方法中添加任何逻辑,也就是说,它是通用的、与具体业务逻辑无关的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)