8.WPF命令

8.WPF命令

命令系统的基本元素

  • 命令:实现了ICommand的类,常见的是RoutedCommand类,也可以自定义命令
  • 命令源:命令的发送者,要实现ICommandSource接口
  • 命令目标:命令发送给谁,或者说命令作用在谁身上,必须实现IInputElement接口
  • 命令关联:把一些外围逻辑和命令关联起来,如之前判断是否可执行,以及命令执行之后还要执行哪些工作

命令的基本使用步骤

  1. 创建命令类,实现ICommand接口,如果命令与具体逻辑无关,则直接可以使用RoutedCommand类。

  2. 声明命令实例,一般一个程序某种操作只需要一个命令实例(命令目标可设置多个)。

  3. 指定命令源,指定谁发送命令,如保存命令可以通过菜单栏来发送,也可以通过快捷工具栏来发送。同时命令源会受命令的影响,如命令不能被执行时,命令源控件为不可用状态。

  4. 指定命令目标,命令目标不是命令的属性,而是命令源的属性。如果没有为命令源设置命令目标,则当前拥有焦点的对象为命令目标。

  5. 设置命令关联,通过CommandBinding在执行前帮助判断命令是否可执行,并在执行后处理其他逻辑。

    **命令目标和命令关联之间的关系:**当命令源和命令目标建立联系后,命令目标会不停发送可路由的PreviewCanExecute和CanExecute事件,事件会沿着UI元素树传递最后被命令关联所捕获,命令关联捕获到这些事件后,把命令能不能执行报告给命令。类似的,如果命令被发送出来并送达目标命令,命令目标会发送PreviewExecuted和Executed事件,这两个事件也会被命令关联捕获,然后命令关联去执行后续工作。其中,命令目标负责发送各种事件,命令负责跑腿,真正执行操作的是命令关联。

使用命令的好处:可以避免自己写代码判断控件是否可用以及添加快捷键

案例:

<StackPanel x:Name="stackPanel">
    <Button x:Name="btn" Content="Send Command" Margin="5"/>
    <TextBox x:Name="txtA" Height="100" Margin="5,0"/>
</StackPanel>

后台代码

public MainWindow()
{
    InitializeComponent();
    InitializeCommand();
}
//声明并定义命令
private RoutedCommand clearCmd = new RoutedCommand("Clear", typeof(MainWindow));
private void InitializeCommand()
{
    //把命令赋值给命令源
    this.btn.Command = this.clearCmd;
    this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));//给命令设置快捷键

    //指定命令目标
    this.btn.CommandTarget = this.txtA;//这是目标源的属性

    //创建命令关联
    CommandBinding cb = new CommandBinding();
    cb.Command = this.clearCmd; //只关注与clearCmd相关的事件
    cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
    cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);

    //把命令关联安置在外围控件上
    this.stackPanel.CommandBindings.Add(cb);

}
//命令送达目标后,此方法被调用
private void cb_Executed(object sender, ExecutedRoutedEventArgs e)
{
    this.txtA.Clear();
    e.Handled = true;//避免继续传递而降低性能
}
//判断命令是否可执行
private void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    if (string.IsNullOrEmpty(this.txtA.Text))
    {
        e.CanExecute = false;
    }
    else
    {
        e.CanExecute = true;
    }
    e.Handled = true;//避免继续传递而降低性能
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eOAzbx8r-1667881224116)(8.WPF命令.assets/image-20221108102114461.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yRszzED7-1667881224117)(8.WPF命令.assets/image-20221108102127153.png)]

​ *案例说明:*RoutedCommand是一个与业务逻辑无关的类,只负责在程序中“跑腿”,而不会对命令目标执行任何操作。命令关联把命令能否可用告诉命令,然后会影响到命令源。命令到达命令目标后,命令目标会触发相关事件。

真正对命令目标执行操作的是命令关联。

命令目标不断的发送路由事件,CommandBinding需要安装在外围的UI树上,起到一个监听作用。

因为命令目标会不断发送CanExecute事件,为了避免降低性能,建议处理完后把e.Handled设置为True。

WPF命令库和命令参数

WPF类库中已经准备了常用的命令如打开、关闭、撤销等。这些命令库包括

  • ApplicationCommands
  • ComponentCommands
  • NavigationCommands
  • MediaCommands
  • EditingCommands

这些都是静态类,其中的命令则是类中的静态属性。

命令参数

命令库中的静态预制命令,全局仅有一个,而如果界面有两个按钮,分别需要用New命令新建不同的工程,如何实现?

命令源实现了ICommandSource接口,接口中具有CommandPrameter属性,表示命令的相关信息。

<Grid Margin="6">
    <Grid.RowDefinitions>
        <RowDefinition Height="24"/>
        <RowDefinition Height="4"/>
        <RowDefinition Height="24"/>
        <RowDefinition Height="4"/>
        <RowDefinition Height="24"/>
        <RowDefinition Height="4"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Name:" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="0"/>
    <TextBox x:Name="txtName" Margin="60,0,0,0" Grid.Row="0"/>
    <Button Content="New Teacher" Command="New" CommandParameter="Teacher" Grid.Row="2"/>
    <Button Content="New Student" Command="New" CommandParameter="Student" Grid.Row="4"/>
    <ListBox x:Name="list" Grid.Row="6"/>
</Grid>
<Window.CommandBindings>
    <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    if (string.IsNullOrEmpty(this.txtName.Text))
    {
        e.CanExecute = false;
    }
    else
    {
        e.CanExecute = true;
    }
    e.Handled = true;
}

private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    string name = this.txtName.Text;
    if (e.Parameter.ToString()=="Teacher")
    {
        this.list.Items.Add($"新教师{name}");
    }
    if (e.Parameter.ToString() == "Student")
    {
        this.list.Items.Add($"新学生{name}");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bfDhOShs-1667881224118)(8.WPF命令.assets/image-20221108110215928.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toMxNSiN-1667881224118)(8.WPF命令.assets/image-20221108110230362.png)]

命令与Binding的结合

控件只有一个Command属性,而命令库有数十种命令,如何使用唯一的Command属性来调用多个命令,答案是Binding。

例如,一个button所关联的命令可能根据某些条件而改变

<Button x:Name="btn" Command={Binding Path=xxx,Source=sss} Content="Command"/>

自定义命令

RoutedCommand与业务逻辑无关,业务逻辑主要依靠CommandBinding来实现业务逻辑。现自定义命令将业务逻辑移入命令的Execute中。

案例:自定义一个名为Save的命令,命令到达命令目标时,先通过命令目标的IsChanged属性判断命令目标的内容是否改变,如果改变则命令可执行,然后命令调用命令目标的Save方法。这样,命令直接在命令目标上起作用了,而不像RoutedCommand那样现在命令目标上激发路由事件,等外围控件捕捉到事件后再对命令目标进行处理。但是这样需要使用接口来约束命令目标具有IsChanged和Save方法。

  1. 声明接口,命令目标实现该接口
public interface IView
{
    bool IsChanged { set; get; }
    void Clear();
}
  1. 自定义命令
public class ClearCommand : ICommand
{
    //命令可执行状态发生改变时激发
    public event EventHandler CanExecuteChanged;

    //判断命令是否可执行,暂不实现
    public bool CanExecute(object parameter)
    {
        throw new NotImplementedException();
    }
    //命令执行,与业务相关的逻辑
    public void Execute(object parameter)
    {
        IView view = parameter as IView;
        if (view !=null)
        {
            view.Clear();
        }
    }
}
  1. 自定义命令源
public class MyCommandSource : UserControl, ICommandSource
{
    public ICommand Command { set; get; }
    public object CommandParameter { set; get; }
    public IInputElement CommandTarget { set; get; }

    //组件被单击时连带执行命令
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        base.OnMouseLeftButtonDown(e);
        //在命令目标上执行命令
        if (this.CommandTarget!=null)
        {
            this.Command.Execute(this.CommandTarget);
        }
    }
}
  1. 定义命令目标-使用WPF自定义组件
<Border CornerRadius="15" BorderBrush="LawnGreen" BorderThickness="2">
    <StackPanel>
        <TextBox x:Name="txt1" Margin="5"/>
        <TextBox x:Name="txt2" Margin="5"/>
        <TextBox x:Name="txt3" Margin="5"/>
        <TextBox x:Name="txt4" Margin="5"/>
    </StackPanel>
</Border>
public partial class MiniView : UserControl,IView
{
    public MiniView()
    {
        InitializeComponent();
    }

    public bool IsChanged { get ; set ; }

    public void Clear()
    {
        this.txt1.Clear();
        this.txt2.Clear();
        this.txt3.Clear();
        this.txt4.Clear();
    }
}
  1. 使用自定义命令
<StackPanel>
    <local:MyCommandSource x:Name="ctlClear" Margin="10">
        <TextBlock Text="清除" TextAlignment="Center" Width="80"/>
    </local:MyCommandSource>
    <local:MiniView x:Name="miniView"/>
</StackPanel>
public MainWindow()
{
    InitializeComponent();

    //声明命令并使命令源和目标关联
    ClearCommand clrCmd = new ClearCommand();
    this.ctlClear.Command = clrCmd;
    this.ctlClear.CommandTarget = this.miniView;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HFQPWc1q-1667881224119)(8.WPF命令.assets/image-20221108121419429.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNwKJ1vu-1667881224119)(8.WPF命令.assets/image-20221108121428786.png)]

该案例使用了TextBlock作为激发控件,也可以使用图片等,如果使用Button,就不要重写OnMouseLeftButtonDown方法了,因为它和Button.Click事件冲突,而是应该捕捉Button.Click事件(Mouse事件会被Button吃掉)

如果想通过Command的CanExecute方法返回值来影响命令源状态,要对ICommand和ICommandSource接口成员组成更复杂的逻辑。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

步、步、为营

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

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

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

打赏作者

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

抵扣说明:

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

余额充值