命令与ICommand接口
一、命令
官方文档:https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/commanding-overview
1. ICommandSource
官方文档:https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/how-to-implement-icommandsource
2. 示例
<Grid>
<TextBox x:Name="textBox" Text="TextBox" VerticalAlignment="Top" Height="150" Margin="219,35,187,0"/>
<Button x:Name="buttonCut"
Content="剪切"
Command="ApplicationCommands.Cut"
CommandTarget="{Binding ElementName=textBox}"
Height="100" Margin="219,233,419,102"/>
<Button x:Name="buttonPaste"
Content="粘贴"
Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=textBox}" Margin="453,233,187,107"/>
</Grid>
3. CommandBinding
- 当TEXTBOX内容长度大于0时,CanExecute被判断为true
- true可以启用命令源按钮,false禁用按钮
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Cut" CanExecute="my_CanExecute" Executed="my_Execute"/>
</Window.CommandBindings>
<Grid>
<TextBox x:Name="textBox" Text="TextBox" VerticalAlignment="Top" Height="150" Margin="219,35,187,0"/>
<Button x:Name="buttonCut" Margin="219,233,419,102" Height="100"
Content="剪切"
Command="ApplicationCommands.Cut"/>
<Button x:Name="buttonPaste"
Content="粘贴"
Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=textBox}"
Margin="453,233,187,107"/>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void my_Execute(object sender, ExecutedRoutedEventArgs e)
{
Console.WriteLine("Execute!!!");
textBox.Cut();
}
private void my_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = textBox.SelectionLength > 0;
}
}
二、ICommand
官方文档:https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.input.icommand?view=net-9.0
1.ICommand接口
ICommand接口的属性与事件如下:
2. ICommand用法
<Grid>
<TextBox HorizontalAlignment="Left" Margin="222,62,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="422" Height="102"/>
<Button x:Name="button1" Content=" 自定义命令" HorizontalAlignment="Left" Margin="222,217,0,0" VerticalAlignment="Top" Height="64" Width="145"/>
<Button x:Name="button2" Content="触发事件" HorizontalAlignment="Left" Margin="499,217,0,0" VerticalAlignment="Top" Height="64" Width="145"/>
</Grid>
在MainWindow.xaml.cs文件内自定义命令接口
<Window.Resources>
<local:MyCommand x:Key="MyCMD" />
</Window.Resources>
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Margin="222,62,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="422" Height="102"/>
<Button x:Name="button1" Content=" 自定义命令"
Command="{StaticResource MyCMD}"
CommandParameter="{Binding ElementName=textBox,Path=Text}"
HorizontalAlignment="Left" Margin="222,217,0,0" VerticalAlignment="Top" Height="64" Width="145"/>
<Button x:Name="button2" Content="触发事件" HorizontalAlignment="Left" Margin="499,217,0,0" VerticalAlignment="Top" Height="64" Width="145"/>
</Grid>
public class MyCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;//按钮可以点击
//return false;//按钮不可点击
}
//Execute方法中实现命令处理逻辑
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString());
}
}
以下为CanExecuteChanged的事件解释:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button2_Click(object sender, RoutedEventArgs e)
{
MyCommand c = Resources["MyCMD"] as MyCommand;
//触发CanExecuteChanged
c.RaiseCanExecuteChanged();
}
}
//实现ICommand接口
public class MyCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
String str = parameter as String;
return str?.Length > 0;
//return true;//按钮可以点击
//return false;//按钮不可点击
}
//Execute方法中实现命令处理逻辑
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString());
}
//定义一个方法,手动触发CanExecuteChanged事件
public void RaiseCanExecuteChanged()
{
//表面上,没有为 CanExecuteChanged 这个事件添加任何订阅方法
//例如CanExecuteChanged += fun;
//但是我们为按钮设置命令时,自动加入了一个此事件订阅的方法,
//并且这个订阅的方法,会去调用命令的CanExecute
//可通过Delegate查看CanExecuteChanged的来源与内容
Delegate[] delegates = CanExecuteChanged.GetInvocationList();
//delegates内查看到 System.Windows.Input.CanExecuteChangedEventManager+HandlerSink"
//OnCanExecuteChanged
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
3. CanExecute
以ButtonBase为例介绍,调用了OnCommandChanged
OnCommandChanged调用了HookCommand
HookCommand调用了AddHandler,CanExecute调用了CanExecuteCommandSource
AddHandler调用的PrivateAddHandler又new 了一个HandlerSink
HandlerSink将OnCommandChanged函数添加到Icommand接口的CanExecuteChanged
总结
关于ButtonBase、CanExecuteChangedEventManager、commandHelpers的详细原理、我们可以参考WPF框架的源码
https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/ButtonBase.cs
https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/CanExecuteRoutedEventArgs.cs
https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Commands/CommandHelpers.cs