第九章 深入浅出话命令
事件的作用是发布、传播一些消息,消息送达接收者,事件的使命也就完成了,每个接收者使用自己的行为来相应事件。事件不具有约束力,命令具有约束力,不仅可以约束代码,还可以约束步骤逻辑。
- 命令:ICommand接口的类,常用的是RoutedCommand类,也可以自定义类。
- 命令源:命令的发送者,ICommandSource接口的类。
- 命令目标:命令发给谁,或者命令作用在谁身上。IInputElement接口的类。
- 命令关联:把一些外围逻辑与命令关联起来,如进行逻辑判断、后续处理等。
示例:用Button发送一个命令,当命令到达TextBox时TextBox会被清空(如果TextBox中没有文字则命令不可被发送)。
<Window x:Class="WpfApplication1_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="LightBlue"
Title="MainWindow" Height="175" Width="260">
<StackPanel x:Name="stackPanel">
<Button x:Name="button1" Content="Send Command" Margin="5"/>
<TextBox x:Name="textBoxA" Margin="5" Height="100"/>
</StackPanel>
</Window>
namespace WpfApplication1_2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeCommand();//
}
//声明并定义命令,一处声明、处处使用
private RoutedCommand clear_Cmd = new RoutedCommand("Clear", typeof(MainWindow));
private void InitializeCommand()
{
//把命令赋值给发送者(命令源)并指定快捷键
this.button1.Command = this.clear_Cmd;
this.clear_Cmd.InputGestures.Add(new KeyGesture(Key.C,ModifierKeys.Alt));
//指定命令目标
this.button1.CommandTarget = this.textBoxA;
//创建命令关联
CommandBinding cb = new CommandBinding();
cb.Command = this.clear_Cmd;//只关注与clear_Cmd相关的事件
cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
cb.Executed += new ExecutedRoutedEventHandler(cv_Executed);
//把命令关联安置在外围控件上
this.stackPanel.CommandBindings.Add(cb);
}
//当探测命令可以执行时,此方法被调用
void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if(string.IsNullOrEmpty(this.textBoxA.Text))
{ e.CanExecute = false; }
else
{e.CanExecute=true ;}
//避免继续向上传而降低程序性能
e.Handled=true;
}
//当命令送达目标后,此方法被调用
void cv_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.textBoxA.Clear();
//避免继续向上传而降低程序性能
e.Handled = true;
}
}
}
RoutedCommand只负责在程序中“跑腿”而并不对命令目标做任何操作,由CommandBinding完成操作,CommandBinding一定要设置在命令目标的外围控件上,不然无法捕捉cb_CanExecute、cv_Executed。
WPF 的命令库
WPF类库里的便捷命令库(都是静态类):
- ApplicationCommands
- ComponentCommands
- NavigationCommands
- MediaCommands
- EditingCommands
示例:界面上有两个按钮,一个新建Teacher档案、一个新建Student档案。都使用New命令,但通过CommandParameter来区别新建类别的不同。(这里使用XAML为窗体添加CommandBinding、CanExecute、Executed)
<Window x:Class="WpfApplication1_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="240" Width="360" Background="LightBlue" WindowStyle="ToolWindow">
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="4"/>
<RowDefinition Height="24"/>
<RowDefinition Height="4"/>
<RowDefinition Height="24"/>
<RowDefinition Height="4"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--下面代码是命令和命令参数-->
<TextBlock Text="Name:" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="0"/>
<TextBox x:Name="nameTextBox" Margin="60,0,0,0" Grid.Row="0"/>
<Button Content="New Teacher" Command="New" CommandParameter="Teacher" Grid.Row="2"/>
<Button Content="New Student" Command="New" CommandParameter="Student" Grid.Row="4"/>
<ListBox x:Name="listBoxNewItems" Grid.Row="6"/>
</Grid>
<!--下列代码为窗体添加CommandBindings-->
<Window.CommandBindings>
<CommandBinding Command="New" CanExecute="New_CanExecute" Executed="New_Executed"/>
</Window.CommandBindings>
</Window>
namespace WpfApplication1_2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void New_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if(string.IsNullOrEmpty(this.nameTextBox.Text))
{ e.CanExecute = false; }
else
{ e.CanExecute = true; }
}
private void New_Executed(object sender, ExecutedRoutedEventArgs e)
{
string name = this.nameTextBox.Text;
//如果是Teacher
if(e.Parameter.ToString()=="Teacher")
{
this.listBoxNewItems.Items.Add(string.Format("New Teacher:{0},以其昭昭使人昭昭。", name));
}
//如果是Student
if(e.Parameter.ToString()=="Student")
{
this.listBoxNewItems.Items.Add(string.Format("New Student:{0},童蒙!",name));
}
}
}
}
![image-20200827162834258](https://i.loli.net/2020/09/04/HFJ2PoIidGfDBha.png)
近观命令
ICommand接口与RoutedCommand,这一章节没有仔细看。
自定义Command
-
WPF命令库中没有自己需要的命令,需要声明定义自己的RoutedCommand实例,这是浅层次的理解,本质还是RoutedCommand的使用(WPF自带的命令源和CommandBinding就是专门为RoutedCommand编写)。
-
从实现ICommand接口开始,定义自己的命令,并把某些业务逻辑也包含在命令中(想使用自己的ICommand派生类就必须连命令源一起实现)。
为了简化CommandBinding的程序结构,将业务逻辑移入命令的Execute方法内。
//自定义接口
public interface IView //需要将其放在MainWindow的外面,这样其他的窗体才可以引用
{
//属性
bool IsChanged { get; set; }
//方法
void SetBinding();
void Refresh();
void Clear();
void Save();
//....
}
//自定义命令源:WPF命令源是为RoutedCommand准备的,且不可重写。只能通过ICommandSource接口创建自己的命令源
namespace WpfApplication1_2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public class MyCommandSource : UserControl, ICommandSource
{
//继承ICommandSource的三个属性:
public ICommand Command { get; set; }
public object CommandParameter { get; set; }//本例中没有用到
public IInputElement CommandTarget { get; set; }//被当作参数传递给了Command的Execute方法
//在组件被单击时连带执行命令 //为命令选择合适时机,此处为控件被左单击时执行
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
//在命令目标上执行命令,或者让命令作用于命令目标
if (this.CommandTarget != null)
{
this.Command.Execute(this.CommandTarget);
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//声明命令并使用命令源和目标与之关联
ClearCommand clearCmd = new ClearCommand();
this.ctrlClear.Command = clearCmd;
this.ctrlClear.CommandTarget = this.miniView;
}
//自定义命令:实现ICommand接口,创建一个专门作用于IView派生类的命令
public class ClearCommand:ICommand
{
//当命令可执行状态发生改变时,应当被激发
public event EventHandler CanExecuteChanged;
//用于判断命令是否可以执行(暂不实现)
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
//命令执行,带有与业务相关的Clear逻辑
public void Execute(object parameter)
{
IView view = parameter as IView;
if(view!=null)
{
view.Clear();
}
}//命令实现了ICommand接口,并继承了CanExecuteChanged事件、CanExecute和Execute方法
}
//至此,命令和命令源都有了。差一个可用的命令目标。因为此处ClearCommand作用于IView派生类,所以
//ClearCommand需要实现IView接口:XAML实现UI部分,C#实现接口部分。
}
}
<Window x:Class="WpfApplication1_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1_2"
Title="MainWindow" Height="205" Width="250" Background="LightBlue" WindowStyle="ToolWindow">
<StackPanel>
<local:MyCommandSource x:Name="ctrlClear" Margin="10">
<TextBlock Text="清除" FontSize="16" TextAlignment="Center" Background="LawnGreen" Width="80"/>
</local:MyCommandSource>
<local:MiniView x:Name="miniView"/>
</StackPanel>
</Window>
<UserControl x:Class="WpfApplication1_2.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"
mc:Ignorable="d"
d:DesignHeight="114" d:DesignWidth="200">
<Border CornerRadius="5" BorderBrush="LawnGreen" BorderThickness="2">
<StackPanel>
<TextBox x:Name="textBox1" Margin="5"/>
<TextBox x:Name="textBox2" Margin="5,0"/>
<TextBox x:Name="textBox3" Margin="5"/>
<TextBox x:Name="textBox4" Margin="5,0"/>
</StackPanel>
</Border>
</UserControl>
namespace WpfApplication1_2
{
/// <summary>
/// MiniView.xaml 的交互逻辑
/// </summary>
//自定义命令目标
public partial class MiniView : UserControl,IView
{
public MiniView()
{
InitializeComponent();
}
//继承自IView的成员们
public bool IsChanged { get; set; }
public void SetBinding() { }
public void Refresh() { }
public void Save() { }
//用于清除内容的业务逻辑
public void Clear()
{
this.textBox1.Clear();
this.textBox2.Clear();
this.textBox3.Clear();
this.textBox4.Clear();
}
}
}
![image-20200827172005749](https://i-blog.csdnimg.cn/blog_migrate/918e7108e398dc01f1fec7ce0a69419c.png)
程序的流程图如下: