[WPF] 使用 MVVM Toolkit 构建 MVVM 程序
1. 什么是 MVVM Toolkit
模型-视图-视图模型 (MVVM) 是用于解耦 UI 代码和非 UI 代码的 UI 体系结构设计模式。 借助 MVVM,可以在 XAML 中以声明方式定义 UI,并使用数据绑定标记将 UI 链接到包含数据和命令的其他层。
微软虽然提出了 MVVM,但又没有提供一个官方的 MVVM 库(多年前有过 Prism,但已经离家出走了)。每次有人提起 MVVM 库,有些人会推荐 Prism(例如我),有些人会推荐 MVVMLight。可是现在 Prism 已经决定不再支持 UWP, 而 MVVMLight 又不再更新,在这左右为难的时候 Windows Community Toolkit 挺身而出发布了 MVVM Toolkit。 MVVM Toolkit 延续了 MVVMLight 的风格,是一个轻量级的组件,而且它基于 .NET Standard 2.0,可用于UWP, WinForms, WPF, Xamarin, Uno 等多个平台。相比它的前身 MVVMLight,它有以下特点:
- 更高:版本号更高,一出手就是 7.0。
- 更快:速度更快,MVVM Toolkit 从一开始就以高性能为实现目标。
- 更强:后台更强,MVVM Toolkit 的全程是 'Microsoft.Toolkit.Mvvm',根正苗红。
目前,MVVM Toolkit 已经更新到 '7.0.2',它的详细资料可以参考下面链接:
Nuget:https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm
文档:https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction
源码:https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Mvvm
虽然是 Windows Community Toolkit 项目的一部分,但它有独立的 Sample 和文档,可以在这里找到:
https://github.com/CommunityToolkit/MVVM-Samples
这篇文章将简单介绍 MVVM Toolkit 的几个基本组件。
2. 各个组件
2.1 ObservableObject
ObservableObject 实现了 INotifyPropertyChanged
和INotifyPropertyChanging
,并触发 PropertyChanged
和 PropertyChanging
事件。
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
在这段示例代码中,如果 name 和 value 的值不同,首先触发 PropertyChanging
事件,然后触发 PropertyChanged
。
2.2 RelayCommand
RelayCommand
和 RelayCommand<T>
实现了 ICommand
接口,INotifyPropertyChanged
和 ICommand
是 MVVM 模式的基础。下面的代码使用 ObservableObject
和 RelayCommand
展示一个基本的 ViewModel:
public class MyViewModel : ObservableObject
{
public MyViewModel()
{
IncrementCounterCommand = new RelayCommand(IncrementCounter);
}
private int counter;
public int Counter
{
get => counter;
private set => SetProperty(ref counter, value);
}
public ICommand IncrementCounterCommand { get; }
private void IncrementCounter() => Counter++;
}
<Page
x:Class="MyApp.Views.MyPage"
xmlns:viewModels="using:MyApp.ViewModels">
<Page.DataContext>
<viewModels:MyViewModel x:Name="ViewModel"/>
</Page.DataContext>
<StackPanel Spacing="8">
<TextBlock Text="{x:Bind ViewModel.Counter, Mode=OneWay}"/>
<Button
Content="Click me!"
Command="{x:Bind ViewModel.IncrementCounterCommand}"/>
</StackPanel>
</Page>
在这段示例里 IncrementCounterCommand
包装了 IncrementCounter
函数提供给 Button 绑定。IncrementCounter
函数更改 Counter
的值并通过 PropertyChanged
事件通知绑定的 TextBlock。
2.3 AsyncRelayCommand
AsyncRelayCommand
和 AsyncRelayCommand<T>
也实现了 ICommand
,不过它们支持异步操作,提供的 ExecutionTask
和 IsRunning
两个属性对监视任务运行状态十分有用。
例如这个 ViewModel:
public MyViewModel()
{
DownloadTextCommand = new AsyncRelayCommand(DownloadTextAsync);
}
public IAsyncRelayCommand DownloadTextCommand { get; }
private async Task<string> DownloadTextAsync()
{
await Task.Delay(3000); // Simulate a web request
return "Hello world!";
}
使用相关的 UI 代码:
<Page.Resources>
<converters:TaskResultConverter x:Key="TaskResultConverter"/>
</Page.Resources>
<StackPanel Spacing="8">
<TextBlock>
<Run Text="Task status:"/>
<Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/>
<LineBreak/>
<Run Text="Result:"/>
<Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, Mode=OneWay}"/>
</TextBlock>
<Button
Content="Click me!"
Command="{x:Bind ViewModel.DownloadTextCommand}"/>
<muxc:ProgressRing
HorizontalAlignment="Left"
IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/>
</StackPanel>
点击 Button
后 DownloadTextAsync
开始运行,在 UI 上 TextBlock 和 ProgressRing 绑定到 ExecutionTask
和 IsRunning
并显示任务运行状态,最后通过 TaskResultConverter
显示任务结果。
2.4 Messenger
对于主要目的是松耦合的 MVVM 框架,提供一个用于消息交换的系统十分有必要。MVVM Toolkit 中用于消息交换的核心是 WeakReferenceMessenger
类。
// Create a message
public class LoggedInUserChangedMessage : ValueChangedMessage<User>
{
public LoggedInUserChangedMessage(User user) : base(user)
{
}
}
// Register a message in some module
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) =>
{
// Handle the message here, with r being the recipient and m being the
// input messenger. Using the recipient passed as input makes it so that
// the lambda expression doesn't capture "this", improving performance.
});
// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));
正如这段代码所示,WeakReferenceMessenger
主要通过 Register
和 Send
进行信息交换,它的使用方式类似于 MVVMLight 的 messenger 类。MVVM Toolkit 另外还提供了一个 StrongReferenceMessenger
类,更多使用方法可以参考这篇 文档。Messenger
功能强大且简单易用,但也由于误用会带来风险而引发了一些争议,有必要更详细地理解它的原理和用法以避免它带来的其它风险,这篇文章只是简单地介绍一下它的用法。
2.5 ObservableRecipient
ObservableRecipient
继承了 ObservableObject
并支持从 Messenger
接收信息,可通过 IsActive
属性激活或停用。它可以用作 ViewModel 的基类,事实上它的作用基本上相遇于 MVVMLight 中的 ViewModelBase :
public class MyViewModel : ObservableRecipient, IRecipient<LoggedInUserRequestMessage>
{
public void Receive(LoggedInUserRequestMessage message)
{
// Handle the message here
}
}
3. The 性能
MVVM Toolkit 在开发过程中为了追求卓越的性能做了很多努力,例如提供一个 StrongReferenceMessenger
类,性能如上图所示地有了大幅提升。又例如下面这篇文章所介绍的:
MVVM Toolkit Preview 3 & The Journey of an API
有兴趣的话可以通过源码详细了解一下。
4. 结语
这篇文章简单介绍了 MVVM Toolkit 中的主要功能,更多内容可参考 源码、单元测试 或 windows-toolkit/MVVM-Samples 中提供的示例应用:
5. 参考
Microsoft.Toolkit.Mvvm at master
[Feature] Basic MVVM primitives (.NET Standard)
NuGet Gallery _ Microsoft.Toolkit.Mvvm
[Feature] Microsoft.Toolkit.Mvvm package (Preview 5)
MVVM Toolkit Preview 3 & The Journey of an API
[WPF] 使用 MVVM Toolkit 构建 MVVM 程序的更多相关文章
- 使用MVVM设计模式构建WPF应用程序
使用MVVM设计模式构建WPF应用程序 本文是翻译大牛Josh Smith的文章,WPF Apps With The Model-View-ViewModel Design Pattern,译者水平有 ...
- UWP应用程序使用Prism框架构建MVVM
在我们创建的UWP解决方案中选择引用->管理NuGet包程序包 NuGet管理包 2. 搜索Prism.Core,并安装 搜索Prism.Core 3. 搜索Prism.Unity,并安装 搜索 ...
- 使用 MVVM Toolkit Source Generators
关于 MVVM Toolkit 最近 .NET Community Toolkit 发布了 8.0.0 preview1,它包含了从 Windows Community Toolkit 迁移过来的以下 ...
- WPF学习12:基于MVVM Light 制作图形编辑工具(3)
本文是WPF学习11:基于MVVM Light 制作图形编辑工具(2)的后续 这一次的目标是完成 两个任务. 本节完成后的效果: 本文分为三个部分: 1.对之前代码不合理的地方重新设计. 2.图形可选 ...
- WPF学习11:基于MVVM Light 制作图形编辑工具(2)
本文是WPF学习10:基于MVVM Light 制作图形编辑工具(1)的后续 这一次的目标是完成 两个任务. 画布 效果: 画布上,选择的方案是:直接以Image作为画布,使用RenderTarget ...
- WPF 线程:使用调度程序构建反应速度更快的应用程序
原文:WPF 线程:使用调度程序构建反应速度更快的应用程序 作者:Shawn Wildermuth 原文:http://msdn.microsoft.com/msdnmag/issues/07/10/ ...
- 说不尽的MVVM(1) – Why MVVM
最近学的一篇课文<说不尽的狗>竟让我有了写<说不尽的MVVM>这一想法,事非亵渎,实出无奈.我在刚学WPF不久时听说有MVVM这种东西,做了下尝试,发现他能给程序的设计带来很大 ...
- iOS-马上着手开发iOS应用应用程序-第二部分构建应用程序
第二部分构建应用程序 1,应用程序开发过程 2,设计用户界面 3,定义交互 4,教程:串联图 1,应用程序开发过程 定义概念 设计用户界面 定义交互 实现行为整合数据 对象是应用程序的基石 类是对象的 ...
- 实战案例--Grunt构建Web程序
GruntJS构建Web程序.使用Gruntjs来搭建一个前端项目,然后使用grunt合并,压缩JS文件,熟练了node.js安装和grunt.js安装后,接下来来实战一个案例,案例是根据snandy ...
随机推荐
- nohup &的用法、进程查看以及终止
p.p1 { margin: 0 0 2px; font: 16px ".PingFang SC" } p.p2 { margin: 0; font: 12px "Hel ...
- redis学习笔记(二)——java中jedis的简单使用
redis怎么在java中使用,那就是要用到jedis了,jedis是redis的java版本的客户端实现,原本原本想上来就直接学spring整合redis的,但是一口吃个胖子,还是脚踏实地,从基础开 ...
- B站挂了之后出现的tengine是个啥?
一.描述 晚上刚洗漱完之后听同学说:B站挂了?woc?真挂了? 嗯!确实挂了,404的状态码,懂的都懂. 不过,最下面的tengine字眼吸引了我的注意,一时兴起,打算看看它是个什么东西,起码搞一个h ...
- MySQL基础篇(一)
本文主要内容为MySQL的基础语句以及正则表达式等内容. 本文操作的数据库内容存在个人github:https://github.com/YuanGao-1/blog_demo.git init_sc ...
- s3cmd的使用
目录 1. 安装s3cmd 2. 配置s3cmd 3. 使用s3cmd [前言] s3cmd 是用于创建S3桶,上传,检索和管理数据到对象存储命令行实用程序. 本文将指导linux下安装s3cmd程序 ...
- docker之构建镜像
构建Docker镜像有以下两种方法: 使用docker commit命令. 使用docker build命令和 Dockerfile 文件. 在这里并不推荐使用docker commit来构建镜像,而 ...
- Java基础00-函数式接口33
1. 函数式接口 1.1 函数式接口概述 代码示例: 定义一个接口: //次注解表示该接口为函数式接口,只能有一个抽象方法,如果有第二个就会报错. @FunctionalInterface publi ...
- sync/fsync/fdatasync的简单比较
此文主要转载自 http://blog.csdn.net/zbszhangbosen/article/details/7956558 官网上有关于MySQL的flush method的设置参数说明,但 ...
- python读取csv,Excel,Txt,Yaml 文件
1.数据 1.Csv login.csv文件: byhy,88888888 ReadCsv.py文件 import csv #导入csv包 class ReadCsv(): def csv(self) ...
- 浅析java中的IO流
在java中IO类很庞大,初学的时候觉得傻傻分不清楚.其实java流归根结底的原理是普通字节流,字节缓冲流,转换流.最基础的是普通字节流,即从硬盘读取字节写入到内存中,但在实际使用中又发现一些特殊的需 ...