WPF CommunityToolkit.Mvvm初探 电脑版发表于:2025/4/15 13:11  ># WPF CommunityToolkit.Mvvm初探 [TOC] 什么是 CommunityToolkit.Mvvm? ------------ tn2>CommunityToolkit.Mvvm 是一个现代化的 MVVM 框架,用于 .NET 应用程序。它提供了一系列工具和特性,帮助开发者更高效地实现 MVVM 模式,减少样板代码,并提升代码的可维护性。 为什么选择 CommunityToolkit.Mvvm? ------------ tn2>简化 MVVM 实现:自动实现 INotifyPropertyChanged 接口,减少样板代码。 跨平台支持:适用于 WPF、UWP、MAUI 等多种 .NET 应用。 功能丰富:提供命令处理、消息传递、数据验证等功能。 社区支持:不断更新和改进,确保工具包的稳定性和易用性 安装 CommunityToolkit.Mvvm ------------ tn2>通过执行如下命令可以进行安装。 ```bash Install-Package CommunityToolkit.Mvvm Install-Package MvvmLightLibs ```  tn2>`ObservableObject` 是一个基类,用于实现 `INotifyPropertyChanged` 和 `INotifyPropertyChanging` 接口,从而支持属性更改通知。 `AsyncRelayCommand` 是一个 `ICommand` 实现,扩展了 `RelayCommand` 的功能,支持异步操作。 接下来我们的定义一个`MainViewModel`类,创建一个按钮的命令,让它暂停3秒后输出`"Hello CommunityToolkit"`,内容如下: ```csharp public class MainViewModel:ObservableObject { public MainViewModel() { BtnCommand = new AsyncRelayCommand(DoCommand); } private async Task<string> DoCommand() { await Task.Delay(3000); return "Hello CommunityToolkit"; } public ICommand BtnCommand { get; } } ``` tn2>然后我们进行IOC注册,在`ViewModelLocator`类中编辑如下内容: ```csharp public class ViewModelLocator { public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SimpleIoc.Default.Register<MainViewModel>(); } public MainViewModel MainViewModel { get => ServiceLocator.Current.GetInstance<MainViewModel>(); } } ``` tn2>另外我们添加一个Task的扩展类,用于在数据传输的时候进行转换。 ```csharp public static class TaskExtensions { [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object? GetResultOrDefault(this Task task) { // Check if the instance is a completed Task if ( #if NETSTANDARD2_1 task.IsCompletedSuccessfully #else task.Status == TaskStatus.RanToCompletion #endif ) { if (task != Task.CompletedTask) { PropertyInfo? propertyInfo = #if NETSTANDARD1_4 task.GetType().GetRuntimeProperty(nameof(Task<object>.Result)); #else task.GetType().GetProperty(nameof(Task<object>.Result)); #endif // Return the result, if possible return propertyInfo?.GetValue(task); } } return null; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? GetResultOrDefault<T>(this Task<T?> task) { #if NETSTANDARD2_1 return task.IsCompletedSuccessfully ? task.Result : default; #else return task.Status == TaskStatus.RanToCompletion ? task.Result : default; #endif } } ``` tn2>然后我们定义一个Task转换类型`TaskResultConverter`类。 ```csharp public class TaskResultConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is Task task) { return task.GetResultOrDefault(); } return null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } ``` tn2>接下来我们编辑`MainWindow.xaml`代码,对其进行绑定。 ```csharp <Window x:Class="Learning_mvvmlight.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:localmodel="clr-namespace:Learning_mvvmlight.ViewModel" xmlns:local="clr-namespace:Learning_mvvmlight" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <localmodel:MainViewModel/> </Window.DataContext> <Window.Resources> <local:TaskResultConverter x:Key="TaskResultConverter"/> </Window.Resources> <Grid> <StackPanel> <!-- 绑定 TextBlock 的 Text 属性到 BtnCommand.ExecutionTask,通过 TaskResultConverter 转换显示结果 --> <TextBlock Text="{Binding BtnCommand.ExecutionTask,Converter={StaticResource TaskResultConverter}}"/> <!-- 绑定 TextBlock 的 Text 属性到 BtnCommand.ExecutionTask 的 Status 属性,直接显示任务状态 --> <TextBlock Text="{Binding BtnCommand.ExecutionTask.Status}"/> <!-- 将 Button 的 Command 属性绑定到 BtnCommand,点击按钮时触发 BtnCommand 对应的命令逻辑 --> <Button Command="{Binding BtnCommand}" Content="Button"/> </StackPanel> </Grid> </Window> ```  tn2>开始点击3秒前的Task状态是:`WaitingForActivation`。  tn2>点击了3秒后的Task状态:`RanToCompletion`,并输出如下内容:  消息传递 ------------ ### 什么是 WeakReferenceMessenger? tn2>WeakReferenceMessenger 是一个基于弱引用的消息传递系统,它允许在应用程序的不同部分之间进行松耦合的通信。与传统的强引用不同,弱引用不会阻止对象被垃圾回收器回收,这使得 WeakReferenceMessenger 能有效避免内存泄漏问题,尤其在 MVVM 架构中显得尤为重要 ### 举例 tn2>首先我们使用简单的`string`类型进行传输,然后通过消息框进行弹出。 首先在`MainViewModel`添加一个新的`ICommand`对象取名为`BtnMsgCommand`,进行发送一个`Hello`的消息。 ```csharp public ICommand BtnMsgCommand { get=>new RelayCommand(() => { WeakReferenceMessenger.Default.Send<string>("Hello"); }); } ``` tn2>然后在`MainWindow`中注册该方法,并进行弹出一个消息框进行处理. ```csharp public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); WeakReferenceMessenger.Default.Register<string>(this,DoSomThing); } /// <summary> /// 处理消息 /// </summary> /// <param name="recipient">这里是当前控件</param> /// <param name="message">这是消息</param> private void DoSomThing(object recipient, string message) { MessageBox.Show(message); } } ``` tn2>然后我们在前端页面可以添加一个新的按钮进行绑定命令。 ```csharp <Button Command="{Binding BtnMsgCommand}" Content="Msg Button"/> ```  ### ValueChangedMessage tn2>`ValueChangedMessage<T>` 是一个通用类,用于表示某个值已更改的消息。 它通常与`WeakReferenceMessenger`一起使用,以便在应用程序的不同组件之间传递数据变化的通知。 首先我们定义一个`MessageObject`用于实现消息传输的类。 ```csharp public class MessageObject:ValueChangedMessage<string> { public MessageObject(string message) : base(message) { } } ``` tn2>修改一下`BtnMsgCommand`,发送的时候我们将发送一个`MessageObject`对象。 ```csharp public ICommand BtnMsgCommand { get=>new RelayCommand(() => { WeakReferenceMessenger.Default.Send<MessageObject>(new MessageObject("Hello")); }); } ``` tn2>然后在窗体后台进行增重。 ```csharp public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); WeakReferenceMessenger.Default.Register<string>(this,DoSomThing); WeakReferenceMessenger.Default.Register<MessageObject>(this, DoSomThing); } private void DoSomThing(object recipient, MessageObject message) { MessageBox.Show("MessageObject:" + message.Value); } /// <summary> /// 处理消息 /// </summary> /// <param name="recipient">这里是当前控件</param> /// <param name="message">这是消息</param> private void DoSomThing(object recipient, string message) { MessageBox.Show(message); } } ```  ### IRecipient tn2>`IRecipient<TMessage>` 是一个泛型接口,用于标识一个对象可以接收特定类型的消息。 在使用 `WeakReferenceMessenger` 时,如果一个类实现了 `IRecipient<TMessage>` 接口,那么它就可以接收所有通过 `WeakReferenceMessenger` 发送的 `TMessage` 类型的消息。 修改`MainViewModel`中的实现: ```csharp public class MainViewModel:ObservableRecipient,IRecipient<string> { public MainViewModel() { BtnCommand = new AsyncRelayCommand(DoCommand); // 消息开关 this.IsActive = true; } private async Task<string> DoCommand() { await Task.Delay(3000); return "Hello CommunityToolkit"; } public void Receive(string message) { } public ICommand BtnCommand { get; } public ICommand BtnMsgCommand { get=>new RelayCommand(() => { WeakReferenceMessenger.Default.Send<string>("Hello"); //WeakReferenceMessenger.Default.Send<MessageObject>(new MessageObject("Hello")); }); } } ``` tn2>当我们进行发送`string`类型的参数的方法时候,`Receive`方法会先接收到,然后再到达前端窗体。     替换IOC容器 ------------ tn2>在 MVVM 架构模式下,依赖注入(IoC)容器是实现解耦和管理对象生命周期的关键组件。MvvmLight 是一个广受欢迎的 MVVM 框架,它提供了自己的 IoC 容器 SimpleIoc,但随着 .NET 生态系统的发展,Microsoft.Extensions.DependencyInjection(.NET Core 中的依赖注入框架)凭借其简洁性和灵活性,逐渐成为许多开发者的首选。 ### 代码示例 ```csharp public class ViewModelLocator { public static IServiceProvider ServiceProvider { get; set; } public ViewModelLocator() { //ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); //SimpleIoc.Default.Register<MainViewModel>(); ServiceProvider = GetService(); } private IServiceProvider GetService() { var service = new ServiceCollection(); service.AddSingleton<MainViewModel>(); return service.BuildServiceProvider(); } //public MainViewModel MainViewModel { get => ServiceLocator.Current.GetInstance<MainViewModel>(); } public MainViewModel MainViewModel { get => ServiceProvider.GetService<MainViewModel>(); } } ```