深入解析WPF命令模式

深入解析WPF命令模式

一、引言

WPF命令相对来说是一个崭新的概念,因为命令对于之前的WinForm根本没有实现这个概念,但是这并不影响我们学习WPF命令,因为设计模式中有命令模式,关于命令模式可以参考我设计模式的博文:http://www.cnblogs.com/zhili/p/CommandPattern.html。命令模式的要旨在于把命令的发送者与命令的执行者之间的依赖关系分割开了。对此,WPF中的命令也是一样的,WPF命令使得命令源(即命令发送者,也称调用程序)和命令目标(即命令执行者,也称处理程序)分离。现在是不是感觉命令是不是亲切了点了呢?下面就详细分享下我对WPF命令的理解。

二、命令是什么呢?

上面通过命令模式引出了WPF命令的要旨,那在WPF中,命令是什么呢?对于程序来说,命令就是一个个任务,例如保存,复制,剪切这些操作都可以理解为一个个命令。即当我们点击一个复杂按钮时,此时就相当于发出了一个复制的命令,即告诉文本框执行一个复杂选中内容的操作,然后由文本框控件去完成复制的操作。在这里,复杂按钮就相当于一个命令发送者,而文本框就是命令的执行者。它们之间通过命令对象分割开了。如果采用事件处理机制的话,此时调用程序与处理程序就相互引用了。

所以对于命令只是从不同角度理解问题的一个词汇,之前理解点击一个按钮,触发了一个点击事件,在WPF编程中也可以理解为触发了一个命令。说到这里,问题又来了,WPF中既然有了命令了?那为什么还需要路由事件呢?对于这个问题,我的理解是,事件和命令是处理问题的两种方式,它们之间根本不存在冲突的,并且WPF命令中使用了路由事件。所以准确地说WPF命令应该是路由命令。那为什么说WPF命令是路由的呢?这个疑惑将会在WPF命令模型介绍中为大家解答。

另外,WPF命令除了使命令源和命令目标分割的优点外,它还具有另一个优点:

使得控件的启用状态和相应的命令状态保持同步,即命令被禁用时,此时绑定命令的控件也会被禁用。

三、WPF命令模型

经过前面的介绍,大家应该已经命令了WPF命令吧。即命令就是一个操作,任务。接下来就要详细介绍了WPF命令模型了。
  WPF命令模型具有4个重要元素:

命令——命令表示一个程序任务,并且可跟踪该任务是否能被执行。然而,命令实际上不包含执行应用程序的代码,真正处理程序在命令目标中。
命令源——命令源触发命令,即命令的发送者。例如Button、MenuItem等控件都是命令源,单击它们都会执行绑定的命令。
命令目标——命令目标是在其中执行命令的元素。如Copy命令可以在TextBox控件中复制文本。
命令绑定——前面说过,命令是不包含执行程序的代码的,真正处理程序存在于命令目标中。那命令是怎样映射到处理程序中的呢?这个过程就是通过命令绑定来完成的,命令绑定完成的就是红娘牵线的作用。
  WPF命令模型的核心就在于ICommand接口了,该接口定义命令的工作原理。该接口的定义如下所示:
  WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理。该接口包含两个方法和一个事件:

3.1 ICommand

public interface ICommand
{
    void Execute(object parameter);
    bool CanExecute(object parameter);

    event EventHandler CanExecuteChanged;
}

该接口包括2个方法和一个事件。CanExecute方法返回命令的状态——指示命令是否可执行,例如,文本框中没有选择任何文本,此时Copy命令是不用的,CanExecute则返回为false。

Execute方法就是命令执行的方法,即处理程序。当命令状态改变时,会触发CanExecuteChanged事件。

  • 当自定义命令时,不会直接去实现ICommand接口。

  • 而是使用RoutedCommand类,该类实是WPF中唯一现了ICommand接口的类。

  • 所有WPF命令都是RoutedCommand类或其派生类的实例。

  • 然而程序中处理的大部分命令不是RoutedCommand对象,而是RoutedUICommand对象。

  • RoutedUICommand类派生与RoutedCommand类。

3.2 RoutedCommand

System.Windows.Input.RoutedCommand

#region 程序集 PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\PresentationCore.dll
#endregion

using System.ComponentModel;
using System.Security;
using System.Windows.Markup;

namespace System.Windows.Input
{
    //
    // 摘要:
    //     定义一个实现 System.Windows.Input.ICommand 并在元素树之内进行路由的命令。
    [TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
    [ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
    public class RoutedCommand : ICommand
    {
        //
        // 摘要:
        //     初始化 System.Windows.Input.RoutedCommand 类的新实例。
        public RoutedCommand();
        //
        // 摘要:
        //     使用指定的名称和所有者类型初始化 System.Windows.Input.RoutedCommand 类的新实例。
        //
        // 参数:
        //   name:
        //     用于序列化的已声明名称。
        //
        //   ownerType:
        //     正在注册命令的类型。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     name 为 null。
        //
        //   T:System.ArgumentException:
        //     ownerType 为 null。
        public RoutedCommand(string name, Type ownerType);
        //
        // 摘要:
        //     使用指定的名称、所有者类型和笔势集合初始化 System.Windows.Input.RoutedCommand 类的新实例。
        //
        // 参数:
        //   name:
        //     用于序列化的已声明名称。
        //
        //   ownerType:
        //     正在注册命令的类型。
        //
        //   inputGestures:
        //     与此命令关联的默认输入笔势。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     name 为 null。
        //
        //   T:System.ArgumentException:
        //     name 的长度为零。 或 - ownerType 为 null。
        public RoutedCommand(string name, Type ownerType, InputGestureCollection inputGestures);

        //
        // 摘要:
        //     获取命令的名称。
        //
        // 返回结果:
        //     命令的名称。
        public string Name { get; }
        //
        // 摘要:
        //     获取使用命令注册的类型。
        //
        // 返回结果:
        //     命令所有者的类型。
        public Type OwnerType { get; }
        //
        // 摘要:
        //     获取与此命令关联的 System.Windows.Input.InputGesture 对象的集合。
        //
        // 返回结果:
        //     输入笔势。
        public InputGestureCollection InputGestures { get; }

        //
        // 摘要:
        //     当命令管理器检测到对命令源所进行的更改时发生。 这些更改通常影响是否应对当前命令目标执行命令。
        public event EventHandler CanExecuteChanged;

        //
        // 摘要:
        //     确定此 System.Windows.Input.RoutedCommand 在其当前状态是否可以执行。
        //
        // 参数:
        //   parameter:
        //     用户定义的数据类型。
        //
        //   target:
        //     命令目标。
        //
        // 返回结果:
        //     如果可以对当前命令目标执行此命令,则为 true;否则为 false。
        //
        // 异常:
        //   T:System.InvalidOperationException:
        //     target 不是 System.Windows.UIElement 或 System.Windows.ContentElement。
        [SecurityCritical]
        public bool CanExecute(object parameter, IInputElement target);
        //
        // 摘要:
        //     对当前命令目标执行 System.Windows.Input.RoutedCommand。
        //
        // 参数:
        //   parameter:
        //     要传递到处理程序的用户定义的参数。
        //
        //   target:
        //     要在其中查找命令处理程序的元素。
        //
        // 异常:
        //   T:System.InvalidOperationException:
        //     target 不是 System.Windows.UIElement 或 System.Windows.ContentElement。
        [SecurityCritical]
        public void Execute(object parameter, IInputElement target);
    }
}

3.3 RoutedUICommand

**System.Windows.Input.RoutedUICommand **

#region 程序集 PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\PresentationCore.dll
#endregion

using System.ComponentModel;

namespace System.Windows.Input
{
    //
    // 摘要:
    //     定义一个在元素树中路由并包含一个文本属性的 System.Windows.Input.ICommand。
    [TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
    public class RoutedUICommand : RoutedCommand
    {
        //
        // 摘要:
        //     初始化 System.Windows.Input.RoutedUICommand 类的新实例。
        public RoutedUICommand();
        //
        // 摘要:
        //     使用指定的说明性文本、声明的名称和所有者类型初始化 System.Windows.Input.RoutedUICommand 类的新实例。
        //
        // 参数:
        //   text:
        //     命令的说明性文本。
        //
        //   name:
        //     用于序列化的命令的声明名称。
        //
        //   ownerType:
        //     正在注册命令的类型。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     name 为 null。
        //
        //   T:System.ArgumentException:
        //     ownerType 为 null。
        public RoutedUICommand(string text, string name, Type ownerType);
        //
        // 摘要:
        //     使用指定的说明性文本、声明的名称、所有者类型和输入笔势初始化 System.Windows.Input.RoutedUICommand 类的新实例。
        //
        // 参数:
        //   text:
        //     命令的说明性文本。
        //
        //   name:
        //     用于序列化的命令的声明名称。
        //
        //   ownerType:
        //     正在注册命令的类型。
        //
        //   inputGestures:
        //     要与命令关联的笔势集合。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     name 为 null。
        //
        //   T:System.ArgumentException:
        //     ownerType 为 null。
        public RoutedUICommand(string text, string name, Type ownerType, InputGestureCollection inputGestures);

        //
        // 摘要:
        //     获取或设置描述该命令的文本。
        //
        // 返回结果:
        //     描述该命令的文本。 默认值为一个空字符串。
        public string Text { get; set; }
    }
}

接下来介绍下为什么说WPF命令是路由的呢?
  实际上,RoutedCommand上Execute和CanExecute方法并没有包含命令的处理逻辑,而是将触发遍历元素树的事件来查找具有CommandBinding的对象。
  而真正命令的处理程序包含在CommandBinding的事件处理程序中。所以说WPF命令是路由命令。该事件会在元素树上查找CommandBinding对象,然后去调用CommandBinding的CanExecute和Execute来判断是否可执行命令和如何执行命令。那这个查找方向是怎样的呢?对于位于工具栏、菜单栏或元素的FocusManager.IsFocusScope设置为”true“是从元素树上根元素(一般指窗口元素)向元素方向向下查找,对于其他元素是验证元素树根方向向上查找。

WPF中提供了一组已定义命令,命令包括以下类:ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands 以及ComponentCommands。 这些类提供诸如 Cut、BrowseBack、BrowseForward、Play、Stop 和 Pause 等命令。

3.4 CommandBinding

System.Windows.Input.CommandBinding

#region 程序集 PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\PresentationCore.dll
#endregion


namespace System.Windows.Input
{
    //
    // 摘要:
    //     将 System.Windows.Input.RoutedCommand 绑定到实现该命令的事件处理程序。
    public class CommandBinding
    {
        //
        // 摘要:
        //     初始化 System.Windows.Input.CommandBinding 类的新实例。
        public CommandBinding();
        //
        // 摘要:
        //     使用指定的 System.Windows.Input.CommandBinding 初始化 System.Windows.Input.ICommand 类的新实例。
        //
        // 参数:
        //   command:
        //     新的 System.Windows.Input.RoutedCommand 所基于的命令。
        public CommandBinding(ICommand command);
        //
        // 摘要:
        //     使用指定的 System.Windows.Input.CommandBinding 和指定的 System.Windows.Input.ICommand
        //     事件处理程序初始化 System.Windows.Input.CommandBinding.Executed 类的新实例。
        //
        // 参数:
        //   command:
        //     新的 System.Windows.Input.RoutedCommand 所基于的命令。
        //
        //   executed:
        //     新 System.Windows.Input.CommandBinding.Executed 上的 System.Windows.Input.RoutedCommand
        //     事件的处理程序。
        public CommandBinding(ICommand command, ExecutedRoutedEventHandler executed);
        //
        // 摘要:
        //     使用指定的 System.Windows.Input.CommandBinding 和指定的 System.Windows.Input.ICommand
        //     及 System.Windows.Input.CommandBinding.Executed 事件处理程序初始化 System.Windows.Input.CommandBinding.CanExecute
        //     类的新实例。
        //
        // 参数:
        //   command:
        //     新的 System.Windows.Input.RoutedCommand 所基于的命令。
        //
        //   executed:
        //     新 System.Windows.Input.CommandBinding.Executed 上的 System.Windows.Input.RoutedCommand
        //     事件的处理程序。
        //
        //   canExecute:
        //     新 System.Windows.Input.CommandBinding.CanExecute 上的 System.Windows.Input.RoutedCommand
        //     事件的处理程序。
        public CommandBinding(ICommand command, ExecutedRoutedEventHandler executed, CanExecuteRoutedEventHandler canExecute);

        //
        // 摘要:
        //     获取或设置与此 System.Windows.Input.CommandBinding 关联的 System.Windows.Input.ICommand。
        //
        // 返回结果:
        //     与此绑定关联的命令。
        [Localizability(LocalizationCategory.NeverLocalize)]
        public ICommand Command { get; set; }

        //
        // 摘要:
        //     执行与此 System.Windows.Input.CommandBinding 相关联的命令时发生。
        public event ExecutedRoutedEventHandler PreviewExecuted;
        //
        // 摘要:
        //     执行与此 System.Windows.Input.CommandBinding 相关联的命令时发生。
        public event ExecutedRoutedEventHandler Executed;
        //
        // 摘要:
        //     当与该 System.Windows.Input.CommandBinding 关联的命令启动检查以确定是否可以在当前命令目标上执行此命令时发生。
        public event CanExecuteRoutedEventHandler PreviewCanExecute;
        //
        // 摘要:
        //     在与此 System.Windows.Input.CommandBinding 关联的命令开始检查能否对命令目标执行该命令时发生。
        public event CanExecuteRoutedEventHandler CanExecute;
    }
}

四、使用命令

前面都是介绍了一些命令的理论知识,下面介绍了如何使用WPF命令来完成任务。XAML具体实现代码如下所示:

<Window x:Class="WPFCommand.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="300">
    <!--定义窗口命令绑定,绑定的命令是New命令,处理程序是NewCommand-->
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand"/>
    </Window.CommandBindings>
    
    <StackPanel>
        <Menu>
            <MenuItem Header="File">
                <!--WPF内置命令都可以采用其缩写形式-->
                <MenuItem Command="New"></MenuItem>
            </MenuItem>
        </Menu>
        
        <!--获得命令文本的两种方式-->
        <!--直接从静态的命令对象中提取文本-->
        <Button Margin="5" Padding="5" Command="ApplicationCommands.New" ToolTip="{x:Static ApplicationCommands.New}">New</Button>

        <!--使用数据绑定,获得正在使用的Command对象,并提取其Text属性-->
        <Button Margin="5" Padding="5" Command="ApplicationCommands.New" Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"/>
        <Button Margin="5" Padding="5" Visibility="Visible" Click="cmdDoCommand_Click" >DoCommand</Button>
    </StackPanel>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFCommand
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

             后台代码创建命令绑定
            //CommandBinding bindingNew = new CommandBinding(ApplicationCommands.New);
            //bindingNew.Executed += NewCommand;
             将创建的命令绑定添加到窗口的CommandBindings集合中
            //this.CommandBindings.Add(bindingNew);
        }

        private void NewCommand(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New 命令被触发了,命令源是:" + e.Source.ToString());
        }

        private void cmdDoCommand_Click(object sender, RoutedEventArgs e)
        {
            // 直接调用命令的两种方式
            ApplicationCommands.New.Execute(null, (Button)sender);

            //this.CommandBindings[0].Command.Execute(null);
        }

    }
}

在这里插入图片描述

五、自定义命令

在开发过程中,自然少不了自定义命令来完成内置命令所没有提供的任务。下面通过一个例子来演示如何创建一个自定义命令。

首先,定义一个Requery命令,具体的实现如下所示:

public class DataCommands
    {
        private static RoutedUICommand requery;
        static DataCommands()
        {
            InputGestureCollection inputs = new InputGestureCollection();
            inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
            requery = new RoutedUICommand(
              "Requery", "Requery", typeof(DataCommands), inputs);
        }
         
        public static RoutedUICommand Requery
        {
            get { return requery; }
        }
    }

上面代码实现了一个Requery命令,为了演示效果,我们需要把该命令应用到XAML标签上,具体的XAML代码如下所示:

<!--要使用自定义命令,首先需要将.NET命名空间映射为XAML名称空间,这里映射的命名空间为local-->
    <Window x:Class="WPFCommand.CustomCommand"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
       
        xmlns:local="clr-namespace:WPFCommand" 
        Title="CustomCommand" Height="300" Width="300" >
       
    <Window.CommandBindings>
        <!--定义命令绑定-->
        <CommandBinding Command="local:CustomCommands.Requery" Executed="RequeryCommand_Execute"/>
    </Window.CommandBindings>
    <StackPanel>
        <!--应用命令-->
        <Button Margin="5" Command="local:CustomCommands.Requery" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"></Button>
    </StackPanel>
</Window>

在这里插入图片描述

六、实现可撤销的命令程序

WPF命令模型缺少的一个特征就是Undo命令,尽管提供了一个ApplicationCommands.Undo命令,但是该命令通常被用于编辑控件,如TextBox控件。如果希望支持应用程序范围内的Undo操作,就需要在内部跟踪以前的命令,并且触发Undo操作时还原该命令。这个实现原理就是保持用一个集合对象保存之前所有执行过的命令,当触发Undo操作时,还要上一个命令的状态。这里除了需要保存执行过的命令外,还需要保存触发命令的控件以及状态,所以我们需要抽象出一个类来保存这些属性,我们取名这个类为CommandHistoryItem。为了保存命令和命令的状态,自然就需要在完成命令之前进行保存,所以自然联想到是否有Preview之类的事件呢?实际上确实有,这个事件就是PreviewExecutedEvent,所以我们需要在窗口加载完成后把这个事件注册到窗口上,这里在触发这个事件的时候就可以保存即将要执行的命令、命令源和命令源的内容。另外,之前的命令自然需要保存到一个列表中,这里使用ListBox控件作为这个列表,如果不希望用户在界面上看到之前的命令列表的话,也可以使用List等集合容器。

上面讲解完了主要实现思路之后,下面我们梳理下实现思路:

抽象一个CommandHistoryItem来保存命令相关的属性。
注册PreviewExecutedEvent事件,为了在命令执行完之前保存命令、命令源以及命令源当前的状态。
在PreviewExecutedEvent事件处理程序中,把命令相关属性添加到ListBox列表中。
当执行撤销操作时,可以从ListBox.Items列表中取出上一个执行的命令进行恢复之前命令的状态。
  有了上面的实现思路之后,实现这个可撤销的命令程序也就是码代码的过程了。具体的后台代码实现如下所示:

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WPFCommand
{
    /// <summary>
    /// CommandsMonitor.xaml 的交互逻辑
    /// </summary>
    public partial class CommandsMonitor : Window
    {
        private static RoutedUICommand undo;
        public static RoutedUICommand Undo
        {
            get { return CommandsMonitor.undo; }
        }

        static CommandsMonitor()
        {
            undo = new RoutedUICommand("Undo", "Undo", typeof(CommandsMonitor));
        }

        public CommandsMonitor()
        {
            InitializeComponent();
            // 按下菜单栏按钮时,PreviewExecutedEvent事件会被触发2次,即CommandExecuted事件处理程序被触发了2次
            // 一次是菜单栏按钮本身,一次是目标源触发命令的执行,所以在CommandExecuted要过滤掉不关心的命令源
            this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted));
        }

        public void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            // 过滤掉命令源是菜单按钮的,因为我们只关心Textbox触发的命令
            if (e.Source is ICommandSource)
                return;
            // 过滤掉Undo命令
            if (e.Command == CommandsMonitor.Undo)
                return;

            TextBox txt = e.Source as TextBox;
            if (txt != null)
            {
                RoutedCommand cmd = e.Command as RoutedCommand;
                if (cmd != null)
                {
                    CommandHistoryItem historyItem = new CommandHistoryItem()
                    {
                        CommandName = cmd.Name,
                        ElementActedOn = txt,
                        PropertyActedOn = "Text",
                        PreviousState = txt.Text
                    };

                    ListBoxItem item = new ListBoxItem();
                    item.Content = historyItem;
                    lstHistory.Items.Add(item);
                }

            }
        }

        private void window_Unloaded(object sender, RoutedEventArgs e)
        {
            this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandExecuted));
        }

        private void UndoCommand_Executed(object sender, RoutedEventArgs e)
        {
            ListBoxItem item = lstHistory.Items[lstHistory.Items.Count - 1] as ListBoxItem;

            CommandHistoryItem historyItem = item.Content as CommandHistoryItem;
            if (historyItem == null)
            {
                return;
            }
                
            if (historyItem.CanUndo)
            {
                historyItem.Undo();
            }
            lstHistory.Items.Remove(item);
        }

        private void UndoCommand_CanExecuted(object sender, CanExecuteRoutedEventArgs e)
        {
            if (lstHistory == null || lstHistory.Items.Count == 0)
            {
                e.CanExecute = false;
            }
            else
            {
                e.CanExecute = true;
            }
        }
    }

    public class CommandHistoryItem
    {
        public String CommandName { get; set; }
        public UIElement ElementActedOn { get; set; }

        public string PropertyActedOn { get; set; }

        public object PreviousState { get; set; }

        public bool CanUndo
        {
            get { return (ElementActedOn != null && PropertyActedOn != ""); }
        }

        public void Undo()
        {
            Type elementType = ElementActedOn.GetType();
            PropertyInfo property = elementType.GetProperty(PropertyActedOn);
            property.SetValue(ElementActedOn, PreviousState, null);
        }
    }
}

其对应的XAML界面设计代码如下所示:

<Window x:Class="WPFCommand.CommandsMonitor"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="CommandsMonitor" Height="300" Width="350"
        xmlns:local="clr-namespace:WPFCommand"
        Unloaded="window_Unloaded">
    <Window.CommandBindings>
        <CommandBinding Command="local:CommandsMonitor.Undo"
                        Executed="UndoCommand_Executed"
                        CanExecute="UndoCommand_CanExecuted"/>
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <ToolBarTray Grid.Row="0">
            <ToolBar>
                <Button Command="ApplicationCommands.Cut">Cut</Button>
                <Button Command="ApplicationCommands.Copy">Copy</Button>
                <Button Command="ApplicationCommands.Paste">Paste</Button>
            </ToolBar>
            <ToolBar>
                <Button Command="local:CommandsMonitor.Undo">Reverse Last Command</Button>
            </ToolBar>
        </ToolBarTray>

        <TextBox Margin="5" Grid.Row="1"
             TextWrapping="Wrap" AcceptsReturn="True">
        </TextBox>
        <TextBox Margin="5" Grid.Row="2"
             TextWrapping="Wrap" AcceptsReturn="True">
        </TextBox>

        <ListBox Grid.Row="3" Name="lstHistory" Margin="5" DisplayMemberPath="CommandName"></ListBox>
    </Grid>
</Window>

七、小结

到这里,WPF命令的内容就介绍结束了,关于命令主要记住命令模型四要素——命令、命令绑定、命令源和命令目标。后面继续为大家分享WPF的资源和样式的内容。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是刘彦宏吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值