zoukankan      html  css  js  c++  java
  • WPF 命令

    1. WPF命令模型 
      ICommand接口 
      WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理,它包含了两个方法和一个事件: 
      复制代码
      public interface ICommand
      {
          void Execute(object parameter);         //定义在调用此命令时调用的方法。
          bool CanExecute(object parameter);      //此方法返回命令的状态,如果命令可用则返回true,否则返回false.
          event EventHandler CanExecuteChanged;   //当命令状态改变时,引发该事件。
      }
      复制代码

      RoutedCommand类 
      当创建自己的命令时,不会直接实现ICommand接口,而是使用System.Windows.Input.RoutedCommand类。它是WPF中唯一实例了ICommand接口的类,它为事件冒泡和隧道添加了一些额外的基础结构。为了支持路由事件,RoutedCommand类私有地实现了ICommand接口,并且添加了ICommand接口方法的一些不同的版本,最明显的变化是,Execute()和CanExecute()方法使用了一个额外参数。代码示例如下:

      复制代码
      public void Execute(object parameter, IInputElement target)
      { 
      }
      public bool CanExecute(object parameter, IInputElement target) { }
      复制代码

      参数target是开始处理事件的元素,事件从target元素开始,然后冒泡至高层的容器,直到应用程序为了执行合适的任务而处理了事件。 
      RoutedCommand类还引入了三个属性:Name(命令名称)、OwnerType(包含命令的类)及InputGestures集合(可以被用于触发命令的按键或鼠标操作)。

      RoutedUICommand类 
      RoutedUICommand类只增加了一个属性 Text,它是命令显示的文本。在程序中处理的大部分命令不是RoutedCommand对象,而是RoutedUICommand类的实例,RoutedUICommand类继承自RoutedCommand类。而WPF提供的所有预先构建好的命令都是RoutedUICommand对象。RoutedUICommand类用于具有文本的命令,这些文本显示在用户界面中的某些地方(如菜单项文本,工具栏按钮的工具提示)。 
      命令库 
      因为每个应用程序可能都有大量的命令,且对于许多不同的应用程序,很多命令是通用的,为了减少创建这些命令所需要的工作,WPF提供了一个基本命令库,这些命令通过以下5个专门的静态类的静态属性提供: 
      QQ图片20140805000636 
      许多命令对象都是有一个额外的特征:默认输入绑定,例如,ApplicationCommands.Open命令被映射到Ctrl+O组合键,只要将命令绑定到一个命令源,并为窗口添加该命令源,这个组合键就会被激活,即使没有在用户界面的任何地方显示该命令也同样如此。
    2. 命令源 
      命令源是一个实现了ICommandSource接口的控件,它定义了三个属性: 
      QQ图片20140805002559 
      例如,下面的按钮使用Command属性连接到ApplicationCommands.New命令:
      <Button Command="New">New</Button>

      此时,会看到按钮是被禁用的状态,这是因为按钮查询到命令还没有进行操作绑定,命令的状态为不可用,所以按钮也设置为不可用。

    3. 为命令进行操作绑定 
      下在的代码片段为New命令创建绑定,可将这些代码添加到窗口的构造函数中:

      复制代码
      CommandBinding binding = new CommandBinding(ApplicationCommands.New);
      binding.Executed += new ExecutedRoutedEventHandler(binding_Executed);
      this.CommandBindings.Add(binding);
      
      void binding_Executed(object sender, ExecutedRoutedEventArgs e)
      {
          MessageBox.Show("New command triggered by " + e.Source.ToString());
      }
      复制代码

      尽管习惯上为窗口创建所有绑定,但CommandBindings属性实际上是在UIElement基类中定义的,所以任何元素都支持该属性,但为了得到最大的灵活性,命令绑定通常被添加到顶级窗口,如果希望在多个窗口中使用相同的命令,就需要在这些窗口中分别创建命令绑定。 

      以上的命令绑定是使用代码生成的,但,如果希望精简代码隐藏文件,使用XAML以声明方式关联命令也很容易,如下所示:

      <Window.CommandBindings>
          <CommandBinding Command="ApplicationCommands.New" Executed="binding_Executed"></CommandBinding>
      </Window.CommandBindings>
      <Button Command="ApplicationCommands.New">New</Button>
    4. 使用多命令源,如下是为命令New创建了一个MenuItem的命令源:

      <Menu>
          <MenuItem Header="File">
              <MenuItem Command="New"></MenuItem>
          </MenuItem>
      </Menu>

      注意,没有为New命令的MenuItem对象设置Header属性,这是因为MenuItem类足够智能,如果没有设置Header属性,它将从命令中提取文本(Button不具有此特性)。虽然该特性带来的便利看起来很小,但是如果计划使用不同的语言本地化应用程序,这一特性就很重要了。MunuItem类还有另一个功能,它能够自动提取Command.InputBindings集合中的第一个快捷键,对于以上的New命令,在菜单旁边会显示快捷键:Ctrl+N。

    5. 使Button这种不能自动提取命令文本的控件来提取命令文本,有两种技术来重用命令文本,一种是直接从静态的命令对象中提取文本,XAML可以使用Static标记扩展完成这一任务,该方法的问题在于它只是调用命令对象的ToString()方法,因此,得到的是命令的名称,而不是命令的文本。最好的方法是使用数据绑定表达式,以下第二条代码示例绑定表达式绑定到当前元素,获取正在使用的Command对象,并且提取其Text属性:

      <Button Command="ApplicationCommands.New" Content="{x:Static ApplicationCommands.New}"></Button>
      <Button Command="ApplicationCommands.New" Content="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Command.Text}"></Button>
    6. 直接调用命令
      不是只有实现了ICommandSource接口的类才能触发命令的执行,也可以使用Execute()方法直接调用来自任何事件处理程序的方法:

      ApplicationCommands.New.Execute(null,targetElement);

      targetElement是WPF开始查找命令绑定的地方。可以使用包含窗口(具有命令绑定)或嵌套的元素(实际引发事件的元素)。也可以在关联的CommandBinding对象中调用Execute()方法,对于这种情况,不需要提供目标元素,因为会自动公开正在使用的CommandBindings集合的元素设置为目标元素:this.CommandBindings[0].Command.Execute(null);

    7. 禁用命令 
      例如有一个由菜单、工具栏及一个大的文本框构成的文本编辑器的应用程序,该应用程序可以打开文件,创建新的文档以及保存所进行的操作。在应用程序中,只有文本框中的内容发生了变化才启用Save命令,我们可以在代码中使用一个Boolean变量isUpdate来跟踪是否发生了变化。当文本发生了变化时设置标志。

      private bool isUpdate = false;
      private void txt_TextChanged(object sender, TextChangedEventArgs e)
      {
          isUpdate = true;
      }

      现在需要从窗口向命令绑定传递信息,从而使连接的控件可以根据需要进行更新,技巧是处理命令绑定的CanExecute事件,代码如下:

      CommandBinding binding = new CommandBinding(ApplicationCommands.Save); 
      binding.Executed += new ExecutedRoutedEventHandler(binding_Executed); 
      binding.CanExecute += new CanExecuteRoutedEventHandler(binding_CanExecute); 
      this.CommandBindings.Add(binding); 

      或者使用声明方式:

      <CommandBinding Command="Save" Executed="CommandBinding_Executed_1" CanExecute="binding_CanExecute"></CommandBinding>

      在事件处理程序中,只需要检查isUpdate变量,并设置CanExecuteRoutedEventArgs.CanExecute属性:

      void binding_CanExecute(object sender, CanExecuteRoutedEventArgs e) 
      { 
          e.CanExecute = isUpdate; 
      } 

      如果isUpdate的值为false,就会禁用Save命令,否则会启用Save命令。
      当使用CanExecute事件时,是由WPF负责调用RoutedCommand.CanExecute()方法触发事件处理程序,并且确定命令的状态。当WPF命令管理器探测到一个确信是重要的变化时,例如,当焦点从一个控件移动到另一个控件,或者执行了一个命令之后,WPF命令管理器就会完成该工作。控件还能引发CanExecuteChanged事件以通知WPF重新评估命令,例如,当用户在文本框中按下一个键时就会发生该事件,总之,CanExecute事件会被频繁的触发,所以不应当在该事件的处理程序中使用耗时的代码。 然而,其化因素有可能会影响命令的状态,在以上的示例中,为了响应其它操作,isUpdate标志可能会被修改,如果注意到命令状态没有在正确的时间更新,可以强制WPF为所有正在使用的命令调用CanExecute()方法,通过调用静态的CommandManager.InvalidateRequerySuggested()方法完成该工作。然后命令管理器触发RequerySuggested事件,通知窗口中的命令源。然后命令源会查询它们连接的命令并相应地更新它们的状态。

    8. 具有内置命令的控件 
      一些输入控件自身可以处理命令事件,如TextBox类的Cut、Copy及Paste命令,以及一些来自EditingCommand类的用于选择文本以及将光标移到不同位置的命令,把此类命令绑定到命令源会自动获取对应命令的功能,而不需要再为命令绑定操作。如:

      <ToolBar>    
          <Button Command="Cut" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button> 
          <Button Command="Copy" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button> 
          <Button Command="Paste" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button> 
      </ToolBar> 

      此外,文本框还处理了CanExecute事件,如果在文本框中当前没有选中任何内容,剪切和复制命令就会被禁用,当焦点改变到其他不支持这些命令的控件时,这些命令都会被禁用。 
      在以上代码中使用了ToolBar控件,它提供了一些内置逻辑,可以将它的子元素的CommandTarget属性自动设置为具有焦点的控件。但如果在不同的容器(不是ToolBar或Menu控件)中放置按钮,就不会得到这一优点而按钮不能正常工作,此时就需要手动设置CommandTarget属性,为此,必须使用命名目标元素的绑定表达式。如:

      复制代码
      <StackPanel Grid.Row="1"> 
          <Button Command="Cut" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/> 
          <Button Command="Copy" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/> 
          <Button Command="Paste" CommandTarget="{Binding ElementName=txt}" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"/> 
      </StackPanel>
      复制代码

      另一个较简单的选择是使用FocusManager.IsFocusScope附加属性创建新的焦点范围,当命令触发时,该焦点范围会通知WPF在父元素的焦点范围中查找元素:

      <StackPanel FocusManager.IsFocusScope="True" Grid.Row="1"> 
          <Button Command="Cut" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button> 
          <Button Command="Copy" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button> 
          <Button Command="Paste" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}"></Button> 
      </StackPanel> 

      在有些情况下,可能发现控件支持内置命令,但不想启用它,此时有三种方法可以禁用命令:

      1. 理想情况下,控件会提供用于关闭命令支持的属性,例如TextBox控件的IsUndoEnabled属性。

      2. 如果控件没有提供关闭命令支持的属性,还可以为希望禁用的命令添加一个新的命令绑定,然后该命令绑定可以提供新的事件处理程序。且总是将CanExecute属性设置为false,下面是一个使用该技术删除文本框Cut特性支持的示例:

        复制代码
        CommandBinding binding = new CommandBinding(ApplicationCommands.Cut, null, SuppressCommand); 
        this.CommandBindings.Add(binding); 
        private void SuppressCommand(object sender, CanExecuteRoutedEventArgs e) 
        { 
            e.CanExecute= false; 
            e.Handled= true; 
        } 
        复制代码

        上面的代码设置了Handled标志,以阻止文本框自我执行计算,而文本框可能将CanExecute属性设置为true.

      3. 使用InputBinding集合删除触发命令的输入,例如,可以使用代码禁用触发TextBox控件中Cut命令的Ctrl+X组合键,如下所示:

        KeyBinding keyBinding = new KeyBinding(ApplicationCommands.NotACommand, Key.X, ModifierKeys.Control); 
        txt.InputBindings.Add(keyBinding); 

        ApplicationCommands.NotACommand命令不做任何事件,它专门用于禁用输入绑定。 
        文本框默认显示上下文菜单,可以通过将ContextMenu属性设置为null删除上下文本菜单

        <TextBoxGrid.Row="3" Name="txt" ContextMenu="{x:Null}" TextWrapping="Wrap" TextChanged="txt_TextChanged" />
    9. 自定义命令 
      下面的示例定义了一个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", typeof(DataCommands), inputs); 
          } 
         public static RoutedUICommand Requery   //通过静态属性提供自定义的命令 
          { 
             get { return requery; } 
          } 
      } 
      复制代码

      使用Requery命令时需要将它的.Net名称空间映射为一个XML名称空间,XAML代码如下:  

      复制代码
      <Window x:Class="WpfApplication1.Test4" 
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
              xmlns:local="clr-namespace:WpfApplication1" 
              Title="Test4" Height="300" Width="300"> 
          <Window.CommandBindings> 
              <CommandBinding Command="local:DataCommands.Requery" Executed="Requery_Executed"></CommandBinding> 
          </Window.CommandBindings> 
          <Grid> 
              <Button Command="local:DataCommands.Requery" CommandParameter="ai" Content="{Binding RelativeSource={RelativeSource Mode=Self},Path=Command.Text}" /> 
          </Grid> 
      </Window>
      复制代码

      在以上代码中使用CommandParameter为命令传递了参数,命令的事件处理方法中就可以使用Parameter属性获取该参数:

      private void Requery_Executed(object sender, ExecutedRoutedEventArgs e) 
      { 
          string parameters = e.Parameter.ToString(); 
      }  
    10. 在不同的位置使用相同的命令 
      在WPF命令模型中,一个重要的思想是Scope。尽管每个命令只有一个副本,但是使用命令的效果却会根据触发命令位置而不同,例如,如果有两个文本框,它们都支持Cut、Copy、Paste命令,操作只会在当前具有焦点的文本框中发生。但是对于自定实现的命令如New、Open、Requery及Save命令就区分不出是哪一个文本框触发的命令,尽管ExecuteRoutedEventArgs对象提供了Source属性,但是该属性反映的是具有命令绑定的元素,也就是容器窗口。此问题的解决方法是使用文本框的CommandBindings集合为每个文本框分别绑定命令。

    11. 跟踪和翻转命令
      创建自己的用于支持命令翻转的数据结构,示例中定义一个名为CommandHistoryItem的类用于存储命令状态: 

      复制代码
      public class CommandHistoryItem 
      { 
          public string CommandName { get; set; }             //命令名称 
          public UIElement ElementActedOn { get; set; }       //执行命令的元素 
          public string PropertyActedOn { get; set; }         //在目标元素中被改变了的属性 
          public object PreviousState { get; set; }           //用于保存受影响元素以前状态的对象
          public CommandHistoryItem(string commandName) 
              : this(commandName, null, "", null) 
          {
          }
      
          public CommandHistoryItem(string commandName, UIElement elementActedOn, string propertyActed, object previousState) 
          { 
              this.CommandName = commandName; 
              this.ElementActedOn = elementActedOn; 
              this.PropertyActedOn = propertyActed; 
              this.PreviousState = previousState; 
          }
      
          public bool CanUndo 
          { 
              get { return (ElementActedOn != null && PropertyActedOn != ""); } 
          }
      
          /// <summary> 
          /// 使用反射为修改过的属性应用以前的值 
          /// </summary> 
          public void Undo() 
          { 
              Type elementType = ElementActedOn.GetType(); 
              PropertyInfo property = elementType.GetProperty(PropertyActedOn); 
              property.SetValue(ElementActedOn, PreviousState, null); 
          } 
      }
      复制代码

      需要自定义一个执行应用程序范围内翻转操作的命令,如下所示:

      复制代码
      private static RoutedUICommand applicationUndo;
      public static RoutedUICommand ApplicationUndo 
      { 
          get { return applicationUndo; } 
      }
      
      static ApplicationUndoDemo() 
      { 
          applicationUndo = new RoutedUICommand("Applicaion Undo", "ApplicationUndo", typeof(ApplicationUndoDemo)); 
      }
      复制代码

      可以使用CommandManager类来跟踪任何命令的执行情况,它提供了几个静态事件:Executed及PreviewExecuted,无论何时,当执行任何一个命令时都会触发它们。 尽管CommandManager类挂起了Executed事件,但是仍然可以使用UIElement.AddHandler()方法关联事件处理程序,并且为可选的第三个参数传递true值,从而允许接收事件。下面的代码在窗口的构造函数中关联PreviewExecuted事件处理程序,且在关闭窗口时解除关联: 

      复制代码
      public ApplicationUndoDemo()
      {
          InitializeComponent();
          this.AddHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandPreviewExecute),true);
      }
      
      private void Window_Unloaded(object sender, RoutedEventArgs e)
      {
          this.RemoveHandler(CommandManager.PreviewExecutedEvent, new ExecutedRoutedEventHandler(CommandPreviewExecute));
      }
      复制代码

      当触发PreviewExecute事件时,需要确定准备执行的命令是否是我们所关心的,如果是就创建CommandHistoryItem对象,且将其添加到历史命令集合中。

      复制代码
      private void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
      {
          // Ignore menu button source.
          if (e.Source is ICommandSource) return;
      
          // Ignore the ApplicationUndo command.
          if (e.Command == MonitorCommands.ApplicationUndo) return;
      
          // Could filter for commands you want to add to the stack
          // (for example, not selection events).
      
          TextBox txt = e.Source as TextBox;
          if (txt != null)
          {
              RoutedCommand cmd = (RoutedCommand)e.Command;
                      
              CommandHistoryItem historyItem = new CommandHistoryItem(
                  cmd.Name, txt, "Text", txt.Text);
      
              ListBoxItem item = new ListBoxItem();
              item.Content = historyItem;
              lstHistory.Items.Add(historyItem);
      
              // CommandManager.InvalidateRequerySuggested();
          }
      }
      复制代码

      使用CanExecute事件处理程序,确保只有当Undo历史中有一项时,才能执行翻转操作: 

      <Window.CommandBindings>
          <CommandBinding Command="local:ApplicationUndoDemo.ApplicationUndo" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"></CommandBinding>
      </Window.CommandBindings>
      复制代码
      private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) 
      { 
          //不知lstHistory.Items[lstHistory.Items.Count - 1]为什么强制转化不成CommandHistoryItem,有待解决 
          CommandHistoryItem historyItem = (CommandHistoryItem)lstHistory.Items[lstHistory.Items.Count - 1]; 
          if (historyItem.CanUndo) 
              historyItem.Undo(); 
          lstHistory.Items.Remove(historyItem); 
      }
      
      private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) 
      { 
          if (lstHistory == null || lstHistory.Items.Count == 0) 
              e.CanExecute = false; 
          else 
              e.CanExecute = true; 
      }
      复制代码
       
      https://www.cnblogs.com/jiao1855/p/3891229.html
  • 相关阅读:
    解决<c:if>无else的问题
    极限挑战----3小时完成OA系统(失败)
    jstl的if标签和forEach标签的解析
    EL表达式
    JSP静态包含和动态包含的区别
    JSP九大隐式对象和四大域对象-----面试
    mybatis学习01--基础认识
    怎样将本地的代码上传到github
    随机数的生成,静态导入,可变参数的方法,方法重载,浮点数的比较
    zookeeper学习2
  • 原文地址:https://www.cnblogs.com/sjqq/p/8459549.html
Copyright © 2011-2022 走看看