命令——WPF学习之深入浅出

WPF学习之深入浅出话命令

WPF为我们准备了完善的命令系统,你可能会问:“有了路由事件为什么还需要命令系统呢?”。事件的作用是发布、传播一些消息,消息传达到了接收者,事件的指令也就算完成了,至于如何响应事件送来的消息事件并不做任何限制,每个接收者可已用自己的行为来响应事件。也就是说,事件不具有约束力。命令和事件的区别就在于命令具有约束力。

的确,在实际编程工作中,即使只用事件不用命令程序的逻辑一样被驱动的很好,但我们不能够阻止程序员按照自己的习惯去编写代码。比如保存事件的处理器,程序员可以写Save()、SaveHandle()、SaveDocument()... 这些都符合代码规范。但迟早有一天整个项目会变的让人无法读懂,新来的程序员或修改bug的程序员会很抓狂。如果使用命令,情况就会好很多----当Save命令到达某个组件的时候,命令会自动去调用组件的Save方法。而这个方法可能定义在基类或者接口里(即保证了这个方法是一定存在的),这就在代码结构和命名上做了约束。不但如此,命令还可控制接收者“先做校验,再保存,最后退出”,也就是说命令除了可以约束代码,还可以约束步骤逻辑,让新来的程序员想犯错都难,也让那个修改Bug的程序员容易找到规律,容易上手。

1.1      命令系统的基本元素和关系

  • WPF的命令系统由几个基本要素构成,它们是:
  • 命令(Command):WPF的命令实际上就是实现了ICommand接口的类,平时使用最多的就是RoutedCommand类。我们还会学习使用自定义命令。
  • 命令源(Command Source):即命令的发送者,是实现了ICommandSource接口的类。很多界面元素都实现了这个接口,其中包括Button,ListBoxItem,MenuItem等。
  • 命令目标(Command Target):即命令发送给谁,或者说命令作用在谁的身上。命令目标必须是实现了IInputElement接口的类。
  • 命令关联(Command Binding):负责把一些外围逻辑和命令关联起来,比如执行之前对命令是否可以执行进行判断、命令执行之后还有哪些后续工作等。

1.2      基本元素之间的关系

这些基本元素的关系体现在使用命令的过程中。命令的使用大概分为以下几步:

(1)创建命令类:即获得一个实现ICommand接口的类,如果命令与具体的业务逻辑无关则使用WPF类库中的(RoutedCommand)类即可。如果想得到与业务逻辑相关的专有命令,则需要创建RoutedCommand(或者ICommand接口)的派生类。

(2)声明命名实例:使用命令时需要创建命令类的实例。这里有一个技巧,一般情况下程序中某种操作只需要一个命令实例与之对应即可。比如对应“保存”这个命令操作。因此程序中的命令多使用单件模式以减少代码的复杂度。

(3)指定命令的源:即指定由谁来发送命令。如果把命令看作炮弹,那么命令源就相当于火炮。同一个命令可以有多个源。比如保存命令,即可以由菜单中的保存项来发送,也可以由保存工具栏中的图标进行发送。需要注意的是,一旦把命令指派给了命令源,那么命令源就会受命令的影响,当命令不能被执行的时候命令源的控件处于不可用状态。看来命令这种炮弹还很智能,当不满足发送条件的时候还会给用来发射它的火炮上一道保险、避免走火。还需要注意,各种控件发送命令的方法不经相同,比如Button和MenuButton在单击时发送命令,而ListBoxItem单击时表示被选中,双击的时候才发送命令。

(4)指令命令目标:命令目标并不是命令的属性,而是命令源的属性。指定命令目标是告诉命令源向哪个组件发送命令。无论这个组件是否拥有焦点他都会收到这个命令。如果没有为源指定命令目标,则WPF系统认为当前拥有焦点的对象就是命令目标。这个步骤有点像为火炮指定目标。

(5)设置命令关联:炮兵是不能单独战斗的,就像炮兵在设计之前需要侦察兵观察敌情、判断发射时机,在射击后观测射击效果,帮助修正一样。WPF命令需要CommandBinding在执行之前来帮助判断是不是可以执行、在执行后做一些事来“打扫战场”。

在命令目标和命令关联之间还有一些微妙的关系。无论命令目标是由程序员指定还是由WPF系统根据焦点所在地判断出来的,一旦某个UI组件被命令源瞄上,命令源就会不断的向命令目标投石问路,命令目标就会不停的发送可路由的PreviewCanExecute和CanExecute附加事件。事件会沿UI元素树向上传递并被命令关联所捕获,命令关联会完成一些后续任务。别小看“后续任务”,对于那些业务逻辑无关的通用命令,这些后续任务才是最重要的。

你可能会问:“命令目标怎么会发出PreviewCanExecute、CanExecute、PreviewExecute和Executed事件呢?”其实这4个事件都是附加事件,是被CommandManager类“附加”给命令目标的。大家可以翻过头来再理解一下附加事件。另外,PreviewCanExecute和CanExecute的执行时机不由程序员控制,而且执行效率比较高,这不但给系统性能带来了些降低,偶尔还会引入几个意想不到的BUG并且比较难调试,请大家务必多加小心。

下图所示是WPF命令系统基本元素的关系图:

1.3         小试命令

说起来很热闹,现在让我们动手实践一下。实现这样一个需求:定义一个命令,使用Button来发送这个命令,当命令到达TextBox的时候,TextBox会被清空(如果TextBox没有文字,命令不可用。)。

程序XAML代码如下:

[html] view plaincopyprint?

  1. <Window x:Class="WpfApplication1.Window28"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="Window28" Height="300" Width="300" WindowStyle="ToolWindow">
  5. <StackPanel Background="LightBlue" x:Name="sp1">
  6. <Button Content="Send Command" x:Name="btn1" Margin="5"></Button>
  7. <TextBox x:Name="txtA" Margin="5,0" Height="200"></TextBox>
  8. </StackPanel>
  9. </Window>

后台代码为:

[csharp] view plaincopyprint?

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Data;
  8. using System.Windows.Documents;
  9. using System.Windows.Input;
  10. using System.Windows.Media;
  11. using System.Windows.Media.Imaging;
  12. using System.Windows.Shapes;
  13. namespace WpfApplication1
  14. {
  15. /// <summary>
  16. /// Window28.xaml 的交互逻辑
  17. /// </summary>
  18. public partial class Window28 : Window
  19. {
  20. public Window28()
  21. {
  22. InitializeComponent();
  23. InitializeCommand();
  24. }
  25. //声明并定义命令
  26. private RoutedCommand rouutedCommand = new RoutedCommand("Clear",typeof(Window28));
  27. private void InitializeCommand()
  28. {
  29. //把命令赋值给命令源,并定义快捷键
  30. this.btn1.Command = rouutedCommand;
  31. this.rouutedCommand.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));
  32. //指定命令目标
  33. this.btn1.CommandTarget = txtA;
  34. //创建命令关联
  35. CommandBinding commandBinding = new CommandBinding();
  36. commandBinding.Command = rouutedCommand;//只关注与rouutedCommand相关的命令
  37. commandBinding.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
  38. commandBinding.Executed += new ExecutedRoutedEventHandler(cb_Execute);
  39. //把命令关联安置在外围控件上
  40. this.sp1.CommandBindings.Add(commandBinding);
  41. }
  42. //当命令到达目标之后,此方法被调用
  43. private void cb_Execute(object sender, ExecutedRoutedEventArgs e)
  44. {
  45. txtA.Clear();
  46. //避免事件继续向上传递而降低程序性能
  47. e.Handled = true;
  48. }
  49. //当探测命令是否可执行的时候该方法会被调用
  50. private void cb_CanExecute(object sender,CanExecuteRoutedEventArgs e)
  51. {
  52. if (string.IsNullOrEmpty(txtA.Text))
  53. {
  54. e.CanExecute = false;
  55. }
  56. else
  57. {
  58. e.CanExecute = true;
  59. }
  60. //避免事件继续向上传递而降低程序性能
  61. e.Handled = true;
  62. }
  63. }
  64. }

运行程序,在TextBox中输入内容之后,Button在命令可执行状态下变为可用,此时单击按钮或者按Alt+C,TextBox就会被清空,效果如下图:

                                                

对于以上的代码,需要注意以下几点:

第一,使用命令可以避免自己写代码判断Button是否可以用以及添加快捷键。

第二,RountedCommand是一个与业务逻辑无关的类,只负责在程序中跑腿而并不对命令目标进行操作,TextBox并不是由它清空的。那么TextBox的情况操作是谁呢?答案是CommandBinding。因为无论是探测命令是否可以执行还是命令送达目标,都会激发命令目标发送路由事件,这些事件会沿着UI元素树向上传递,最终被CommandBinding所捕捉。本例中CommandBinding被安装在外围的StackPanel上,Commandbinding站在高处起一个侦听器的作用,而且专门针对rouutedCommand命令捕捉与其相关的事件。本例中,当CommandBinding捕捉到CanExecute就会调用cb_CanExecute方法。当捕捉到是Executed的时候,就调用cb_Execute事件。

第三,因为CanExecute事件的激发频率比较高,为了避免降低性能,在处理完毕之后建议将e.Handle设置为true。

第四,CommandBinding一定要设置在命令目标的外围控件上,不然无法捕捉CanExecute和Executed等路由事件。

1.4         WPF中的命令库

上面这个例子中,我们自己声明定义了一个命令:

[csharp] view plaincopyprint?

  1. private RoutedCommand rouutedCommand = new RoutedCommand("Clear",typeof(Window28));

命令具有一处声明,处处使用的特点,比如Save命令,在程序的任何地方它都表示要求命令目标保存数据。因此,微软在WPF类库里面准备了一些便捷的命令库,这些命令库包括:

ApplicationCommands

ComponentCommands

NavigationCommands

MediaCommands

EditingCommands

它们都是静态类,而命令就是由这些静态类的只读属性以单件模式暴露出来的。例如:ApplicationCommands类就包含CancelPrint、Close、ContextMenu、Copy、CorrectionList、Cut、Delete、Find、Help、New、NotACommand、Open、Paste、Print、PrintPreview、Properties、Redo、Replace、Save、SaveAs、SelectAll、Stop、Undo这些命令。它们的源代码如下:

其它几个命令库也与之类似。如果你的程序需要诸如Open,Save,Stop,Play等标准命令,那就没有必要自己声明了,直接拿命令库来用就好了。

1.5         命令参数

前面提到的命令库里面有很多WPF预制命令,如New,Open,Copy,Cut,Paste等。这些命令都是ApplicationCommands类的静态属性,所以它们的实例永远只能有一个,这就引起了一个问题:如果界面上有两个按钮一个用来创建Student档案,一个用来创建Teacher档案。都使用New命令的话,程序应该如何区别新建的是什么档案呢?

答案是使用CommandParameter,命令源一定是实现了ICommandSource接口的对象,而ICommandSource有一个属性就是CommandParameter,如果把命令看作飞向目标的炮弹,那么CommandParameter就相当于装载在炮弹里面的“消息”。下面是程序的实现代码。

XAML代码如下:

[html] view plaincopyprint?

  1. <Window x:Class="WpfApplication1.Window29"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="Window29" Height="278" Width="398">
  5. <Grid>
  6. <Grid.RowDefinitions>
  7. <RowDefinition Height="24" />
  8. <RowDefinition Height="4" />
  9. <RowDefinition Height="24" />
  10. <RowDefinition Height="4" />
  11. <RowDefinition Height="24" />
  12. <RowDefinition Height="4" />
  13. <RowDefinition Height="*" />
  14. </Grid.RowDefinitions>
  15. <!--命令和命令参数-->
  16. <TextBlock  HorizontalAlignment="Left" Name="textBlock1" Text="Name:" VerticalAlignment="Center" Grid.Row="0"/>
  17. <TextBox x:Name="txtName" Margin="60,0,0,0" Grid.Row="0"></TextBox>
  18. <Button Content="New Teacher" Grid.Row="2" Command="New" CommandParameter="Teacher"></Button>
  19. <Button Content="New Student" Grid.Row="4" Command="New" CommandParameter="Student"></Button>
  20. <ListBox Grid.Row="6" x:Name="lbInfos">
  21. </ListBox>
  22. </Grid>
  23. <!--为窗体添加CommandBinding-->
  24. <Window.CommandBindings>
  25. <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed">
  26. </CommandBinding>
  27. </Window.CommandBindings>
  28. </Window>

以上代码有两个地方需要注意:

两个按钮都使用的是New命令,但分别使用的是Student和Teacher做为的参数。

这次是使用XAML代码为窗体添加CommandBinding,Commandbinding的CanExecute和Executed事件处理器写在后台C#代码里:

[csharp] view plaincopyprint?

  1. private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  2. {
  3. if (string.IsNullOrEmpty(txtName.Text))
  4. {
  5. e.CanExecute = false;
  6. }
  7. else
  8. {
  9. e.CanExecute = true;
  10. }
  11. //路由终止,提高系统性能
  12. e.Handled = true;
  13. }
  14. private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
  15. {
  16. if (e.Parameter.ToString() == "Student")
  17. {
  18. this.lbInfos.Items.Add(string.Format("New Student:{0} 好好学习,天天向上。",txtName.Text));
  19. }
  20. else if(e.Parameter.ToString()=="Teacher")
  21. {
  22. this.lbInfos.Items.Add(string.Format("New Teacher:{0} 学而不厌,诲人不倦。", txtName.Text));
  23. }
  24. //路由终止,提高系统性能
  25. e.Handled = true;
  26. }

运行程序,当TextBox中没有内容的时候,两个按钮都不可用;当输入文字后按钮变为可用,单击按钮,ListBox中会添加不同的条目,效果如下图:

  

1.6         命令于Binding结合

初试命令,你可能会想到这样一个问题,控件那么多事件,可以让我们进行各种各样的不同操作,可控件只有一个Command属性、而命令库却有数10种命令,这样怎么可能使用这个唯一的Command属性来调用那么多种命令呢?答案是使用BIndding。前面已经说过,Binding作为一种间接的、不固定的赋值手段,可以让你有机会选择在某个条件下为目标赋特定的值(有时候需要借助Converter)。

例如一个Button所关联的命令有可能根据某些条件而改变,我们可以把代码写成这样:

[html] view plaincopyprint?

  1. <Button x:Name="cmdBtn" Command="{Binding Path=ppp, Source=sss}" Content="Command"></Button>

不过话又说回来了,因为大多数命令按钮都有相对应的图标来表示固定的含义,所以日常工作中一个控件的命令一经确定很少改变。

2.0            近观命令

一般情况下,程序中使用于逻辑无关的RoutedCommand来跑跑龙套就足够了,但为了让程序的结构更加简洁(比如去掉外围的CommandBinding和与之相关的事件处理器),我们常需要定义自己的命令。本节中我们走进WPF命令,先由RoutedCommand入手,再创建自己的命令。

2.1          ICommand接口与RoutedCommand

WPF中的命令是实现了ICommand接口的类。ICommand接口非常简单,只包含两个方法和一个事件:

Execute方法:命令执行,或者说命令执行于命令目标之上。需要注意的是,现实世界中的命令是不会自己执行的,而这里,执行变成了命令的方法,有点拟人化的味道。

CanExecute方法:在执行之前探知命令是否可以执行。

CanExecuteChanged事件:当命令的可执行状态改变的时候,可激发此事件通知其它对象。

RoutedCommand就是一个实现了ICommand接口的类。RoutedCommand在实现ICommand接口时,并未向Execute和CanExecute方法中添加任何逻辑,也就是说,它是通用的、与具体的业务逻辑无关的。怎么理解“与具体的业务逻辑无关这句话呢”?我们从外部和内部两部分来理解。

丛外部来看,我们回顾一下ApplicationCommands命令库里的命令们:

虽然它们都有自己的名字,但它们都是普普通通的RoutedUICommand实例。也就是说,当一个命令到达命令目标之后,具体执行Copy或Cut即业务逻辑不是由命令来决定的,而是由外围的CommandBinding捕获到命令目标受命令激发而发送的路由事件后在其Executed事件处理器中完成的。换句话说,就算你的CommandBInding在捕捉的Copy命令后执行的是清除操作,也与命令无关。

从内部分析,我们就要看看RoutedCommand的源码了。RoutedCommand类与命令执行相关的代码简化如下:

阅读代码我们可以发现,从ICommand接口继承来的Execute并没有被公开(甚至可以说废弃不用了),仅仅是调用新声明的带两个参数的Execute方法,新声明的带两个参数的Execute方法是对外公开的,可以使用第一个参数向命令传递一些数据,第二个参数是命令的目标,如果目标为null,Execute就会把当前拥有焦点的控件当作自己的目标。新的Execute方法会调用命令执行逻辑的核心----ExecuteImpl方法(ExecuteImpl是Execute Implement的缩写),而这个方法内部并没有什么神秘的地方,简要而言就是“借用”命令目标的RaiseEvent把RoutedEvent发送出去。显然这个事件会被外围的CommandBInding捕获然后执行程序员预设的与业务逻辑相关的东西。

最后我们用ButtonBase为例来看看UI元素是如何发送命令的。ButtonBase是在Click发生的时候发送命令的,而Click事件的激发放在OnClick方法里面,ButtonBase的OnClick方法如下:

Button调用了一个.netframeWorke里面的内部类(这个类没有向程序员暴露)CommandHelpers的ExecuteCommandSource方法,并把ButtonBase对象自己作为参数传了进去。如果我们走进ExecuteCommandSource方法内部会发现这个方法实际上是吧传进来的参数当作命令源、调用命令源的ExecuteCore方法(本质上是调用了ExecuteImpl方法),获取命令源的CommandTarget属性值(命令目标)并使命令作用于命令目标之上。

2.2      制定自定义Command

说到自定义命令,我们可以分为两个层次来理解。第一个层次比较浅,指的是当WPF命令库里面没有包含想要的命令时,我们就得声明自己定义的RoutedCommand实例。比如你想让命令目标在命令到达时发出笑声,WPF命令库里面没有这个命令,那就可以自己定一个Laugh的RoutedCommand实例。很难说这是一种真正意义上的自定义命令,这只是对RoutedCommand的使用。第二个层次是指从继承ICommand接口开始,第一自己的命令并把某些业务逻辑包含在命令里,这才称得上是真正意义上的自定义命令。但比较棘手的是,在WPF系统中,命令源(ButtonBase,MenuItem,ListBoxItem,Hyperlink)、RoutedCommand和CommandBinding三者互相依赖的相当紧密。在源码级别上,不但没有将命令相关的方法声明为Virtual以供我们重写,而且还有很多未向程序公开的逻辑(比如对ExecuteCore和CanExecuteCore这些方法的声明和调用)。换句话说,WPF中的命令源和CommandBinding就是专门为RoutedCommand编写的,如果我们想使用自己的ICommand派生类就必须连命令源一起实现(即实现IComamndSource接口)。因此为了简便的使用WPF这套成熟的体系,为了更高效率的“从0开始”打造自己的命令系统,需要我们根据项目的实际情况进行权衡。

既然本节名为自定义命令,那么我们就从ICommand接口开始,打造一个纯手工的自定义命令。

为了简化CommandBinding来处理程序业务逻辑的程序结构,我们可能希望把业务逻辑移入命令的Execute方法内。比如我们可以自定义一个Save的命令,当命令到达命令目标的时候先通过命令目标的IsChanged属性判断命令目标的类容是否已经改变,如果改变,命令可以执行,命令执行会直接调用命令目标的Save方法,驱动命令目标以自己的形式去保存数据。很显然,这回是命令直接在命令目标上起作用了,而不像RoutedCommand那样现在目标上激发出路由事件等外围控件来捕获到路由事件之后“翻过头来”对命令目标加以处理。你可能会问,如果命令目标不包含IsChanged和Save方法怎么办?这就要靠接口来约束了,比如我们在程序中定义这样一个接口:

[csharp] view plaincopyprint?

  1. public interface IView
  2. {
  3. //属性
  4. bool IsChanged { get; set; }
  5. //方法
  6. void SetBinding();
  7. void Refresh();
  8. void Clear();
  9. void Save();
  10. }

并且要求每个接收命令的组件必须实现这个接口,这样可以确保命令可以对其进行操作。

接下来我们实现ICommand接口,创建一个专门作用于IView派生类的命令。

[csharp] view plaincopyprint?

  1. /// <summary>
  2. ///自定义命令
  3. /// </summary>
  4. public class ClearCommand:ICommand
  5. {
  6. //用来判断命令是否可以执行
  7. public bool CanExecute(object parameter)
  8. {
  9. throw new NotImplementedException();
  10. }
  11. //当命令可执行状态发送改变时,应当被激发
  12. public event EventHandler CanExecuteChanged;
  13. //命令执行时,带有与业务相关的Clear逻辑
  14. public void Execute(object parameter)
  15. {
  16. IView view = parameter as IView;
  17. if(view!=null)
  18. {
  19. view.Clear();
  20. }
  21. }
  22. }

命令实现了ICommand接口并继承了CanExecuteChanged事件、CanExecute方法、Execute方法。目前这个命令比较简单,只用到了Execute方法。在实现这个方法时,我们将这个方法唯一的参数作为命令的目标,如果目标是IView接口的派生类则调用其Clear方法---显然我们已经把程序的业务逻辑引入到了命令的Execute方法中。

有了自定义命令,我们使用什么命令源来“发射”它呢?前面说过,wpf中的命令源是专门为RoutedCommand准备的并且不能重写,所以我们只能通过实现ICommandSource接口来创建自己的命令源,代码如下:

[csharp] view plaincopyprint?

  1. /// <summary>
  2. /// MyCommandSource.xaml 的交互逻辑
  3. /// </summary>
  4. public partial class MyCommandSource : UserControl,ICommandSource
  5. {
  6. /// <summary>
  7. /// 继承自ICommand的3个属性
  8. /// </summary>
  9. public ICommand Command
  10. {
  11. get;
  12. set;
  13. }
  14. public object CommandParameter
  15. {
  16. get;
  17. set;
  18. }
  19. public IInputElement CommandTarget
  20. {
  21. get;
  22. set;
  23. }
  24. //在命令目标上执行命令,或者说让命令作用于命令目标
  25. protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  26. {
  27. base.OnMouseLeftButtonDown(e);
  28. if(this.CommandTarget!=null)
  29. {
  30. this.Command.Execute(CommandTarget);
  31. }
  32. }
  33. }

ICommand接口只包含Command,CommandParameter,CommandTarget  3个属性,至于这3个属性直接有什么样的关系就看我们要怎么去实现了。在本例中CommandParameter完全没有被用到,而CommandTarget作为参数传递给了Command的Execute方法。命令不会自己被发出,所以一定要为命令的执行选择一个好的时机,本例中我们在控件左单击的时候执行命令。

现在命令和命令源都有了,还差一个命令目标。应为我们的ClearCommand专门作用于IView派生类,所以合格的ClearCommand命令必须实现IView接口。设计这种既有UI又需要实现接口的类可以先用XAML编辑器实习UI部分在转到后台用C#实现接口,原来很简单,WPF会自动为UI元素类添加partial关键字修饰,XAML代码会被翻译为类的一部分,后台代码是类的一部分,我们可以在后台代码部分自定基类或者实现接口,最终这些代码会被编译到一起。

这个组件的XAML部分如下:

[html] view plaincopyprint?

  1. <UserControl x:Class="WpfApplication1.MniView"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  6. mc:Ignorable="d"
  7. d:DesignHeight="300" d:DesignWidth="300">
  8. <Border CornerRadius="5" BorderBrush="GreenYellow" BorderThickness="2">
  9. <StackPanel>
  10. <TextBox Margin="5" x:Name="txt1"></TextBox>
  11. <TextBox Margin="5" x:Name="txt2"></TextBox>
  12. <TextBox Margin="5" x:Name="txt3"></TextBox>
  13. <TextBox Margin="5" x:Name="txt4"></TextBox>
  14. </StackPanel>
  15. </Border>
  16. </UserControl>

它的后台代码部分如下:

[csharp] view plaincopyprint?

  1. /// <summary>
  2. /// MniView.xaml 的交互逻辑
  3. /// </summary>
  4. public partial class MniView : UserControl,IView
  5. {
  6. //构造器
  7. public MniView()
  8. {
  9. InitializeComponent();
  10. }
  11. //继承自IView的成员们
  12. public bool IsChanged
  13. {
  14. get
  15. {
  16. throw new NotImplementedException();
  17. }
  18. set
  19. {
  20. throw new NotImplementedException();
  21. }
  22. }
  23. public void SetBinding()
  24. {
  25. throw new NotImplementedException();
  26. }
  27. public void Refresh()
  28. {
  29. throw new NotImplementedException();
  30. }
  31. /// <summary>
  32. /// 用于清除内容的业务逻辑
  33. /// </summary>
  34. public void Clear()
  35. {
  36. this.txt1.Clear();
  37. this.txt2.Clear();
  38. this.txt3.Clear();
  39. this.txt4.Clear();
  40. }
  41. public void Save()
  42. {
  43. throw new NotImplementedException();
  44. }
  45. }

因为我们只演示Clear方法被调用,所以其它几个方法没有具体实现。当Clear方法被调用的时候,它的几个TextBox会被清空。

最后把自定义命令,命令源,命令目标集成起来,窗体的XAML代码如下:

[html] view plaincopyprint?

  1. <Window x:Class="WpfApplication1.Window30"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="Window30" Height="300" Width="300" xmlns:my="clr-namespace:WpfApplication1">
  5. <StackPanel>
  6. <my:MyCommandSource x:Name="myCommandSource1">
  7. <TextBlock Text="清除" Width="80" FontSize="16" TextAlignment="Center" Background="LightGreen"></TextBlock>
  8. </my:MyCommandSource>
  9. <my:MniView x:Name="mniView1" />
  10. </StackPanel>
  11. </Window>

本例中使用简单的文本作为命令源的显示内容,实际工作中可以使用图标,按钮或者更复杂的内容来填充它,但要适当更改激发命令的方法。不然你打算在里面放置一个按钮,那么就不要用OnMouseLeftButtonDown的方法来执行命令了,而应该捕获button的Click事件并在事件处理器中执行方法(Mouse事件会被Button吃掉)。

后台C#代码:

[csharp] view plaincopyprint?

  1. /// <summary>
  2. /// Window30.xaml 的交互逻辑
  3. /// </summary>
  4. public partial class Window30 : Window
  5. {
  6. public Window30()
  7. {
  8. InitializeComponent();
  9. ClearCommand clearCommand = new ClearCommand();
  10. this.myCommandSource1.Command = clearCommand;
  11. this.myCommandSource1.CommandTarget = mniView1;
  12. }
  13. }

我们首先创建了一个ClearCommand实例并把它赋值给自定义命令源的Command属性,自定义命令源的CommandTarget属性目标是MiniView的实例。提醒一句:为了讲解清晰才把命令放在这里,正规的方法应该是把命令声明为静态全局的地方供所有对象调用。运行程序,在TextBox里输入然后再单击清除控件,效果如下图:

至此,一个简单的自定义命令就完成了,若想通过Command的CanExecute方法来影响命令源的状态,还需要使用到ICommand和ICommandSource接口的成员组成更复杂的逻辑。

转载请注明出处:http://blog.csdn.net/fwj380891124

时间: 2024-10-07 23:11:13

命令——WPF学习之深入浅出的相关文章

8 WPF学习之深入浅出话属性

转载:http://blog.csdn.net/fwj380891124/article/details/8131080 通过前面的学习,我们已经知道Data Binding是WPF"数据驱动UI"理念的基础.上一章我们将主要的精力放在了Binding的数据源这一端,研究了Binding的Source和Path.本章我们将把目光移向Binding的目标端,研究一下什么样的对象才能作为Binding的Target以及Binding将把数据送往何处. 1.1      属性(Propert

WPF学习之深入浅出话模板

图形用户界面应用程序较之控制台界面应用程序最大的好处就是界面友好.数据显示直观.CUI程序中数据只能以文本的形式线性显示,GUI程序则允许数据以文本.列表.图形等多种形式立体显示. 用户体验在GUI程序设计中起着举足轻重的作用-----用户界面设计成什么样看上去才足够的漂亮?控件如何安排才简单易用并且少犯错误?这些都是设计师需要考虑的问题.WPF系统不但支持传统的Winfrom编程的用户界面和用户体验设计,更支持使用专门的设计工具Blend进行专业设计,同时还推出了以模板为核心的新一代设计理念.

【WPF学习】第三十二章 执行命令

原文:[WPF学习]第三十二章 执行命令 前面章节已经对命令进行了深入分析,分析了基类和接口以及WPF提供的命令库.但尚未例举任何使用这些命令的例子. 如前所述,RoutedUICommand类没有任何硬编码的功能,而是只表达命令,为触发命令,需要有命令源(也可使用代码).为响应命令,需要有命令绑定,命令绑定将执行转发给普遍的事件处理程序. 一.命令源 命令库中的命令始终可用.触发他们的最简单的方法是将它们关联到实现了ICommandSource接口的控件,其中包括继承自ButtonBase类的

【WPF学习】第三十一章 WPF命令模型

原文:[WPF学习]第三十一章 WPF命令模型 WPF命令模型由许多可变的部分组成.总之,它们都具有如下4个重要元素: 命令:命令表示应用程序任务,并且跟踪任务是否能够被执行.然而,命令实际上不包含执行应用程序任务的代码. 命令绑定:每个命令绑定针对用户界面的具体区域,将命令连接到相关的应用程序逻辑.这种分解的设计是非常重要的,因为单个命令可用于应用程序中的多个地方,并且在每个地方具有不同的意义.为处理这一问题,需要将同一命令与不同的命令绑定. 命令源:命令源触发命令.例如,MenuItem和B

WPF学习07:MVVM 预备知识之数据绑定

MVVM是一种模式,而WPF的数据绑定机制是一种WPF内建的功能集,两者是不相关的. 但是,借助WPF各种内建功能集,如数据绑定.命令.数据模板,我们可以高效的在WPF上实现MVVM.因此,我们需要对各种MVVM相关的WPF内建功能集进行了解,才能在扎实的基础上对MVVM进行学习与实践. 本文是WPF学习03:Element Binding的后续,将说明实现数据绑定的三个重点:DataContext INotifyPropertyChanged IValueConverter MVVM简介 MV

WPF学习08:MVVM 预备知识之COMMAND

WPF内建的COMMAND是GOF 提出的23种设计模式中,命令模式的实现. 本文是WPF学习07:MVVM 预备知识之数据绑定的后续,将说明实现COMMAND的三个重点:ICommand  CommandManager InputBindings COMMAND简介 一般情况我们应用设计如下,一个个控件的各类Handler直接关心了如何实现具体的应用逻辑. 借助COMMAND,我们将具体实现的应用逻辑放在COMMAND中实现,控件只需要绑定相应的COMMAND,而无需关心应用逻辑,从而实现界面

WPF学习11:基于MVVM Light 制作图形编辑工具(2)

本文是WPF学习10:基于MVVM Light 制作图形编辑工具(1)的后续 这一次的目标是完成 两个任务. 画布 效果: 画布上,选择的方案是:直接以Image作为画布,使用RenderTargetBitmap绑定为Image的图片源,这样可以为后续的导出图片功能提供很大的便利. 对拖动栏XAML进行如下修改: <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="

【WPF学习】第二十章 内容控件

原文:[WPF学习]第二十章 内容控件 内容控件(content control)是更特殊的控件类型,它们可包含并显示一块内容.从技术角度看,内容控件时可以包含单个嵌套元素的控件.与布局容器不同的是,内容控件只能包含一个子元素,而布局容器主要愿意可以包含任意多个牵头元素. 正如前面所介绍,所有WPF布局容器都继承自抽象类Panel,该类提供了对包含多个元素的支持.类似地,所有内容控件都继承自抽象类ContentControl.下图显示了ContentControl类的层次结构. 图 Conten

linux 命令 及学习进度综合整理

linux  命令  及学习进度综合整理 pwd 查看当前所在位置 cd  跳转到什么什么目录 ls  显示所有文件和目录 ls -l  显示目录详细信息 cd ..  返回上一级 vi  lnany.txt  创建一个文件 vi  .lnany.txt    创建一个隐藏文件 vim 是 vi 的升级版 功能更多 出现 -bash: vim: command not found 的解决办法 i. 那么如何安裝 vim 呢?输入rpm -qa|grep vim 命令, 如果 vim 已经正确安裝