
WPF CommunityToolkit.Mvvm初探
什么是 CommunityToolkit.Mvvm?
CommunityToolkit.Mvvm 是一个现代化的 MVVM 框架,用于 .NET 应用程序。它提供了一系列工具和特性,帮助开发者更高效地实现 MVVM 模式,减少样板代码,并提升代码的可维护性。
为什么选择 CommunityToolkit.Mvvm?
简化 MVVM 实现:自动实现 INotifyPropertyChanged 接口,减少样板代码。
跨平台支持:适用于 WPF、UWP、MAUI 等多种 .NET 应用。
功能丰富:提供命令处理、消息传递、数据验证等功能。
社区支持:不断更新和改进,确保工具包的稳定性和易用性
安装 CommunityToolkit.Mvvm
通过执行如下命令可以进行安装。
Install-Package CommunityToolkit.Mvvm
Install-Package MvvmLightLibs
ObservableObject
是一个基类,用于实现 INotifyPropertyChanged
和 INotifyPropertyChanging
接口,从而支持属性更改通知。AsyncRelayCommand
是一个 ICommand
实现,扩展了 RelayCommand
的功能,支持异步操作。
接下来我们的定义一个MainViewModel
类,创建一个按钮的命令,让它暂停3秒后输出"Hello CommunityToolkit"
,内容如下:
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; }
}
然后我们进行IOC注册,在ViewModelLocator
类中编辑如下内容:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel MainViewModel { get => ServiceLocator.Current.GetInstance<MainViewModel>(); }
}
另外我们添加一个Task的扩展类,用于在数据传输的时候进行转换。
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
}
}
然后我们定义一个Task转换类型TaskResultConverter
类。
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();
}
}
接下来我们编辑MainWindow.xaml
代码,对其进行绑定。
<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>
开始点击3秒前的Task状态是:WaitingForActivation
。
点击了3秒后的Task状态:RanToCompletion
,并输出如下内容:
消息传递
什么是 WeakReferenceMessenger?
WeakReferenceMessenger 是一个基于弱引用的消息传递系统,它允许在应用程序的不同部分之间进行松耦合的通信。与传统的强引用不同,弱引用不会阻止对象被垃圾回收器回收,这使得 WeakReferenceMessenger 能有效避免内存泄漏问题,尤其在 MVVM 架构中显得尤为重要
举例
首先我们使用简单的string
类型进行传输,然后通过消息框进行弹出。
首先在MainViewModel
添加一个新的ICommand
对象取名为BtnMsgCommand
,进行发送一个Hello
的消息。
public ICommand BtnMsgCommand {
get=>new RelayCommand(() =>
{
WeakReferenceMessenger.Default.Send<string>("Hello");
});
}
然后在MainWindow
中注册该方法,并进行弹出一个消息框进行处理.
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);
}
}
然后我们在前端页面可以添加一个新的按钮进行绑定命令。
<Button Command="{Binding BtnMsgCommand}" Content="Msg Button"/>
ValueChangedMessage
ValueChangedMessage<T>
是一个通用类,用于表示某个值已更改的消息。
它通常与WeakReferenceMessenger
一起使用,以便在应用程序的不同组件之间传递数据变化的通知。
首先我们定义一个MessageObject
用于实现消息传输的类。
public class MessageObject:ValueChangedMessage<string>
{
public MessageObject(string message)
: base(message)
{
}
}
修改一下BtnMsgCommand
,发送的时候我们将发送一个MessageObject
对象。
public ICommand BtnMsgCommand {
get=>new RelayCommand(() =>
{
WeakReferenceMessenger.Default.Send<MessageObject>(new MessageObject("Hello"));
});
}
然后在窗体后台进行增重。
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
IRecipient<TMessage>
是一个泛型接口,用于标识一个对象可以接收特定类型的消息。
在使用 WeakReferenceMessenger
时,如果一个类实现了 IRecipient<TMessage>
接口,那么它就可以接收所有通过 WeakReferenceMessenger
发送的 TMessage
类型的消息。
修改MainViewModel
中的实现:
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"));
});
}
}
当我们进行发送string
类型的参数的方法时候,Receive
方法会先接收到,然后再到达前端窗体。
替换IOC容器
在 MVVM 架构模式下,依赖注入(IoC)容器是实现解耦和管理对象生命周期的关键组件。MvvmLight 是一个广受欢迎的 MVVM 框架,它提供了自己的 IoC 容器 SimpleIoc,但随着 .NET 生态系统的发展,Microsoft.Extensions.DependencyInjection(.NET Core 中的依赖注入框架)凭借其简洁性和灵活性,逐渐成为许多开发者的首选。
代码示例
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>(); }
}
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

