WPF入门到跪下 第九章 MVVM-行为处理

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

MVVM模式下的行为处理

一、命令

命令是指特定指令、有明确的执行内容且具有一定的强制性。

命令VS事件
命令与控件的事件看起来有类似的地方,但是两者是不同的。

  • 控件的事件触发函数是在对应窗体的后台代码中进行定义的,仔细查阅窗体的后台代码,能发现这是一个部分类,也就是编译过后后台代码与XAML合成同一个类的。这样一来控件事件的整个触发、处理流程一直都是在窗体对象中进行的(也就是View层)。
  • 命令能通过控件的Command属性进行命令属性的绑定,从而让View层与业务逻辑进行分离。

命令的用途
第一个目的是将语义和调用命令的对象与执行命令的逻辑分开。这允许多个和不同源调用相同的命令逻辑,并允许针对不同的目标自定义命令逻辑。

另一个用途是统一的指示操作是否可用,即根据业务逻辑对界面进行操作、控制。例如当登录界面用户名或密码为空时,登录按钮不可用。

二、自定义命令的实现

1、创建命令类型

创建CommandBase类型并实现ICommand接口和对应成员。

  • 在MVVM模式的项目框架中,命令类型不属于MVVM中的任意一层,所以可以新建一个Base来存放。(可以是程序集或文件夹)

ICommand中有三个必须实现的成员:

  • CanExecuteChanged:事件成员,触发该事件会执行一次CanExcute方法,然后根据该方法的返回结果,来决定绑定该命令的控件对象是否可用。也就是每当关键属性变化的时候可以通过触发此事件,来刷新一下控件对象的可用状态。
    • CanExecuteChanged的订阅(CanExcute),WPF会在运行中自动完成,不需要我们再做订阅。
  • bool CanExecute(object parameter):方法成员,返回结果决定对应的控件对象是否可用,一般通过触发CanExecuteChanged事件来调用。
    • parameter:通过控件的CommandParameter属性传来的参数。
  • void Execute(object parameter):命令的执行内容。
    • parameter:通过控件的CommandParameter属性传来的参数。

CommandBase代码

public class CommandBase : ICommand
{
    //事件成员,调用该事件会执行一次CanExcute方法,然后根据该方法的返回结果,来决定绑定该命令的控件对象是否可用
    //一般在关键数据进行变换时调用
    public event EventHandler CanExecuteChanged;
    //由于外界只能使用事件的订阅或取消订阅,无法直接调用事件,因此需要做函数封装
    public void DoCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
    
    public Func<object, bool> DoCanExecute { get; set; }
    /// <summary>
    /// 根据返回值来决定绑定了该命令的控件是否可用,一般会通过CanExecuteChanged事件来调用
    /// </summary>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public bool CanExecute(object parameter)
    {
        //直接使用对外提供的委托,由外界决定控件对象是否可用的判定逻辑
        return DoCanExecute?.Invoke(parameter) == true;
    }

    public Action<object> DoExecute { get; set; }
    /// <summary>
    /// 命令的执行内容
    /// </summary>
    /// <param name="parameter"></param>
    public void Execute(object parameter)
    {
        //直接使用对外提供的委托,由外界决定命令的执行内容
        DoExecute?.Invoke(parameter);
    }
}

CommandBase简易版

class BaseCommand : ICommand
{
    private Action<object?>? _doExecute;

    public BaseCommand(Action<object?> doExecute)
    {
        _doExecute = doExecute;
    }

    public event EventHandler? CanExecuteChanged;

    public bool CanExecute(object? parameter)
    {
        return true;
    }

    public void Execute(object? parameter)
    {
        _doExecute?.Invoke(parameter);
    }
}

2、Model层中定义命令属性

在对应的数据模型中,定义命令属性、命令的执行内容、命令所对应控件是否可用的判定条件、CanExecuteChanged事件的调用时机。

关于命令部分,其实是可以放到ViewModel层中来实现的,这里为了方便就直接在Model中实现了。

MainModel

public class MainModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private double _value1 = 0;

    public double Value1
    {
        get { return _value1; }
        set
        {
            _value1 = value;
            Command1.DoCanExecuteChanged();
        }
    }

    private double _value2 = 0;

    public double Value2
    {
        get { return _value2; }
        set
        {
            _value2 = value;
            Command2.DoCanExecuteChanged();
        }

    }

    private double _value3;

    public double Value3
    {
        get { return _value3; }
        set
        {
            _value3 = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value3"));
        }
    }

    public CommandBase Command1 { get; set; }
    public CommandBase Command2 { get; set; }
    public MainModel()
    {
        Command1 = new CommandBase
        {
            DoExecute = Execute1,
            DoCanExecute = CanExecute1
        };
        Command2 = new CommandBase
        {
            DoExecute = Execute2,
            DoCanExecute = CanExecute2
        };
    }
    
    private void Execute1(object obj) {
        //这里进行命令执行内容的定义......
        Value3 = Value1 + Value2;
        //执行完后,要调用一下命令事件,重新确认控件是否可用
        Command1.DoCanExecuteChanged();
    }

    private void Execute2(object obj) {
        //这里进行命令执行内容的定义......
        Value3 = Value1 + Value2;
        //执行完后,要调用一下命令事件,重新确认控件是否可用
        Command1.DoCanExecuteChanged();
    }

    private bool CanExecute1(object obj)
    {
        return Value1 != 0;
    }
    private bool CanExecute2(object obj)
    {
        return Value2 != 0;
    }

}

3、ViewModel层定义模型对象

public class MainViewModel
{
    public MainModel mainModel { get; set; } = new MainModel();
}

4、View层中使用命令

<Window.DataContext>
    <vm:MainViewModel/>
</Window.DataContext>
<Grid>
    <StackPanel>
        <TextBox Text="{Binding mainModel.Value1, UpdateSourceTrigger=PropertyChanged}"/>
        <Slider Value="{Binding mainModel.Value2}"/>
        <TextBlock Text="{Binding mainModel.Value3}"/>
        <Button Content="CommanTest1" Command="{Binding mainModel.Command1}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
        <Button Content="CommanTest2" Command="{Binding mainModel.Command2}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
    </StackPanel>
</Grid>

三、全局命令状态更新

在上面的自定义命令的实现示例中可以看到,命令对象的可用状态的更新操作是十分重要的(也就是CanExecuteChanged事件的触发)。但是,如果只能按照示例中的用法,通过命令对象去触发对象中的CanExecuteChanged事件的话,难免会造成层次之间的耦合,有没有什么办法可以将CanExecuteChanged事件剥离出来,不需要命令对象就能随时随地的去触发呢?此时就需要使用CommandManager类型了。

CommandManager类型用于在全局范围内对命令的可用状态更新进行管理。

CommandManager.RequerySuggested:命令对象事件成员CanExecuteChanged的挂载事件。

  • 可以在创建命令对象时,将CanExecuteChanged事件挂载在CommandManager.RequerySuggested事件上。
  • RequerySuggested事件的触发条件是 WPF 内置的,WPF 内置的触发条件会导致多次调用CanExecute
  • 此外,通过调用CommandManager.InvalidateRequerySuggested()可主动触发一次RequerySuggested事件,但必须在UI线程。
  • 自定义命令不建议使用RequerySuggested

CommandManager.InvalidateRequerySuggested():主动触发CommandManager.RequerySuggested事件。

  • 这里需要注意的是,如果RequerySuggested中挂载了多个对象的CanExecuteChanged事件,那么一旦调用InvalidateRequerySuggested()方法将会全部触发。

自定义命令实现全局命令状态更新

自定义命令的CanExecuteChanged事件挂载:

public event EventHandler CanExecuteChanged
{
		add
    {
		    CommandManager.RequerySuggested += value;
    }
    remove
    {
		    CommandManager.RequerySuggested -= value;
    }
}

在需要更新命令可用状态的地方直接调用CommandManager.InvalidateRequerySuggested()主动触发RequerySuggested事件。

private void Execute1(object obj) {
    //这里进行命令执行内容的定义......
    Value3 = Value1 + Value2;
    //执行完后,要调用一下命令事件,重新确认控件是否可用
    CommandManager.InvalidateRequerySuggested();
}

实际上,RequerySuggested事件的触发条件是 WPF 内置的,所以即使不去调用InvalidateRequerySuggested()大多数情况下也是可以的,但是WPF内置的触发条件会导致多次调用CanExecute,因此自定义命令不建议使用RequerySuggested

四、内置命令(了解)

WPF预定了一些命令及相关操作,方便我们在开发过程中快速的实现控件的行为处理。

1、常见内置命令

媒体命令(共24个)

MediaCommands.PlayMediaCommands.StopMediaCommands.Pause、…….

应用命令(共23个)

ApplicationCommands.NewApplicationCommands.OpenApplicationCommands.CopyApplicationCommands.CutApplicationCommands.Print、………

导航命令(共16个)

NavigationCommands.GoToPageNavigationCommands.LastPageNavigationCommands.Favorites、……

联合命令(共27个)

ComponentCommands.ScrollByLineComponentCommands.MoveDownComponentCommands.ExtendSelectionDown、……

编辑命令(共54个)

EditingCommands.DeleteEditingCommands.ToggleUnderlineEditingCommands.ToggleBold、……

2、内置命令的使用

内置命令的使用主要分以下几个步骤:

绑定内置命令

通过控件的Command属性绑定内置命令

<Button Command="ApplicationCommands.Open" ....../>

定义命令内容

在控件中,通过设置CommandManager的相关属性完成命令的执行内容以及命令可用判定条件的定义。

CommandManager.Executed:当前控件命令的执行内容。

CommandManager.CanExecute:当前控件命令的可用判定条件。

<Button Command="ApplicationCommands.Open"
        CommandManager.Executed="Button_Executed"
        CommandManager.CanExecute="Button_CanExecute"
        Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"/>
private bool flag = true;
private void Button_Executed(object sender, ExecutedRoutedEventArgs e)
{
		MessageBox.Show("命令被触发了");
    flag = false;
}

private void Button_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = flag;
}

在使用时候发现以下几点需要注意的地方:

  • 绑定了内置命令后可以通过Command.Text属性获得内置命令对应的文本内容,这个好处是可以根据运行环境进行语言切换的。
  • 内置命令具有快捷键,比如ApplicationCommands.Open的快捷键是Ctrl+o,快捷键会执行命令,但传给执行函数的sender参数为窗体对象。
  • 不需要显示触发状态更新事件,例如上文中自定义命令时,是需要去触发CanExecuteChanged事件来更新命令的可用状态的。但这里是通过CommandManager来对命令进行管理的,WPF会自动触发去触发命令状态更新事件。当然,我们也可以通过CommandManager.InvalidateRequerySuggested()来进行主动触发。但由于WPF对于命令可用状态更新事件的触发是很频繁的,基本上没必要。

3、内置命令业务的统一定义

在上面例子中,虽然可以在控件中通过CommandManager来对命令的执行内容、判定条件进行定义,但如果由很多个控件使用了内置命令的话,一个个去定义会显得很繁杂。WPF为此提供了解决方案,可以通过容器元素的CommandBindings属性,对同一类型的内置命令进行统一的逻辑定义,使得在作用域范围内的命令都使用都一套业务逻辑。

<Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Open" Executed="Button_Executed" CanExecute="Button_CanExecute"/>
</Window.CommandBindings>
<Grid>
    <StackPanel>
        <Button Command="ApplicationCommands.Open"
                Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"
        />
        <Button Command="ApplicationCommands.Open"
                Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"
        />
    </StackPanel>
</Grid>

上面是通过Window元素进行ApplicationCommands.Open命令的统一,如果希望缩小作用域范围也可以通过<StackPanel.CommandBindings>来进行统一定义。

4、内置命令的终止

内置命令实质上是路由命令,普通的路由事件的消息传递是一样的,有隧道跟冒泡机制,可以通过设置Hendled属性进行命令的终止。

private void Button_Executed(object sender, ExecutedRoutedEventArgs e)
{
    ......
    e.Handled = true;
}

5、使用内置命令自带业务

在使用过程中发现大部分的内置业务是需要自己重新做业务定义的,但也有个别内置命令自带的业务也挺好用的,比如ApplicationCommands.Copy,其自带业务是,当文本框绑定该命令时,如果用光标选中文本内容,则该命令对应控件为可用状态,执行命令则将选中内容进行复制。

<TextBox Name="tb"/>
<Button Command="ApplicationCommands.Copy" CommandTarget="{Binding ElementName=tb}"
        Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

除了Copy外,比较好用的内置命令还有CutPaste等等。

五、路由命令

查阅内置命令的源码,可以看到两种路由命令类型:RoutedUICommandRoutedCommand

其实RoutedUICommand就是在继承了RoutedCommand的基础上新增了Text属性。

自定义路由命令

在xaml后台代码中定义对应属性:

public partial class MainWindow : Window
{
		......
		public RoutedUICommand MyRoutedCommand { get; set; }
		public MainWindow()
		{
		    InitializeComponent();
				//快捷键组合
		    InputGestureCollection inputGestureCollection = new InputGestureCollection()
		    {
		        new KeyGesture(Key.T, ModifierKeys.Alt)
		    };
		    MyRoutedCommand = new RoutedUICommand("textContent", "commandName", typeof(MainWindow),inputGestureCollection);
		}
		......
}

在xaml中使用自定义路由命令文章来源地址https://www.toymoban.com/news/detail-786819.html

<Button Command="{Binding RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor},Path=MyRoutedCommand}"
        CommandManager.Executed="Button_My_Executed"
        CommandManager.CanExecute="Button_My_CanExecute"
        Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>

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

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

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

相关文章

  • WPF真入门教程23--MVVM简单介绍

            在WPF开发中,经典的编程模式是MVVM,是为WPF量身定做的模式,该模式充分利用了WPF的数据绑定机制,最大限度地降低了Xmal文件和CS文件的耦合度,也就是UI显示和逻辑代码的耦合度,如需要更换界面时,逻辑代码修改很少,甚至不用修改。与WinForm开发相比,我们一般

    2024年02月03日
    浏览(31)
  • WPF入门教程系列二十七 ——DataGrid使用示例MVVM模式(4)

    WPF入门教程系列目录 WPF入门教程系列二——Application介绍 WPF入门教程系列三——Application介绍(续) WPF入门教程系列四——Dispatcher介绍 WPF入门教程系列五——Window 介绍 WPF入门教程系列十一——依赖属性(一) WPF入门教程系列十五——WPF中的数据绑定(一)       计算机界的顶

    2024年02月07日
    浏览(29)
  • WPF入门教程系列二十九 ——DataGrid使用示例MVVM模式(7)

    WPF入门教程系列目录 WPF入门教程系列二——Application介绍 WPF入门教程系列三——Application介绍(续) WPF入门教程系列四——Dispatcher介绍 WPF入门教程系列五——Window 介绍 WPF入门教程系列十一——依赖属性(一) WPF入门教程系列十五——WPF中的数据绑定(一)   接上文 WPF入门教程

    2024年02月10日
    浏览(34)
  • WPF入门教程系列二十八 ——DataGrid使用示例MVVM模式(6)

    WPF入门教程系列目录 WPF入门教程系列二——Application介绍 WPF入门教程系列三——Application介绍(续) WPF入门教程系列四——Dispatcher介绍 WPF入门教程系列五——Window 介绍 WPF入门教程系列十一——依赖属性(一) WPF入门教程系列十五——WPF中的数据绑定(一)       7.上面Buttom的

    2024年02月09日
    浏览(28)
  • WPF入门教程系列二十八 ——DataGrid使用示例MVVM模式(5)

    WPF入门教程系列目录 WPF入门教程系列二——Application介绍 WPF入门教程系列三——Application介绍(续) WPF入门教程系列四——Dispatcher介绍 WPF入门教程系列五——Window 介绍 WPF入门教程系列十一——依赖属性(一) WPF入门教程系列十五——WPF中的数据绑定(一)           通过上面

    2024年02月08日
    浏览(25)
  • 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(7) -- 图标列表展示和选择处理

    我们在WPF应用端的界面中,使用lepoco/wpfui 来做主要的入口框架,这个项目它的菜单内置了不少图标,我们需要在动态菜单的配置中,使用它作为图标的展示处理,本篇随笔介绍如何基于图标枚举集合进行图标的展示和选择处理。并扩展到Font-Awesome-WPF的处理进行展示和选择。

    2024年02月08日
    浏览(35)
  • 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(8) -- 使用Converter类实现内容的转义处理

    在我们WPF应用端的时候,和WInform开发或者Vue前端开发一样,有时候也需要对内容进行转义处理,如把一些0,1数值转换为具体含义的文本信息,或者把一些布尔变量转换为是否等,都是常见的转换处理,本篇随笔介绍在WPF应用端对内容使用Converter类实现内容的转义处理的操作。

    2024年02月08日
    浏览(35)
  • 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(6) -- 窗口控件脏数据状态IsDirty的跟踪处理

    在我们窗口新增、编辑状态下的时候,我们往往会根据是否修改过的痕迹-也就是脏数据状态进行跟踪,如果用户发生了数据修改,我们在用户退出窗口的时候,提供用户是否丢弃修改还是继续编辑,这样在一些重要录入时的时候,可以避免用户不小心关掉窗口,导致窗口的数

    2024年02月08日
    浏览(44)
  • 【WPF MVVM SelectionChanged】

    关于combox 的 selectionChanged 在MVVM 中的使用 ,在网上找了很长时间,很多都是很早以前的方法。但是本人还是在 工控匠 的博文中看到 已替换的方法 如下 由于最新的更新 vs2019/vs2022中已经不支持 System.Windows.Interactivity 并且已经不在维护所以建议使用 Microsoft.Xaml.Behaviors.Wpf 替代

    2024年02月15日
    浏览(42)
  • WPF CommunityToolkit.Mvvm

    CommunityToolkit.Mvvm(以下简称Toolkit)是WPF最有名的两个框架,一个是Prism,另一个就是Toolkit。 Prism可以看我的Prism详解 WPF Prims框架详解 Toolkit 官方文档 用 CommunityToolkit.Mvvm 加速 MVVM 开发流程 Toolkit简单复写了我们常用的两个方法 一个是 SetProperty,一个是RelayCommand SetProperty,通知

    2024年02月12日
    浏览(34)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包