WPF 路由命令(Command)
1.WPF 命令
四个概念,牢记概念哦!
-
命令是要执行的操作。
-
命令源是调用命令的对象。
-
命令目标是在其上执行命令的对象。
-
命令绑定是将命令逻辑映射到命令的对象。
2.命令源
WPF 中的命令源通常实现 ICommandSource 接口,例如 Button 实现了 ICommandSource,KeyGesture 接口。因此才有这种栗子:
<Grid>
<Button Command="{Binding CustomCommand}"></Button>
</Grid>
因为 Button 实现了 ICommandSource 接口,所以我们才能使用 Command 属性来绑定我们自己的命令(如果你不理解xml里是什么意思,暂且不用计较,我细细道来)。
在了解 ICommandSource 之前,我们看一下 ICommand 接口,如下:
namespace System.Windows.Input
{
public interface ICommand
{
// 能否执行状态 发生改变时被调用,一般少用。
event EventHandler? CanExecuteChanged;
// 如果该方法返回 true,则 Execute 将被调用,否则不调用。
bool CanExecute(object? parameter);
// 如果一个命令可以执行,则该方法将被调用。
void Execute(object? parameter);
}
}
在上述代码中,ICommand 接口定义了命令执行时的一些流程规范方法。
知道 ICommand 接口之后,我们可以接着看 ICommandSource 接口,如下:
namespace System.Windows.Input
{
// 定义一些知道如何调用一个命令的对象。
public interface ICommandSource
{
// 当命令源被调用时将执行这个命令。
ICommand Command
{
get;
}
// 调用命令时将传递的参数。
object CommandParameter
{
get;
}
// 正在其上执行命令的对象。
// 值得注意的是,在 WPF 中,ICommandSource 上的 CommandTarget 属性仅在 Icommand 为 RoutedCommand 时使用,否则忽略命令目标。
// 如果未设置 CommandTarget,则具有键盘焦点的元素将成为命令目标。
IInputElement CommandTarget
{
get;
}
}
}
在 WPF 中,有这么一段代码
public class UIElement : Visual, IInputElement, IAnimatable
UIElement 实现了 IInputElement,而 UIElement 几乎是所有元素的基类,所以通常 System.Windows.Controls 下的控件都可以作为命令源中的目标。
为什么是几乎呢?因为有些元素可以只是继承自 Visual 的一个对象,并且可以将 Visual 派生的子元素添加到视觉树中。(如果这里不懂,跳过就行)
3.使用命令
如何将命令使用到我们的控件中呢?
WPF 框架已经帮我们写好了如何将 WPF 命令应用的元素中,UIElement 元素有以下定义:
UIElement.CommandBindings
CommandBindings 是一个 CommandBinding 类型的集合,它的作用是将命令与实现该命令的事件处理程序相关联。
CommandBinding 类型的事件如下:
namespace System.Windows.Input
{
public class CommandBinding
{
// 需要执行的命令,最好 RoutedCommand 的派生类,否则 CommandTarget 是无效的。
public ICommand Command;
// 这两个是冒泡路由策略。
public event CanExecuteRoutedEventHandler CanExecute;
public event ExecutedRoutedEventHandler Executed;
// 这两个是隧道路由策略。
public event CanExecuteRoutedEventHandler PreviewCanExecute;
public event ExecutedRoutedEventHandler PreviewExecuted;
}
}
如果上面的你仔细看了 ICommand,那么对这其中的这几个事件,就很容易理解。
- 如何使用命令?
/// <summary>
/// Event to execute a command
/// </summary>
public static readonly RoutedEvent ExecutedEvent =
EventManager.RegisterRoutedEvent("Executed",
RoutingStrategy.Bubble,
typeof(ExecutedRoutedEventHandler),
typeof(CommandManager));
/// <summary>
/// Event before a command is executed
/// </summary>
public static readonly RoutedEvent PreviewExecutedEvent =
EventManager.RegisterRoutedEvent("PreviewExecuted",
RoutingStrategy.Tunnel,
typeof(ExecutedRoutedEventHandler),
typeof(CommandManager));
命令其实只是对类 CommandManager
中的 ExecutedEvent
和 PreviewExecutedEvent
这两个路由事件进行了封装,所以我们可以使用 ComandManager
, 并且像使用路由事件一样来使用命令。
上代码:
// 自己定义一个命令。
internal class MyRoutedCommands
{
// 显示一个对话框。
public static readonly RoutedUICommand ShowMessageBox = new();
}
// 我们随便定义一个用户控件类,用于接收并执行命令。
internal sealed class MyUserControl : UserControl
{
public MyUserControl()
{
// 这种方式也能响应命令。
//this.CommandBindings.Add(new CommandBinding(
// MyRoutedCommands.ShowMessageBox,
// Binding_Executed
// ));
// 像订阅路由事件一样去使用命令。
CommandManager.AddPreviewCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_PreviewCanExecute));
CommandManager.AddPreviewExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_PreviewExecuted));
CommandManager.AddCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_CanExecute));
CommandManager.AddExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_Executed));
}
private void Binding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show($"Excute Executed method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
private void Binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
MessageBox.Show($"Excute CanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
private void Binding_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show($"Excute PreviewExecuted method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
private void Binding_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
MessageBox.Show($"Excute PreviewCanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
}
// 主窗口类,点击按钮之后,引发路由触发命令,此时将隧道(预览)路由到 _control 元素上,并执行响应的命令。
internal class MainWindow : Window
{
private readonly MyUserControl _contol = new MyUserControl();
public MainWindow()
{
Width = 400;
Height = 647;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var grid = new Grid();
this.AddChild(grid);
var myButton = new Button
{
Content = "Toggle Event",
Width = 200,
Height = 20
};
myButton.Click += MyButton_Click;
grid.Children.Add(_contol);
grid.Children.Add(myButton);
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
// 引发路由事件,因为 MainWindow 是上层元素,所以需要指定预览到具体的子元素,这里指定为 _contol。
MyRoutedCommands.ShowMessageBox.CanExecute("Parameter", _contol);
MyRoutedCommands.ShowMessageBox.Execute("Parameter", _contol);
}
}
方法执行顺序为:
- Binding_PreviewCanExecute
- Binding_CanExecute
- Binding_PreviewExecuted
- Binding_Executed
当然,我们也可以使用以下方法去接收并执行命令
internal sealed class MyUserControl : UserControl
{
public MyUserControl()
{
// 这种方式也能响应命令。
this.CommandBindings.Add(new CommandBinding(
MyRoutedCommands.ShowMessageBox,
Binding_Executed
));
//// 像订阅路由事件一样去使用命令。
//CommandManager.AddPreviewCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_PreviewCanExecute));
//CommandManager.AddPreviewExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_PreviewExecuted));
//CommandManager.AddCanExecuteHandler(this, new CanExecuteRoutedEventHandler(Binding_CanExecute));
//CommandManager.AddExecutedHandler(this, new ExecutedRoutedEventHandler(Binding_Executed));
}
private void Binding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show($"Excute Executed method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
private void Binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
MessageBox.Show($"Excute CanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
private void Binding_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show($"Excute PreviewExecuted method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
private void Binding_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
MessageBox.Show($"Excute PreviewCanExecute method as {sender.GetType().Name}. Message: {e.Parameter}", "Message");
}
}
这个栗子中,当我们在 MainWindow 中触发命令后,Binding_Executed 方法将会被执行。
- 忽略命令目标使用命令
上述有提到过,在 WPF 中,ICommandSource 上的 CommandTarget 属性仅在 Icommand 为 RoutedCommand 时使用,否则忽略命令目标。如果未设置 CommandTarget,则具有键盘焦点的元素将成为命令目标,所以平时我们的 Button.Command 使用的命令,直接继承实现 ICommand 即可,因为点击按钮之后,按钮本身就获取到了焦点。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类