WPF 入门笔记 - 06 - 命令

这篇具有很好参考价值的文章主要介绍了WPF 入门笔记 - 06 - 命令。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们把世界看错,反说它欺骗了我们。 --飞鸟集

前言

相较而言,命令对我来说是一个新概念,因为在Winform中压根没有所谓的命令这个概念?。从文字角度理解,"命令"可以指代一种明确的指令或要求,用于向某个实体传达特定的操作或行为。它可以是一个动词性的词语,表示对某个对象或主体的要求或指示。命令通常具有明确的目标和执行内容,它告诉接收者要执行什么操作,并在某种程度上对行为进行约束。

在软件开发中,"命令"是一种设计模式,它描述了将操作封装为对象的方法,以便在不同的上下文中使用和重用。这种命令模式通过将请求和操作封装到一个命令对象中,使得发送者和接收者之间解耦,从而实现了更灵活和可扩展的设计。在这种模式下,命令对象充当了发送者和接收者之间的中间媒介,接收者通过执行命令对象来完成特定的操作。

在提到命令的时候,我们通常拿它来和事件作比较。我们都知道,WPF里已经有了路由事件,比如按钮在被点击以后做出的反应(直接事件),我们一般会通过ButtonClick新建一个事件,然后在这个事件里面写一些业务代码:

private void Button_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("You click me.");
}

当我们运行程序点击按钮时,Button_Click事件会被驱动响应点击的动作弹出一个消息对话框,没有问题对吧。我们都知道,在早期的GUI框架中,应用程序的外观和行为方式之间没有真正的分离,回到这里也一样,将业务代码直接写在事件处理程序中会导致界面和业务逻辑紧密耦合在一起,使得两者难以分离和独立变更。实际开发中,我们会给按钮一个特定功能,当按钮的功能发生变化时,我们需要在UI界面中修改与这个功能所绑定的东西,同时也需要调整业务代码,这就导致了界面元素(外观)和业务逻辑(行为)混在一起,使得按钮的XAML代码既承担了外观的定义,又承担了业务逻辑的实现。此外,假设我们有很多个一样的按钮,都需要在用户点击按钮后为它提供相同的功能,通过上述面的方法,当这个功能发生改变时,我们就需要调正每一个涉及到的按钮以及相应的事件。为了解决这个问题,WPF提供了命令机制(Command),可以将按钮的行为与其外观进行分离。

命令(Command)在WPF中是一种用于处理用户界面交互的机制,它们提供了一种在界面元素(UI)和后台逻辑之间进行解耦的方式,使得交互操作可以以一种统一的、可重用的方式进行处理。WPF命令的概念和实现是基于MVVM(Model-View-ViewModel)架构模式的,它使得界面元素的交互操作可以通过命令对象进行管理和处理,而不需要直接在界面代码中编写事件处理程序。

通过使用命令,我们能够更好地组织和管理界面交互行为,使得代码结构清晰,易于维护和扩展。同时,命令还提供了一些额外的功能,如参数传递、命令的可用性控制等,使得我们能够更灵活地处理用户的操作。

事件和命令:

事件是与用户动作进行联动的,而命令是那些想要与界面分离的动作,比如常见的复制粘贴命令,当我们点击一个具有复制功能的按钮时,相当于我们通过点击的这个动作触发了一个复制的命令,这样做的好处就是 - 界面的交互操作变得简单、代码可重用性提高,在不破坏后台逻辑的情况下可以更加灵活的控制用户界面。

命令模型

WPF命令模型主要包含以下几个基本元素:

命令(Command):指的是实现了ICommand接口的类,例如RoutedCommand类及其子类RoutedUICommand类,一般不包含具体逻辑。

命令源(Command Source):即命令的发送者,指的是实现了ICommandSource接口的类。像ButtonMenuItem等界面元素都实现了这个接口,单击它们都会执行绑定的命令。

命令目标(Command Target):即命令的接受者,指的是实现了IInputElement接口的类。

命令关联(Command Binding):即将一些外围逻辑和命令关联起来。

借用刘老师的图来看一下他们的关系:

ICommand

ICommand接口,包含一个事件两个方法:

public interface ICommand
{
    //
    // 摘要:
    //     当出现影响是否应执行该命令的更改时发生。
    event EventHandler CanExecuteChanged;

    // 摘要:
    //     定义用于确定此命令是否可以在其当前状态下执行的方法。
    //
    // 参数:
    //   parameter:
    //     此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。
    //
    // 返回结果:
    //     如果可以执行此命令,则为 true;否则为 false。
    bool CanExecute(object parameter);
    
    //
    // 摘要:
    //     定义在调用此命令时调用的方法。
    //
    // 参数:
    //   parameter:
    //     此命令使用的数据。如果此命令不需要传递数据,则该对象可以设置为 null。
    void Execute(object parameter);
}

反过来看:

  • Execute - 执行某个动作

  • CanExecute - 能不能执行动作

  • CanExecuteChanged - 命令状态发生变化是响应的事件

通过实现ICommand接口,我们可以创建自定义的命令对象,并将其与界面元素进行绑定。这样,界面元素就可以与命令相关联,通过调用命令的Execute方法来执行具体的操作,而无需直接编写事件处理程序。

定义命令

下面我们试着用命令的方式来实现上面的点击事件,并逐步理解模型中的内容。我们新建一个类MainViewModel来提供我们需要的功能方法:

using System.Windows;

namespace WPFDemo
{
    public class MainViewModel
    {
        public void ShowInfo()
        {
            MessageBox.Show("You click me.");
        }
    }
}

ShowInfo()这个时候跟UI界面是分开的对吧,有了方法之后,我们可以说MainViewModel中的ShowInfo就命令了吗,根据模型来看显然还不行。继续走,写一个实现ICommand接口但不带具体逻辑的类,比如CustomCommand:

using System;
using System.Windows.Input;

namespace WPFDemo
{
    public class CustomCommand : ICommand
    {
        private readonly Action _execute;
        public CustomCommand(Action execute)
        {
            _execute = execute;
        }
        public event EventHandler CanExecuteChanged;
        public bool CanExecute(object parameter)
        {
            // 在这里实现命令的可执行逻辑
            return true; // 默认返回true,表示命令可执行
        }
        public void Execute(object parameter)
        {
            // 在这里实现命令的执行逻辑
            _execute?.Invoke();
        }
    }
}

之后在MainViewModel类中需要添加一个公共属性来暴露CustomCommand实例作为ShowInfoCommand,以便在XAML中进行绑定;

using System.Windows;

namespace WPFDemo
{
    public class MainViewModel
    {
        public CustomCommand ShowInfoCommand { get; set; }

        public MainViewModel()
        {
            ShowInfoCommand = new CustomCommand(ShowInfo);
        }
        public void ShowInfo()
        {
            MessageBox.Show("You click me.");
        }
    }
}

最后,将CustomCommand实例与界面元素进行绑定:


    
        
    
    
        

设置数据上下文也可以在后台代码中完成

回到命令模型上来,梳理以下对应关系:

  1. CustomCommand 类本身是一个命令对象(ICommand),它实现了命令接口,包括 ExecuteCanExecute 方法。这个命令对象是命令模式中的 "具体命令"。
  2. MainViewModel 类作为命令的执行者(或者称为命令目标),其中包含了 ShowInfo 方法。在命令模式中,执行者负责实际执行命令所需的操作。在我们的示例中,ShowInfo 方法是具体执行的业务逻辑。
  3. XAML中,我们使用 CommandBinding 将按钮的 Click 事件与 ShowInfoCommand 关联起来。这个关联是通过在 WindowUserControlCommandBindings 集合中添加一个新的 CommandBinding 对象来完成的。这个 CommandBinding 指定了 ShowInfoCommand 作为命令和 MainViewModel 作为命令的执行者。

注意:Command属性仅仅作为Click行为的绑定,其他行为,如鼠标移入、移出。。。等行为,要使用另外的MVVM方式进行绑定。

最后,梳理下程序结构,可以看到,我们分别在MainViewModel.csMainWindow.xaml中书写业务代码和逻辑。

通知更改

WPF中,实现属性的通知更改是通过实现 INotifyPropertyChanged 接口来实现的。这个接口定义了一个 PropertyChanged 事件,当属性的值发生变化时,可以通过触发该事件来通知界面进行更新。

在进行演示之前,先来看看我们上面所使用的例子能否像之前学习的绑定一样实现自动更新,按照业务分离的逻辑,我们在MainViewModel.cs中添加一个Name字段并在页面进行绑定。

MainViewModel.cs

using System.Windows;

namespace WPFDemo
{
    public class MainViewModel
    {
        public CustomCommand ShowInfoCommand { get; set; }
        public string Name { get; set; }

        public MainViewModel()
        {
            Name = "狗熊岭第一狙击手";
            ShowInfoCommand = new CustomCommand(ShowInfo);
        }
        public void ShowInfo()
        {
            Name = "光头强";
            MessageBox.Show("You click me.");
        }
    }
}

MainWindow.xaml


    
        
    
    
        
            

之前xaml中的数据上下文不太好,需要在每个控件中都定义一次?

上面的代码中,我们将Name绑定到了TextBox的文本中,并在点击按钮是改变Name的值,如果它自己可以通知更改,自动更新的话那么在Name变化的时候文本也应该变化,对吧。我们运行试一下:

可以看到Name的变化时Text并没有随之变化,这说明Name发生改变以后并没有通知Text也进行变化。如果你喜欢捣鼓的话,可以看看Text如果被修改以后Name会不会变化。

回到正题,上文提到过,实现属性的通知更改是通过实现 INotifyPropertyChanged 接口来实现的,我们来对自定义的MainViewModel.cs稍作修改实现属性的通知更改:

using System.ComponentModel;
using System.Windows;

namespace WPFDemo
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public CustomCommand ShowInfoCommand { get; set; }
        public event PropertyChangedEventHandler PropertyChanged;
        
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }
        
        private void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        
        public MainViewModel()
        {
            Name = "狗熊岭第一狙击手";
            ShowInfoCommand = new CustomCommand(ShowInfo);
        }
        
        public void ShowInfo()
        {
            Name = "光头强";
            MessageBox.Show("You click me.");
        }
    }
}

?nameof(Name) 是C# 6.0 引入的一个语法糖,它可以在编译时获取属性、方法、字段、类型等成员的名称作为一个字符串。

WPF中,nameof(Name) 用于在属性更改通知中指定属性的名称。它的作用是避免硬编码属性名称,从而减少在重构过程中出现由于重命名属性而导致的错误。

修改过后,MainViewModel 类实现了 INotifyPropertyChanged 接口,并在 Name 属性的 setter 中进行了属性更改通知。当 Name 属性的值发生变化时,会触发 PropertyChanged 事件,通知界面进行更新。

进阶玩法

在实现通知更改的方式中,可以将通知更改的逻辑定义在一个基类中,例如 ViewModelBase 类。这个基类可以包含通用的属性更改通知实现,以便其他具体的视图模型类可以继承该基类并重用这些通知更改的功能。

以下是一个简单的示例,展示了如何在 ViewModelBase 类中实现通知更改:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WPFDemo
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

在上面的代码中,ViewModelBase 类实现了 INotifyPropertyChanged 接口,并提供了 OnPropertyChanged 方法用于触发属性更改通知事件,并将属性名称作为参数传递。

默认情况下,CallerMemberName 特性用于自动获取调用该方法的成员的名称,并作为属性名称传递。

通过继承 ViewModelBase 类,并使用 OnPropertyChanged 方法来设置属性,可以简化视图模型类中属性更改通知的实现:

MainViewModel.cs

using System.Windows;

namespace WPFDemo
{
    public class MainViewModel : ViewModelBase
    {
        public CustomCommand ShowInfoCommand { get; set; }
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged();
            }
        }

        private string _description;
        public string Description
        {
            get { return _description; }
            set { _description = value; OnPropertyChanged(); }
        }

        public MainViewModel()
        {
            Name = "狗熊岭第一狙击手";
            Description = "光头强";
            ShowInfoCommand = new CustomCommand(ShowInfo);
        }
        public void ShowInfo()
        {
            Name = "光头强";
            Description = "狗熊岭第一突破手,麦克阿瑟如是说。";
            MessageBox.Show("You click me.");
        }
    }
}

在这个示例中,MainViewModel 类继承了 ViewModelBase 类,并使用 OnPropertyChanged 方法来设置 Name 属性。当 Name 属性的值发生更改时,SetProperty 方法会自动处理属性更改通知的逻辑,无需手动触发事件或编写重复的代码。

通过将通知更改的实现定义在基类中,可以实现更简洁、可维护和可重用的代码,避免在每个具体的视图模型类中重复编写通知更改的逻辑。

夹带私货 - 命令参数

有时候我们需要在执行命令时传递参数。在WPF中,可以使用CommandParameter属性来传递参数给命令。

CommandParameter是一个附加属性,可以将任意对象指定为命令的参数。当命令执行时,命令的Execute方法会接收到该参数,并可以在命令处理逻辑中使用它。

以下是一个示例,展示如何在XAML中为命令指定CommandParameter

在这个示例中,我们将ButtonCommand属性绑定到MyCommand命令。同时,我们通过CommandParameter属性指定了一个字符串参数"Hello, World!"。当点击按钮时,该参数将传递给MyCommand命令的Execute方法。

在命令的执行逻辑中,可以通过命令参数来获取传递的值。以下是一个简单的命令类示例:

CustomCommand.cs

using System;
using System.Windows.Input;

namespace WPFDemo
{
    public class CustomCommand : ICommand
    {
        private readonly Action _execute;
        public CustomCommand(Action execute)
        {
            _execute = execute;
        }
        public event EventHandler CanExecuteChanged;
        public bool CanExecute(object parameter)
        {
            // 在这里实现命令的可执行逻辑
            return true; // 默认返回true,表示命令可执行
        }
        public void Execute(object parameter)
        {
            // 在这里实现命令的执行逻辑
            _execute?.Invoke(parameter as string);
        }
    }
}

CustomCommand 类接受一个 Action 类型的参数,在构造函数中将传递的方法保存到 _execute 字段中。然后,在 Execute 方法中,通过调用 _execute?.Invoke(parameter) 来执行传递的方法,并将 parameter 作为参数传递给该方法。

这样,当你在 MainViewModel 中创建 CustomCommand 实例时,可以将 ShowInfo 方法作为参数传递进去,

MainViewModel.cs:

using System.Windows;

namespace WPFDemo
{
    public class MainViewModel
    {
        public CustomCommand ShowInfoCommand { get; set; }
        public MainViewModel()
        {
            ShowInfoCommand = new CustomCommand(ShowInfo);
        }
        public void ShowInfo(object parameter)
        {
            MessageBox.Show(parameter as string);
        }
    }
}

那么ShowInfo(object parameter)的参数从哪里来呢 - CommandParameter附加属性:


    
        
    
    
        
            

这样看上去可能比较蠢,我们可以些微调整一下在页面中完成显示内容的修改,代码就不贴了,大家应该知道:

通过命令参数,可以实现更灵活的命令处理逻辑,根据传递的参数来执行不同的操作。同时,使用命令参数也可以实现与界面元素的交互,例如根据按钮的点击位置传递坐标信息等。

? 以上就是本篇文章的所有内容了 ?文章来源地址https://www.toymoban.com/news/detail-497801.html

到了这里,关于WPF 入门笔记 - 06 - 命令的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • WPF 入门笔记 - 01 - 入门基础以及常用布局

    🥑本篇为学习博客园大佬圣殿骑士的《WPF基础到企业应用系列》以及部分DotNet菜园的《WPF入门教程系列》所作笔记,对应圣殿骑士《WPF基础到企业应用系列》第 1 - 6 章之间内容,包括 WPF 项目结构、程序的启动和关闭、程序的生命周期、继承关系以及常见的布局控件及其应用

    2024年02月05日
    浏览(43)
  • WPF 入门笔记 - 03 - 样式基础

    ? 程序的本质 - 数据结构 + 算法 ? ?本篇为学习李应保老师所著的《WPF专业编程指南》并搭配 WPF 开发圣经《WPF编程宝典第4版》以及痕迹大佬《WPF入门基础教程系列》文章所作笔记,对应《WPF专业编程指南》第 9 章之间内容,主要概述 WPF 中关于样式的相关内容,希望可以帮到

    2024年02月08日
    浏览(46)
  • WPF 入门笔记 - 05 - 依赖属性

    如果预计中的不幸没有发生的话,我们就会收获意外的喜悦。 --人生的智慧 - 叔本华 这一部分是中途加的,直接依赖属性有点迷糊😪,正好有了绑定的基础,理解起来还一些。 WPF 提供一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能。这些服务通常统称为

    2024年02月09日
    浏览(38)
  • WPF 入门笔记 - 04 - 数据绑定

    慢慢来,谁还没有一个努力的过程。 --网易云音乐 数据绑定概述 (WPF .NET) 什么是数据绑定? 数据绑定(Data Binding)是 WPF 一种强大的机制,用于在应用程序的各个部分之间建立数据的双向关联。它允许你将数据从一个源(例如对象、集合、数据库等)绑定到目标控件的属性,

    2024年02月09日
    浏览(64)
  • WPF 入门笔记 - 07 - MVVM示例

    滴咚,大家好久不见💖。好就没写东西了,鸽着鸽着就无了😭。。。 回到正题,上篇文章说完命令提了一嘴 MVVM 模式直接就上 MVVMLight 这些程序的框架了,虽然也没说多少,但还是有点不好过渡,这篇对 MVVM 做一些系统学习,同时复习一下前几篇博文中的内容,完成一个采

    2024年02月13日
    浏览(53)
  • WPF 入门笔记 - 02 - 布局综合应用

    本篇博文对接上篇末尾处WPF常用布局控件的综合应用,为痕迹g布局控件介绍课后作业的一个思路方法。 首先来谈一谈布局原则: WPF 窗口只能包含一个元素(Window元素属于内容控件,内容控件只允许有一个子元素),所以我们得在窗口中放置一个容器,才能使我们的窗口放置更

    2024年02月06日
    浏览(48)
  • WPF 入门笔记 - 03 - 样式基础及模板

    🍟 程序的本质 - 数据结构 + 算法 🍟 本篇为学习李应保老师所著的《WPF专业编程指南》并搭配 WPF 开发圣经《WPF编程宝典第4版》以及痕迹大佬《WPF入门基础教程系列》文章所作笔记,对应《WPF专业编程指南》第 9-10 章之间内容,主要概述 WPF 中关于样式及模板部分的梳理及应

    2024年02月08日
    浏览(43)
  • WPF 零基础入门笔记(1):WPF静态页面,布局+样式+触发器

    WPF 零基础入门笔记(0):WPF简介 WPF MaterialDesign 初学项目实战(0):github 项目Demo运行 WPF MaterialDesign 初学项目实战(1)首页搭建 WPF MaterialDesign 初学项目实战(2)首页导航栏样式 WPF MaterialDesign 初学项目实战(3)动态侧边栏 WPF MaterialDesign 初学项目实战(4)侧边栏路由管理

    2024年02月11日
    浏览(42)
  • WPF 入门笔记 - 04 - 数据绑定 - 补充内容:资源基础

    宇宙很大,生活更大,也许以后还有缘相见。 --三体 🌌 💭 该篇作为[WPF 入门笔记 - 04 - 数据绑定] - Additional Content 章节的补充内容 XAML 资源概述 (WPF .NET) WPF中的每一个元素都有一个 Resources 属性,该属性存储了一个资源字典集合。一般来说,可以把WPF的资源按照不同的性质分

    2024年02月11日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包