开发工具与关键技术:Visual Studio 2017
作者:邓崇富
撰写时间:2019 年 6 月 4 日
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ICommand接口与RoutedCommand:
WPF的命令是实现了ICommand接口的类。ICommand接口非常简单,只包含两个方法和一个事件:
- Execute方法:命令执行,或者说命令作用于命令目标之上需要注意的是,显示当中的命令是不会自己“执行”的,他只能“被执行”,而这里,执行变成了命令的方法。
- CanExecute方法:在执行之前用来探知命令是否可被执行。
- CanExecuteChanged事件:当命令可执行状态发生改变时,可激发此事件来通知其他对象。
RoutedCommand就是这样一个实现ICommand接口的类。RoutedCommand在实现ICommand接口时,并未向Execute和CanExecute方法中添加任何逻辑,也就是说,它是通用的、与具体业务逻辑无关的。
自定义Command:
说到“自定义命令”,可以分为两个层次来理解。第一个层次比较浅,指的是当WPF命令库中没有包含想要的命令时,我们就的声明定义自己的RoutedCommand实例。第二个层次是指从实现ICommand接口开始,定义自己的命令并且把某些业务逻辑也包含在命令之中。在WPF的命令系统中命令源(包括ButtonBase、MenuItem、ListBoxItem、Hyperlink)、RoutedCommand和CommandBinding三者互相依赖的相当紧密。在源代码级别上,不但没有将命令相关的方法声明为virtual以供我们重写,而且还很多未向程序员公开的逻辑(比如对ExecuteCore和CanExecuteCore这些方法的声明和调用)。换句话说就是,WPF自带的命令源和CommandBinding就是专门为RoutedCommand而编写的,如果我们想使用自己的ICommand派生类就必须连命令源一起实现(即实现ICommandSource接口)。
下面在程序中定义了一个这样的接口:
namespace WPF练习
{
public interface IView
{
//属性
bool IsChanged { get; set; }
//方法
void SetBinding();
void Refresh();
void Clear();
void Save();
}
}
并且要求每个需要接受命令的组件都必须实现这个接口,这样就确保了命令可以成功地对它执行操作。下面,是实现ICommand接口,创建一个·专门作用于IView派生类的命令。
namespace WPF练习
{
//自定义命令
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方法。目前这个命令比较简单,只用到了Execute方法。在实现这个方法时,我们将这个方法唯一的参数作为命令的目标,如果目标是IView接口的派生类则用其Clear方法,显然,我们已经把业务逻辑引入了命令的Execute方法中。
有了自定义命令,因为WPF命令系统的命令源是专门为RoutedCommand准备的并且不能重写,所以我们只能通过实现ICommandSource接口来创建自己的命令源。
代码如下:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WPF练习
{
public class MyCommandSource : UserControl, ICommandSource
{
//继承自ICommandSource的三个属性
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
public IInputElement CommandTarget { get; set; }
//在组件被单击时连带执行命令
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
//在命令目标上执行命令,或称上命令作用于命令目标
if (this.CommandTarget != null)
{
this.Command.Execute(this.CommandTarget);
}
}
}
}
ICommandSource接口只包含Command、CommandParameter和CommandTarget三个属性,至于这三个属性之间有什么关系就要看我们怎么实现了。在本例子中,CommandParameter完全没有被用到,而CommandTarget被当作参数传递给了Command的Execute方法。命令不会自己被发出,所以要为命令的执行选择一个合适的时机,本例中我们在控件被左单击时执行命令。
现在命令和命令源都准备好了,就差一个可用的命令目标。因为我们的ClearCommand专门作用于IView的派生类,所以合格的ClearCommand命令目标必须实现IView接口。设计这种既有UI有需要实现接口的类可以先用XAML编译器实现其UI部分再找到它的后台C#代码实现接口,原理很简单,WPF会自动为UI元素类添加partial关键字修饰,XAML代码会被翻译成类的一部分,后台代码是类的一部分(甚至可以再多添加几个部分),我们可以在后台代码部分指定基类或实现接口,最终这些部分代码会被翻译到一起。
这个组件的XAML部分代码如下:
<UserControl x:Class="WPF练习.MiniView"
<!--此处省略了引用得命名空间-->
Height="114" Width="200">
<Border CornerRadius="5" BorderBrush="LawnGreen" BorderThickness="2">
<StackPanel>
<TextBox x:Name="texBox1" Margin="5"/>
<TextBox x:Name="texBox2" Margin="5,0"/>
<TextBox x:Name="texBox3" Margin="5"/>
<TextBox x:Name="texBox4" Margin="5,0"/>
</StackPanel>
</Border>
</UserControl>
它的后台代码部分如下:
using System.Windows.Controls;
namespace WPF练习
{
/// <summary>
/// UserControl1.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.texBox1.Clear();
this.texBox2.Clear();
this.texBox3.Clear();
this.texBox4.Clear();
}
}
}
因为上面只演示了命令对Clear方法的调用,所以其它的方法没有具体实现。当Clear方法被调用的时候,它的几个TextBox会被清空。
下面是把自定义的命令
命令源和命令目标集成起来,窗体的XAML代码如下:
<Window x:Class="WPF练习.Window11"
<!--此处省略了引用的命名空间-->
xmlns:local="clr-namespace:WPF练习"
Title="Window11" Height="205" Width="250">
<StackPanel>
<local:MyCommandSource x:Name="ctrlClear" Margin="10">
<TextBlock Text="清除" FontSize="16" TextAlignment="Center" Background="LightGreen" Width="80"/>
</local:MyCommandSource>
<local:MiniView x:Name="miniView"/>
</StackPanel>
</Window>
下面是它的后台代码:
using System.Windows;
namespace WPF练习
{
/// <summary>
/// Window11.xaml 的交互逻辑
/// </summary>
public partial class Window11 : Window
{
public Window11()
{
InitializeComponent();
//声明命令并使用命令源和目标与之关联
ClearCommand clearComd = new ClearCommand();
this.ctrlClear.Command = clearComd;
this.ctrlClear.CommandTarget = this.miniView;
}
}
}
运行程序后,在TextBox里输入文字再点击“清空”按钮,效果如下图: