命令使用步骤:
创建命令类。创建一个实现ICommand接口的类,如果命令与具体业务逻辑无关,使用WPF类库中的RoutedCommand类即可。如果想得到与业务逻辑相关的专有命令,则需创建RoutedCommand或ICommand接口的派生类
声明命令实例。使用命令时需要创建命令类的实例。一般情况下,程序中的某种操作只需要一个命令实例与之对应即可,因此,程序中的命令多使用单件模式,以减少代码复杂度。
指定命令的源。同一个命令可以有多个源,一旦命令指派给命令源,那么命令源就会受命令的影响,当命令不能被执行的时候,作为命令源的控件将处于不可用的状态。
指定命令的目标。命令目标不是命令的属性,而是命令源的属性,被指定为命令源的组件,无论是否拥有焦点都会收到命令。如果没有指定目标,wpf默认当前拥有焦点的对象就是命令目标。
设置命令关联。无论命令目标是由程序员指定还是由WPF根据焦点所在判断出来的,一旦某个UI组件被命令源“瞄上”,命令源就会不断向命令目标投石问路,命令目标就会不停地发送可路由的PreviewCanExecute 和CanExecute附加事件,这两个事件会沿着UI元素树向上传递并被命令关联所捕捉,命令关联捕捉到这些事件后就会实时向命令报告是否能够发送。如果命令被发送出来,并到达命令目标,命令目标就会发送PreviewExecuted和Executed两个附加事件,这两个事件会沿着UI元素树传递并被命令关联捕捉,命令关联会完成一些后续的任务。
一、RoutedCommand
前端代码:
<StackPanel Name="clearStackPanel">
<StackPanel Orientation="Horizontal">
<Button Name="clearButton" Content="Send Clear Text Command" Margin="5" />
<Button Name="addButton" Content="Send Add Text Command" Margin="5" />
</StackPanel>
<TextBox x:Name="beCleardeText" Height="80" Margin="5" Text="鸿雁长飞光不度,鱼龙潜跃水成文"/>
</StackPanel>
后端 :
private RoutedCommand m_clearCmd=new RoutedCommand("Clear",typeof(CommandTab)); //RoutedCommand与业务逻辑无关
private RoutedCommand m_addCmd = new RoutedCommand("Add", typeof(CommandTab));
public CommandTab()
{
InitializeComponent();
InitCommand();
}
private void InitCommand()
{
clearButton.Command = m_clearCmd; //命令赋值给命令源
m_clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt)); //指定快捷键
clearButton.CommandTarget = beCleardeText; //指定命令目标
//创建命令关联
CommandBinding cb = new CommandBinding();
cb.Command = m_clearCmd;
cb.CanExecute += cb_CanExecute;
cb.Executed += Cb_Executed;
clearStackPanel.CommandBindings.Add(cb); //命令关联安置在外围控件,不然无法捕捉CanExecute、Executed等路由事件
addButton.Command = m_addCmd;
m_addCmd.InputGestures.Add(new KeyGesture(Key.A, ModifierKeys.Alt));
addButton.CommandTarget = beCleardeText;
CommandBinding addCb=new CommandBinding();
addCb.Command = m_addCmd;
addCb.CanExecute += AddCb_CanExecute;
addCb.Executed += AddCb_Executed;
clearStackPanel.CommandBindings.Add(addCb);
}
private void Cb_Executed(object sender, ExecutedRoutedEventArgs e)
{
beCleardeText.Clear();
e.Handled = true;
}
private void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (string.IsNullOrEmpty(beCleardeText.Text))
e.CanExecute = false;
else
e.CanExecute = true;
e.Handled = true; //CanExecute激发频率较高,handled避免影响性能
}
private void AddCb_Executed(object sender, ExecutedRoutedEventArgs e)
{
beCleardeText.Text = "山高月小,水落石出";
e.Handled = true;
}
private void AddCb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (string.IsNullOrEmpty(beCleardeText.Text))
e.CanExecute = true;
else
e.CanExecute = false;
e.Handled = true;
}
}
二、自定义Command
首先定义一个接口,约束命令的作用目标。每个需要接受命令的组件都要实现这个接口,确保命令可以成功地执行。
public interface IView
{
void Clear();
void Fill();
}
其次,创建自定义命令,实现 ICommand接口。业务逻辑被引入到Execute方法中
/// <summary>
/// 作用于 IView派生类的填充命令
/// </summary>
public class FillCommand : ICommand
{
//命令可执行状态发生改变时被激发
public event EventHandler? CanExecuteChanged;
//用于判断命令是否可以执行
public bool CanExecute(object? parameter)
{
throw new NotImplementedException();
}
public void Execute(object? parameter)
{
IView view =parameter as IView;
if(view != null)
{
view.Fill();
}
}
}
然后,创建自己的命令源 ,需要实现 ICommandSource 接口
/// <summary>
/// 创建命令源
/// </summary>
public class MyCommandSource : UserControl, ICommandSource
{
public ICommand Command {get; set;}
public object CommandParameter {get; set;}
public IInputElement CommandTarget {get; set;}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
//在命令目标上执行命令
if(CommandTarget != null)
{
Command.Execute(CommandTarget);
}
}
}
之后,创建命令目标,目标需实现 IView接口
界面:
<UserControl x:Class="WPF深入浅出.Controls.MiniView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPF深入浅出.Controls"
mc:Ignorable="d" >
<StackPanel>
<TextBox Name="text1" Margin="5" MinWidth="100" MinHeight="50" FontSize="20"/>
<TextBox Name="text2" Margin="5,0" MinWidth="100" MinHeight="50" FontSize="20"/>
</StackPanel>
</UserControl>
后端:
public partial class MiniView : UserControl,IView
{
public MiniView()
{
InitializeComponent();
}
public void Clear()
{
text1.Clear();
text2.Clear();
}
public void Fill()
{
text1.Text = "樱桃樊素口";
text2.Text = "杨柳小蛮腰";
}
}
最后,将命令、命令源和命令目标集成起来
界面:
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<controls:MiniView x:Name="miniView" Margin="5 50 5 40"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<wpf深入浅出:MyCommandSource x:Name="ctrlFill">
<TextBlock Text="填充" FontSize="16" TextAlignment="Center" Background="LightGreen" Width="80" Margin="0 0 50 0"/>
</wpf深入浅出:MyCommandSource>
<wpf深入浅出:MyCommandSource x:Name="ctrlClear">
<TextBlock Text="清除" FontSize="16" TextAlignment="Center" Background="LightGreen" Width="80"/>
</wpf深入浅出:MyCommandSource>
</StackPanel>
</StackPanel>
</Grid>
后端:
private void InitCommand()
{
FillCommand fillCommand = new FillCommand();
ctrlFill.Command = fillCommand;
ctrlFill.CommandTarget = miniView;
ClearCommand clearCommand = new ClearCommand();
ctrlClear.Command = clearCommand;
ctrlClear.CommandTarget = miniView;
}
todo:使用ICommand和 ICommandSource 的成员组成逻辑,通过Command的CanExecute方法返回值来影响命令源的状态