WPF命令

Posted lonelyxmas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF命令相关的知识,希望对你有一定的参考价值。

原文:WPF命令

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

命令是为了解除UI和交互逻辑代码间耦合,以前winform的时候,界面控件通过事件绑定代码,这样界面和代码就无法拆开。

1、WPF命令模型。

WPF命令模型是由许多可变的部分组成,但主要的部分有以下4个重要元素。

1.1)、命令。

命令表示应用程序任务,并且跟踪任务是否能够被执行,然而,命令实际上不包含执行应用程序任务的代码。

1.2)、命令绑定。

每个命令绑定针对用户界面的具体区域,将命令连接到相关的应用程序逻辑。

1.3)、命令源。

命令源触发命令,例如MenuItem和Button都是源对象,单击他们都会执行绑定命令。

1.4)、命令目标。

命令目标是在其中执行命令的元素。

2、ICommand接口。

 WPF命令模型的核心是System.WIndows.Input.ICommand接口,该接口中包含了两个方法和一个事件。

public interface ICommand
{  
    event EventHandler CanExecuteChanged; //当命令状态改变时,引发该事件。
    
    bool CanExecute(object parameter); //此方法返回命令状态,如果命令可用则返回true,不可用则返回false。
    
    void Execute(object parameter); //定义在调用此命令时要调用的方法。
}

3、RoutedCommand类。

当创建自己的命令时,不会直接实现ICommand接口,而是使用System.Windows.Input.RoutedCommand类,该类自动实现了ICommand接口,RoutedEvent类是WPF中唯一实现了ICommand接口的类,换句话说,所有WPF命令都是RoutedCommand类及其派生类的实例。为支持路由事件,RoutedCommand类私有地实现了ICommand接口,并添加了ICommand接口方法的一些不同版本,最大的变化就是Excute()方法和CanExcute()方法使用了一个额外的参数

public bool CanExecute(object parameter, IInputElement target); //如果可以对当前命令目标执行此命令,则为 true;否则为 false。
        
public void Execute(object parameter, IInputElement target); //定义在调用此命令时要调用的方法。

4、RouteCommand类。 

public class RoutedCommand : ICommand
{    
    public RoutedCommand();
       
    public RoutedCommand(string name, Type ownerType);
        
    public RoutedCommand(string name, Type ownerType, InputGestureCollection inputGestures);

    public InputGestureCollection InputGestures { get; }
    
    //除了Excute()和CanExecute()修改外,还引入了三个属性:Name、OwnerType和触发命令的鼠标操作。
        
    public string Name { get; } // 获取命令的名称。
       
    public Type OwnerType { get; } //获取使用命令注册的类型。
        
    public event EventHandler CanExecuteChanged;    
    //参数Target是处理事件的元素,事件从Target元素开始,然后冒泡至最高层的容器,知道应用程序为了执行合适的任务而处理了事件(为了处理Executed事件,元素还需要借助于另一个类Commanding类的对象)。    
    public bool CanExecute(object parameter, IInputElement target);
       
     public void Execute(object parameter, IInputElement target);
}

5、RoutedUICommand类。

在程序中处理的大部分命令不是RoutedCommand对象,而是RoutedUICommand类的实例,RoutedUICommand类继承自RoutedCOmmand类,RoutedUIElement类只增加了Text属性,该属性是为命令显示的文本。

6、命令库。

每个应用程序可能有大量命令,所有基于文档的应用程序都有他们自己的版本的 New、Open以及Save命令,WPF提供了基本的命令库,而这些命令库通过5个专门的静态类的静态属性提供。

a)、ApplicationCommands:该类提供了通用命令,包括剪贴板命令(如Copy、Open、New、Delete、SelectAll、Stop)。

b)、NavigationCommands:该类提供了用于导航的命令。

c)、EditingCommands:该类提供了许多重要的文档编辑命令。

d)、ComponentCommands:该类提供了由用户界面组建使用的命令。

e)、MediaCommands:该类提供了一组用于处理多媒体的命令。

7、执行命令。

RoutedUICommands类没有任何硬编码的功能,而只是表示命令,为触发命令,需要有命令源,为响应命令,需要有命令绑定,命令绑定将执行转发给普通的事件处理程序。

7.1)命令源。

命令库中的命令始终可用,触发他们的最简单方法是将他们关联到实现了ICommandSource接口的控件,其中包括继承自ButtonBase类的控件(如Button、CheckBox)、单独的ListBoxItem对象和MenuItem。

ICommandSource接口的属性

名称

说明

Command

指向连接的命令,这是唯一必须的细节

CommandParameter

提供其他希望随命令发送的数据

CommandTarget

确定将在其中执行命令的元素

 

 

 

 

 

 

 

 

<!--使用Command属性连接到ApplicationCommands.New命令-->
<Button Margin="50" Command="ApplicationCommands.New">命令</Button>

 7.2)、命令绑定。 

<!--使用Command属性连接到ApplicationCommands.New命令-->
<Button Margin="50" Command="ApplicationCommands.New">命令</Button>

当将命令关联到命令源时,会看到一些有趣的现象,命令源会被自动禁用。这是因为按钮已经查询了命令的状态,而且由于命令还没有与其关联的绑定,所以按钮被认为是禁用的。

为改变这种状态,需要明确做以下三件事:

a)、当命令被触发时执行什么操作。

b)、如何确定命令是否能够被执行 (这是可选的,只要提供了关联的事件处理程序,命令总是可用的)。

c)、命令在何处起作用。

Xaml代码: 

<!--使用Command属性连接到ApplicationCommands.New命令-->
<Button Name="btn1" Margin="50" Command="ApplicationCommands.New">命令测试</Button>

后台代码: 

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    //CommandBinding binding = new CommandBinding(ApplicationCommands.New);
    //CommandBinding:绑定到实现该命令的事件处理程序。
    CommandBinding binding = new CommandBinding();
    //Command属性:关联到ICommand。
    binding.Command = ApplicationCommands.New;
    //关联事件处理程序。
    binding.Executed += Binding_Executed; 
    //将创建的CommandBinding对象添加到包含窗口的CommandBindings集合中,当单机按钮时,CommandBinding.Executed事件从按钮冒泡到包含元素。
    this.CommandBindings.Add(binding);
}
        
private void Binding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    MessageBox.Show(e.Source.ToString());
}

 单机按钮,就会触发Excuted事件,该事件冒泡至窗口,并被上面给出的NewCommand()事件处理程序处理。

在Xaml中创建命令,绑定处理程序:

 Xaml代码:

<Window x:Class="命令测试2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:命令测试2"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" Executed="CommandBinding_Executed"></CommandBinding>
    </Window.CommandBindings>

    <StackPanel Margin="10">
        <Button Command="ApplicationCommands.New">Xaml测试</Button>
    </StackPanel>
</Window>

后台代码:

private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    MessageBox.Show(e.Source.ToString());
}

8、使用多命令源。

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.New" Executed="CommandBinding_Executed"></CommandBinding>
</Window.CommandBindings>    
   
<StackPanel Margin="10">        
    <Menu>
        <MenuItem Header="File">
            <MenuItem Command="New"></MenuItem>
        </MenuItem>
    </Menu>        
</StackPanel>

技术分享图片

MenuItem类足够智能,如果没有设置Header属性,他将从命令中提取文本,如上面Xaml中提取了"新建",MenuItem类还会自动提取Commad.InputBindings集合中的第一个快捷键(如果存在快捷键的话)。

 9、微调命令文本。

说白了就是提取命令中的字体。

 <Button Margin="5" Command="New" Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}" Height="40"></Button>

技术分享图片

10、直接调用命令。

 并且只能使用实现了ICommandSource接口的类来触发执行的命令,也可以使用Execute()方法直接调用来自任何事件处理程序的方法,这时需要传递参数值(或null引用),和目标元素的引用。

语法:ApplicationCommands.New.Execute(null, targetElement);

 Xaml代码:

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.New" Executed="CommandBinding_Executed"></CommandBinding>
 </Window.CommandBindings>

<StackPanel Margin="10">
    <Button Name="btn2" Height="50">Hello,Test</Button>
</StackPanel>

后台代码: 

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(Command_Test);
    }

    private void Command_Test(object sender, RoutedEventArgs e)
    {
        //也可在关联的CommandBinding对象中调用Excute()方法,这种情况下不需要提供目标元素,
        //会自动将公开正在使用CommandBinding集合的元素设置为目标元素。
        this.CommandBindings[0].Command.Execute(null); 
        
         ApplicationCommands.New.Execute(null, btn2); //目标元素是WPF开始查找命令的地方(实际引发事件的元素)
    }

    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show(e.Source.ToString());
    }
}

11、禁用命令(通过设置CanExecute属性来更改按钮状态)。

如果想要创建状态在启动和禁用之间变化的命令,将会体会到命令模型的真正优势。

Xaml代码:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <TextBox TextChanged="textBox1_TextChanged" Name="textBox1" Margin="10"></TextBox>
    <Button Command="Save" Grid.Row="1" Width="250" Height="60">当TextBox值改变时,此按钮才可以点击</Button>        
</Grid>

后台代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        //创建命令对象。
        CommandBinding binding = new CommandBinding();
        //设置关联的命令。
        binding.Command = ApplicationCommands.Save;
        //命令处理程序。
        binding.Executed += Binding_Executed;
        //检查是否可用,true:可用,false:不可用。
        binding.CanExecute += Binding_CanExecute;
        //将命令加入到CommandBindings集合中。
        this.CommandBindings.Add(binding);
    }

    //设置状态标志。
    bool drag = false;
    private void Binding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = drag;
    }

    private void Binding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("保存成功!");   
    }

    private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
    {
        //如果文本框中的值改变了,就设置为可用状态。
        drag = true;
    }
}

12、具有内置命令的控件(主要用于Menu控件或ToolBar控件)。

<TextBox  Name="textBox1" Margin="10"></TextBox>
<ToolBar Grid.Row="1" Margin="10,-23,-9.6,22">
    <Button Command="Copy">复制</Button>
    <Button Command="Paste">粘贴</Button>
    <Button Command="Cut">剪切</Button>
</ToolBar>

如果不是Menu控件和ToolBar控件,需要借助FocusManager.IsFocusScope属性。

<TextBox  Name="textBox1" Margin="10"></TextBox>
<StackPanel Grid.Row="1" FocusManager.IsFocusScope="True">
    <Button  Command="Copy" Height="30">复制</Button>
    <Button  Command="Paste" Height="30">粘贴</Button>
    <Button  Command="Cut" Height="30">剪切</Button>
</StackPanel>

 

以上是关于WPF命令的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——cli的终端命令大全

VSCode自定义代码片段4——cli的终端命令大全

VSCode自定义代码片段15——git命令操作一个完整流程

VSCode自定义代码片段15——git命令操作一个完整流程

从控制台应用程序启动 WPF 窗口

WPF命令