08 MVVM框架
WPF是Windows Presentation Foundation的缩写,它是一种用于创建桌面应用程序的用户界面框架。WPF支持多种开发模式,其中一种叫做MVVM(Model-View-ViewModel)。
在WPF开发中,经典的编程模式是MVVM,是为WPF量身定做的模式,该模式充分利用了WPF的数据绑定机制,最大限度地降低了Xaml文件和CS文件的耦合度,也就是UI显示和逻辑代码的耦合度,如需要更换界面时,逻辑代码修改很少,甚至不用修改。
与WinForm开发相比,我们一般在后置代码中会使用控件的名字来操作控件的属性来更新UI,而在WPF中通常是通过数据绑定来更新UI;在响应用户操作上,WinForm是通过控件的事件来处理,而WPF可以使用命令绑定的方式来处理,耦合度将降低。
什么是MVVM?
MVVM是一种软件架构模式,它将应用程序分为三个层次:Model(模型),View(视图)和ViewModel(视图模型)。Model表示应用程序的数据和业务逻辑,View表示应用程序的用户界面,ViewModel表示View和Model之间的桥梁,它负责处理View的数据绑定和用户交互。
与此同时,在技术层面,WPF也带来了 诸如Binding(绑定)、Dependency Property(依赖属性)、Routed Events(路由事件)、Command(命令)、DataTemplate(数据模板)、ControlTemplate(控件模板)等新特性。
MVVM模式其实是MVP模式与WPF结合的应用方式时发展演变过来的一种新型架构模式。它立足于原有MVP框架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化。
为什么要使用MVVM(MVVM的优势)?
MVVM的根本思想就是界面和业务功能进行分离,View的职责就是负责如何显示数据及发送命令,ViewModel的功能就是如何提供数据和执行命令。各司其职,互不影响。
在实际的业务场景中我们经常会遇到客户对界面提出建议要求修改,使用MVVM模式开发,当设计的界面不满足客户时,我们仅仅只需要对View作修改,不会影响到ViewModel中的功能代码,减少了犯错的机会。 随着功能地增加,系统越来越复杂,相应地程序中会增加View和ViewModel文件,将复杂的界面分离成局部的View,局部的View对应局部的ViewModel,功能点散落在各个ViewModel中,每个ViewModel只专注自己职能之内的事情。ViewModel包含了View要显示的数据,并且知道View的交互代码,所以ViewModel就像一个无形的View。
使用MVVM有以下几个好处:
- 降低了View和Model之间的耦合度,使得它们可以独立地开发和测试。
- 提高了代码的可重用性和可维护性,因为ViewModel可以在不同的View之间共享。
- 简化了单元测试,因为ViewModel不依赖于具体的UI控件。
- 支持双向数据绑定,使得View可以自动更新Model的变化,反之亦然。
- 利用了WPF提供的强大特性,如命令、依赖属性、数据注解等。
结构模型图
- Model: 就是系统中的对象,可包含属性和行为(就是一个class,是对现实中事物的抽象,开发过程中涉及到的事物都可以抽象为Model,例如客户,客户的姓名、编号、电话、住址等);
- View: 就是用xaml实现的界面,负责与用户交互,接收用户输入,把数据展现给用户;
- ViewModel: 是一个C#类,负责收集需要绑定的数据和命令,聚合Model对象,通过View类的DataContext属性绑定到View,同时也可以处理一些UI逻辑。显示的数据对应着ViewMode中的Property,执行的命令对应着ViewModel中的Command。
三者之间的关系: View对应一个ViewModel,ViewModel可以聚合N个Model,ViewModel可以对应多个View
MVVM前戏,需要掌握的知识
INotifyPropertyChanged
我们使用Binding
语法将控件的某个属性绑定到类属性上之后,控件属性的修改将会同步到类的属性上,但是即使我们设置了绑定方式为TwoWay
,我们修改类的属性时控件却不会发生变化,这是因为我们属性的变更没有进行通知
微软官方文档上对应数据绑定有这样一段话:
数据绑定是在应用 UI 与其显示的数据之间建立连接的过程。 如果绑定具有正确的设置,并且数据提供适当的通知,则在数据更改其值时,绑定到该数据的元素会自动反映更改。
那么我们如何将属性的变更进行通知呢?就需要用到INotifyPropertyChanged
接口了。INotifyPropertyChanged接口,用于通知客户端我们的属性已经更改,需要进行UI的刷新了,具体用法如下
// 1、继承INotifyPropertyChanged接口
public partial class Window1 : Window, INotifyPropertyChanged
{
// 2、实现INotifyPropertyChanged接口,只有一个事件
public event PropertyChangedEventHandler PropertyChanged;
private string myName = "张三";
// 3、当属性变化时,触发对应的事件,并传递参数,通知变化的属性名字叫什么
public string MyName
{
get { return myName; }
set
{
myName = value;
PropertyChanged(this, new PropertyChangedEventArgs("myName"));
}
}
private int myAge = 20;
public int MyAge
{
get { return myAge; }
set
{
myAge = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyAge"));
}
}
public bool MySex { get; set; } = true;
private void Button_Click(object sender, RoutedEventArgs e)
{
MyName = "李四";
MyAge = 88;
MySex = false;
}
}
该例中类实现了INotifyPropertyChanged
接口,并在MyName和MyAge
变化时提供了通知,MySex
变化时未提供通知,当MyName和MyAge
变化时,xaml中的绑定也会进行更新,MySex
不会更新
可以封装一下代码如下
void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
// 当某个属性变化时进行调用
OnPropertyChanged(nameof(MyAge)); // nameof用于获取某个属性的属性名
但是如果该属性是一个集合,修改集合的项不会发起通知,因为不会走set
,我们需要使用ObservableCollection
类代替之前使用的List
,该类中实现了对应的通知
public ObservableCollection<string> Names { get; set; } = new ObservableCollection<string>
{
"张三",
"李四"
};
Names.Add("王五");
ICommand
命令是WPF中的一种机制,可以在xaml中绑定命令来执行一些操作,类似于绑定方法,但是又不太相同。命令可以写在其他的类中,由不同的窗体进行调用,而控件的事件绑定一般只出现在当前的窗体中,局限性较大。比如我们要实现一个关闭窗体的功能,这个功能需要在大多数窗口使用,那我们将不得不在每个窗体中都实现该方法,如果使用命令的话就不必如此了。
系统内置了一些命令供我们使用,如剪切、复制、粘贴等,更多命令参考这里
我们也可以自定义自己的命令,需要使用``这个接口,这个接口用于实现一个命令类,我们根据自己实现的命令类创建我们的命令,由View层发起命令,ViewModel层执行命令
RelayCommand 中继命令类的实现
我们不能直接使用ICommand接口创建一个命令,而是需要自己实现一个ICommand接口的类来创建我们的命令,最基本的结构如下
public class RelayCommand : ICommand
{
// 一个用于通知命令可执行状态发生变化的事件
public event EventHandler CanExecuteChanged;
// 用于判断命令是否可以执行的方法,初次绑定和命令执行之前都会执行这个方法
public bool CanExecute(object parameter)
{
// 根据返回值决定该任务是否可以被执行,比如:ApplicationCommands.Copy 命令,如果没有文本被选中,这个命令无法执行, 按钮也处于禁用状态
return true;
}
// 命令执行所进行的任务
public void Execute(object parameter)
{
MessageBox.Show("命令执行了");
}
}
命令的创建和绑定
在某个类中对中继类进行实例化,并在xaml中进行命令的绑定即可,以下示例将会在点击按钮时弹窗,执行对应的命令任务
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CommandA = new RelayCommand();
}
public RelayCommand CommandA { get; private set; }
}
<Button Command="{Binding CommandA}" Content="测试"/>
完整写法
上例中演示了最基本的命令使用,该命令十分简单,仅仅完成了命令的绑定和执行,我们应该让这个类更加的灵活,不能总执行相同的任务代码,命令的可执行状态也不能老是为true,完整的代码如下
public class RelayCommand : ICommand
{
// 可执行状态变化时应该被触发的事件
public event EventHandler CanExecuteChanged;
// 用于判断命令是否为可执行状态的方法
public bool CanExecute(object parameter)
{
// 如果没有canExecute,则命令永远处于可以执行的状态
if (canExecute == null) return true;
// 如果有,则返回该方法的返回值
return canExecute.Invoke(parameter);
}
// 执行的命令的方法
public void Execute(object parameter)
{
// 触发执行的任务
execute.Invoke(parameter);
}
// 一个委托,存储要执行的任务
Action<object> execute;
// 一个委托,存储判断命令是否能执行的方法
Func<object, bool> canExecute;
// 构造函数,接收委托方法并存储
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
// 用于触发CanExecuteChanged事件的一个方法
public void OnCanExecuteChange()
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
使用示例
该示例有两个按钮,一个按钮用于触发命令,另一个按钮控制命令是否可以被执行
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 初始化命令,并指定任务和判断命令是否能执行的方法
CommandA = new RelayCommand(Fn, CanExecute);
}
// 声明命令
public RelayCommand CommandA { get; private set; }
// 命令要执行的任务
public void Fn(object o)
{
Console.WriteLine("触发了");
}
// 限制命令是否能执行的变量
bool canRun = true;
// 判断命令是否能执行的方法
bool CanExecute(object o)
{
return canRun;
}
// 按钮点击修改状态的方法
private void Button_Click(object sender, RoutedEventArgs e)
{
canRun = !canRun;
// 修改后要手动触发该方法,否则页面不会更新命令的状态
CommandA.OnCanExecuteChange();
}
}
<Button Command="{Binding CommandA}" Content="测试"/>
<Button Click="Button_Click" Content="修改状态"/>
MVVM示例1
实现MVVM需要遵循以下几个步骤:
- 创建一个Model类,定义应用程序所需的数据和业务逻辑。
- 创建一个ViewModel类,继承自INotifyPropertyChanged接口,并实现属性变更通知。在ViewModel中定义与Model相关联的属性,并提供相应的命令来执行用户操作。
- 创建一个View类(通常是一个XAML文件),定义应用程序的用户界面。在View中使用数据绑定来连接ViewModel中的属性和命令,并设置相关的样式和行为。
- 在App.xaml或其他合适的地方创建一个ViewModel实例,并将其作为View中DataContext属性值。
新建一个窗口:WindowExample1.xaml
创建一个Model类
创建一个Model类,定义应用程序所需的数据和业务逻辑。
新建一个文件夹命名为:Models,并添加一个类文件:User.cs
// Model class
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
创建一个ViewModel类
创建一个ViewModel类:UserInfoViewModel.cs,继承自INotifyPropertyChanged接口,并实现属性变更通知。在ViewModel中定义与Model相关联的属性,并提供相应的命令来执行用户操作。
public class UserInfoViewModel : INotifyPropertyChanged
{
private User user;
public UserInfoViewModel()
{
user = new User();
SaveCommand = new RelayCommand(Save);
CancelCommand = new RelayCommand(Cancel);
}
public string UserName
{
get { return user.Name; }
set
{
user.Name = value;
OnPropertyChanged("UserName");
}
}
public int UserAge
{
get { return user.Age; }
set
{
user.Age = value;
OnPropertyChanged("UserAge");
}
}
public string UserInfo
{
get { return $"Name:{UserName} Age:{UserAge}"; }
}
// 上一章节中封装的RelayCommand类实例
public ICommand SaveCommand { get; private set; }
public ICommand CancelCommand { get; private set; }
private void Save(object parameter)
{
// Save user data to database or service
MessageBox.Show("User data saved!");
OnPropertyChanged("UserInfo");
}
private void Cancel(object parameter)
{
// Close dialog window without saving data
var window = parameter as Window;
if (window != null)
window.Close();
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 实现通知更新
/// </summary>
/// <param name="propertyName"></param>
/// OnPropertyChanged这个属性在WinForm时代就有了,WPF只是向下兼容而已。WPF使用依赖属性自动通知注册者属性值更变。
/// OnPropertyChanged需要你在属性值每次变化的时候主动调用一个方法,会引发此事件,当Entity绑定到控件时,控件会主动注册OnPropertyChanged事件,所以属性变化的时候控件会自动更新,这就是数据绑定的基础。
/// OnPropertyChanged 监听属性值的变化 然后前端可以根据值的变化做出一些改变。比如checkbox 当你设定的isCheck值为 false 他就会把勾取消 你再点击一下 他的值变成了True
/// 然后会响应OnPropertyChanged 然后前端的checkbox就会自动有个勾选的状态 如果是类似name,id类的属性 前端当然就不会有什么改变了
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
UI
创建一个View类(通常是一个XAML文件),定义应用程序的用户界面。在View中使用数据绑定来连接ViewModel中的属性和命令,并设置相关的样式和行为。
<Window x:Class="MVVMDemo.WindowExample1"
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:MVVMDemo"
mc:Ignorable="d"
Title="WindowExample1" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Name:" Grid.Row="0" Grid.Column="0" Margin="10"/>
<TextBox Text="{Binding UserName}" Grid.Row="0" Grid.Column="1" Margin="10"/>
<Label Content="Age:" Grid.Row="1" Grid.Column="0" Margin="10"/>
<TextBox Text="{Binding UserAge}" Grid.Row="1" Grid.Column="1" Margin="10"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
Grid.Row="3" Grid.ColumnSpan="2">
<!--Foreground="{Binding Foreground,: 当前元素Button绑定目标元素的Foreground属性-->
<Button Content="Save" Command="{Binding SaveCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}" Margin= "10"/>
<Button Content="Cancel" Command="{Binding CancelCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}" Margin= "10"/>
</StackPanel>
</Grid>
</Window>
namespace MVVMDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class WindowExample1 : Window
{
public WindowExample1()
{
InitializeComponent();
this.DataContext = new UserInfoViewModel();
}
}
}
MvvmLight框架包
快速上手
引入该框架包之后, 默认会在目录下创建ViewModel层的示例代码 同时入口启动文件也发生了变化。
安装之后,代码报错: 解决办法
添加一个 Student 类
在 Models 中新增一个:Student.cs 类
namespace MVVMDemo.Models
{
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Sex { get; set; }
}
}
通过在MainViewModel中创建一些业务代码, 将其与MainWindow.xaml 通过上下文的方式关联起来, 而MainWindow则是通过Binding的写法 引用业务逻辑的部分。
- 在MainViewModel中, 添加同一个班级名称, 与学生列表, 分别用于显示在文本 和列表上展示, Command则用于绑定DataGrid的双击命令上, 通过双击, 展示点击行的学生信息: MainViewModel.cs:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MVVMDemo.Models;
using System.Collections.ObjectModel;
using System.Windows;
using RelayCommand = GalaSoft.MvvmLight.Command.RelayCommand;
namespace MVVMDemo.ViewModel
{
/// <summary>
/// This class contains properties that the main View can data bind to.
/// <para>
/// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
/// </para>
/// <para>
/// You can also use Blend to data bind with the tool's support.
/// </para>
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
ClassName = "200302班";
students = new ObservableCollection<Student>();
students.Add(new Student() { Name="张三",Age=18,Sex="男" });
students.Add(new Student() { Name="李四",Age=12,Sex="女" });
students.Add(new Student() { Name="王五",Age=20,Sex="男" });
}
public string ClassName { get; set; }
private ObservableCollection<Student> students;
public ObservableCollection<Student> Students
{
get { return students; }
set { students = value; RaisePropertyChanged(); }
}
private RelayCommand<Student> command;
public RelayCommand<Student> Command
{
get
{
if (command == null)
command = new RelayCommand<Student>((t)=> Rcommand(t));
return command;
}
}
private void Rcommand(Student stu)
{
MessageBox.Show($"学生的姓名:{stu.Name},学生的年龄:{stu.Age},学生的性别:{stu.Sex}");
}
}
}
- 设计UI层,在XMAL文件中 添加一个文本用于显示班级名称, 添加一个DataGrid 用于展示学生列表, 同时DataGrid中添加一个绑定的命令 MainWindow.xaml:
<Window x:Class="MVVMDemo.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:MVVMDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!--展示班级-->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Margin="5,0,0,0" Text="班级名称:"></TextBlock>
<TextBlock Margin="5,0,0,0" Text="{Binding ClassName}"></TextBlock>
</StackPanel>
<DataGrid Grid.Row="1" ItemsSource="{Binding Students}" AutoGenerateColumns="False">
<!--同时DataGrid中添加一个绑定的命令-->
<!--下面为一种绑定语法, 主要在MouseBinding中, MouseAction 以为触发的事件类型, CommandParameter 则是命令传递的参数,
也就是DataGrid选中的一行的类型 Student。Command 则是MainViewModel中定义的Command。RelativeSource FindAncestor,
主要用于控件模板或可预测的自包含 UI 组合。-->
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGrid},Path=SelectedItem}"
Command="{Binding Command}"/>
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="名称"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Age}" Header="年龄"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Sex}" Header="性别"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
namespace MVVMDemo
{
/// <summary>
/// WindowExample2.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
选中一行,鼠标左键双击测试
RaisePropertyChanged() 实现动态通知更新
- 通过set访问器更新ClassName的同时, 调用RaisePropertyChanged 方法, 界面刷新更新后的值;
private string className;
public string ClassName
{
get { return className; }
set { className = value; RaisePropertyChanged(); }
}
- 添加一个无参数的UpdateCommand , 并设置为 UpdateText 手动把ClassName更新为 “其他班级”:
private RelayCommand updateCommand;
public RelayCommand UpdateCommand
{
get
{
if (updateCommand == null)
updateCommand = new RelayCommand(() => UpdateText());
return updateCommand;
}
}
private void UpdateText()
{
ClassName = "其他班级";
}
- UI层添加一个简单按钮, 绑定后台的UpdateCommand命令
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="刷新" Command="{Binding UpdateCommand}"></Button>
<TextBlock Margin="5,0,0,0" Text="班级名称:"></TextBlock>
<TextBlock Margin="5,0,0,0" Text="{Binding ClassName}"></TextBlock>
</StackPanel>
CommunityToolkit.Mvvm 框架包
包 `` (前称 Microsoft.Toolkit.Mvvm
MVVM 工具包)是一个现代、快速和模块化的 MVVM 库。 它是 .NET 社区工具包的一部分,围绕以下原则构建:
- 平台和运行时 Independent.NET - Standard 2.0 、 .NET Standard 2.1 和 .NET 6 (UI 框架不可知)
- 易于选取和使用 - 对应用程序结构或编码范例(在“MVVM”外部)没有严格的要求,即灵活使用。
- 点菜 - 自由选择要使用的组件。
- 参考实现 - 精简和高性能,为基类库中包含的接口提供实现,但缺少直接使用它们的具体类型。
使用入门
若要从 Visual Studio 中安装包,请执行以下操作:
-
在解决方案资源管理器中,右键单击项目并选择“管理 NuGet 包”。 搜索 CommunityToolkit.Mvvm 并安装它。
-
添加 using 或 Imports 指令以使用新 API:
using CommunityToolkit.Mvvm;
ObservableObject 可观测对象
在 WPF 中, 写出 MVVM 设计模式的程序, 自然需要进行前台与后台数据的绑定, 而在原生实现中, 编写一个可绑定的类,继承INotifyPropertyChanged
接口,并且编写该类中可绑定的成员, 是非常麻烦的一件事情。 你需要手动在值变更的时候, 引发 “PropertyChanged” 事件,这在前面的章节已经讨论过了。
ObservableObject
这是通过实现INotifyPropertyChanged
和INotifyPropertyChanging
接口可观察的对象的基类。 它可以用作需要支持属性更改通知的各种对象的起点。我们要封装一个可观测的对象,应该让该类继承自ObservableObject
ObservableObject
具有以下主要功能:
- 它为和
INotifyPropertyChanging
公开PropertyChanged
事件PropertyChanging
提供了基本实现INotifyPropertyChanged
。 - 它提供了一系列
SetProperty
方法,可用于从继承ObservableObject
自的类型轻松设置属性值,并自动引发相应的事件。 - 它提供了类似于
SetPropertyAndNotifyOnCompletion
此方法,SetProperty
但能够设置Task
属性并在分配的任务完成后自动引发通知事件。 - 它公开了可在派生类型中重写的
OnPropertyChanged
和OnPropertyChanging
方法,以自定义如何引发通知事件。
下面是一个可观测对象的示例,并实现了一个自定义的属性进行通知
public class MainViewModel : ObservableObject
{
private string test = "测试哈哈哈";
public string MyTest
{
get { return test; }
set { SetProperty(ref test, value); }
}
}
使用SetProperty
方法更新属性值,并在特定的时候进行通知,引发相关的事件
RelayCommand和RelayCommand<T>
ICommand接口是用于在 .NET 中为 Windows 运行时 应用编写的命令的代码协定。 这些命令为 UI 元素提供命令行为,如Button的Command。
RelayCommand实现了ICommand接口,可以将一个方法或委托绑定到视图(View)上。
这个的使用方法和我们自己实现的RelayCommand
基本相同,示例如下
public class ViewModel : ObservableObject
{
// 1、定义ICommand类型的属性来存储命令
public ICommand AddCommand { get; }
// 2、创建命令所执行的方法
void Fn()
{
}
// 3、在构造函数中对命令进行初始化
public MainViewModel()
{
AddCommand = new RelayCommand(Fn);
AddMoreCommand = new RelayCommand<string>(AddMore);
}
// 有参的命令
public ICommand AddMoreCommand { get; }
void AddMore(string a)
{
}
}
<Button Content="点击增加" Command="{Binding AddCommand}"/>
我们可以通过传递一个方法来实现对命令的可执行状态进行控制,使用方式如下
public partial class MainViewModel : ObservableObject
{
public MainViewModel()
{
// 创建命令并指定任务和判断命令是否可执行的方法
AddCommand = new RelayCommand(Add, CanAdd);
}
// 可监听的字段和属性
private int num;
public int Num
{
get => num;
set
{
SetProperty(ref num, value);
// !!!!!!!!!!!!一定要在依赖属性变化时通知对应的命令,否则不会变更命令的状态
AddCommand.NotifyCanExecuteChanged();
}
}
public IRelayCommand AddCommand { get; }
bool CanAdd() => Num <= 10;
void Add()
{
Num++;
}
}
AsyncRelayCommand
提供了和RelayCommand一样的基础命令功能,但是在此基础上,增加了异步。
AsyncRelayCommand具备功能如下:
- 支持异步操作,可以返回Task。
- 使用带ConcellationToken重载的版本,可以取消Task。公开了CanBeCanceled和IsCancellationRequested属性,以及Cancel()方法。
- 公开ExecutionTask属性,可用于监视待处理操作的进度。公开 IsRunning
属性,可以用于判断操作是否完成
- 实现了IAsyncRelayCommand and IAsyncRelayCommand<T>接口。IAsyncRelayCommand就是在IRelayCommand接口的基础上增加异步操作的接口。
使用方法如下
class Test : ObservableObject
{
// 存储命令的属性
public AsyncRelayCommand AsyncComm { get; }
// 执行的异步任务,需要返回一个Task
private Task Abc()
{
Task t = new Task(() =>
{
Thread.Sleep(1000);
Num = 10;
});
t.Start();
return t;
}
public MainViewModel()
{
// 进行命令的初始化
AsyncComm = new AsyncRelayCommand(Abc);
}
}
<!--绑定命令-->
<Button Content="异步命令" Command="{Binding AsyncComm}" CommandParameter="3"/>
<!--IsRunning属性用于获取命令是否正在运行-->
<CheckBox IsChecked="{Binding AsyncComm.IsRunning,Mode=OneWay}" Content="运行中"/>
<TextBlock>
<Run Text="Status: "/>
<!--Status用于获取命令的状态-->
<Run Text="{Binding AsyncComm.ExecutionTask.Status,Mode=OneWay}"/>
</TextBlock>
标签:绑定,MVVM,框架,08,ViewModel,命令,属性,public,View
From: https://www.cnblogs.com/laoguonana/p/08-mvvm-framework-1o2wgj.html