MVVM模式
MVVM模式是一种设计模式,用于将应用程序的用户界面(UI)与业务逻辑分离。它主要包括三个部分:模型(Model)、视图(View)和视图模型(ViewModel)。MVVM模式的主要特点是数据绑定和命令绑定,通过这两种绑定方式,可以实现UI和数据的自动同步更新。
MVVM模式的实现思路如下:
- 实现一个数据监听器Observer,能够对Model的所有属性进行监听,当Model的属性有变化时,通知订阅者。
- 实现一个指令解析器Compile,对每个元素的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
- 实现一个Watcher,做为连接Observer和Compile的桥梁,订阅并收到每个属性数据变化的通知,执行指令绑定的相应回调函数,从而更新视图。
MVVM模式的主要功能包括:
- 提供了一种简单的方式来处理UI和数据之间的同步问题。
- 通过数据绑定,可以实现UI和数据的自动同步更新,减少了手动操作UI的复杂性。
- 通过命令绑定,可以实现UI和业务逻辑的解耦,提高了代码的可维护性和可读性。
- 提供了一种灵活的方式来处理UI的事件和用户交互。
CommunityToolkit.Mvvm 是实现MVVM模式的类库,可以更方便实现MVVM模式,并简化代码量,提高开发效率。
主要功能:官方文档MVVM 工具包简介 - MVVM 工具包简介 - Community Toolkits for .NET | Microsoft Learn加州消费者隐私法案 (CCPA) 禁用图标加州消费者隐私法案 (CCPA) 禁用图标
ObservableObject
ObservableObject这是通过实现INotifyPropertyChanged和INotifyPropertyChanging接口可观察的对象的基类。 它可以用作需要支持属性更改通知的各种对象的起点。
简单使用
提供 SetProperty<T>(ref T, T, string) 的方法检查属性的当前值,并更新它(如果不同)
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
特性使用
public partial class User : ObservableObject
{
[ObservableProperty]
private string name;
}
通知依赖属性
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
private string? name;
可以使用 NotifyPropertyChangedFor 特性实现,等效如下代码
public string? Name
{
get => name;
set
{
if (SetProperty(ref name, value))
{
OnPropertyChanged("FullName");
}
}
}
通知依赖命令
/// <summary>
/// 4、通知依赖命令. 控制一个命令是否可以执行.
/// 这里需要定义一个Reset方法,不要加Command,加上特性 [RelayCommand]
/// 使用[NotifyCanExecuteChangedFor(nameof(ResetCommand))]
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ResetCommand))]
private bool resetIncrement;
[RelayCommand(CanExecute = nameof(ResetIncrement))]
private void Reset()
{
Increment = 1;
Counter = 0;
}
等效
public string? Name
{
get => name;
set
{
if (SetProperty(ref name, value))
{
MyCommand.NotifyCanExecuteChanged();
}
}
}
ObservableValidator
请求属性验证,ObservableValidator继承 ObservableObject, INotifyDataErrorInfo. 主要增加严重和,错误消息处理。
[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[MinLength(2,ErrorMessage = "至少输入2位")]
[MaxLength(20, ErrorMessage = "至多输入20位")]
private string? userName;
RelayCommand
RelayCommand和 RelayCommand些实现可向视图公开方法或委托。 这些类型充当在 viewmodel 和 UI 元素之间绑定命令的方法。
使用ICommand
public RelayCommandDemoCtlViewModel()
{
IncrementCounterCommand = new RelayCommand(IncrementCounter);
}
private int counter;
public int Counter
{
get => counter;
private set => SetProperty(ref counter, value);
}
/// <summary>
/// 1、 使用 ICommand
/// </summary>
public ICommand IncrementCounterCommand { get; }
private void IncrementCounter() => Counter++;
AsyncRelayCommand
public RelayCommandDemoCtlViewModel()
{
// 2、使用 AsyncRelayCommand
LoadAsyncCommand = new AsyncRelayCommand<string>(DoLongTimeTaskAsync);
}
public IAsyncRelayCommand<string> LoadAsyncCommand { get; }
private async Task DoLongTimeTaskAsync(string? name)
{
var text = await Task.Run(async () =>
{
await Task.Delay(5000);
return "我执行花费了5秒异步事件任务";
});
Result = text;
}
[ObservableProperty]
private string result;
AsyncRelayCommand 单机按钮后,按钮会置灰指导任务完成.
RelayCommand 属性
/// <summary>
/// 3、RelayCommand 特性简单使用.
/// 它回自动生产InfoCommand 方法
/// </summary>
[RelayCommand]
private void Info()
{
Growl.Info("我是RelayCommand特性使用,它回让事件定义更简单");
}
/// <summary>
/// 4、RelayCommand 特性简单使用.
/// 它回自动生产WarningCommand 方法
/// </summary>
[RelayCommand]
private async Task Warning()
{
await Task.Delay(100);
Growl.Warning("我是RelayCommand特性异步方法使用,注意看按钮效果");
}
/// <summary>
/// 5、RelayCommand 带参数使用.
/// </summary>
[RelayCommand]
private async Task Answer(string name)
{
await Task.Delay(100);
Growl.Info(name+ " "+ UserName);
}
RelayCommand 让写事件更加简单,只需要增加这个特性即可。
启用和禁用命令
通常,能够禁用命令,然后在以后使其状态失效,并让它们再次检查是否可以执行它们。 为了支持此功能,该 RelayCommand 属性公开 CanExecute 属性,该属性可用于指示用于评估是否可以执行命令的目标属性或方法。
这样, CanGreetUser 在按钮首次绑定到 UI 时调用(例如,绑定到按钮),然后每次 IRelayCommand.NotifyCanExecuteChanged 在命令上调用该按钮时都会再次调用它
/// <summary>
/// 6、RelayCommand 启用禁用按钮.
/// </summary>
[RelayCommand(CanExecute = nameof(IsEnabled))]
private void HelloWord()
{
Growl.Info("Hello, World!");
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(HelloWordCommand))]
private bool isEnabled;
Messenger
IMessenger 接口是可用于在不同对象之间交换消息的类型协定。 这可用于分离应用程序的不同模块,而无需保留对所引用类型的强引用。 还可以将消息发送到特定通道,由令牌唯一标识,并在应用程序的不同部分中具有不同的信使。 MVVM 工具包提供两种现用的实现:WeakReferenceMessenger 和 StrongReferenceMessenger:前者在内部使用弱引用,为收件人提供自动内存管理,而后者使用强引用,并要求开发人员在不再需要收件人时手动取消订阅收件人(有关如何注销消息处理程序的更多详细信息,可在下面找到),但这一点换来的是提供更好的性能,而且内存使用量要少得多。
WeakReferenceMessenger 和 StrongReferenceMessenger 还公开一个 Default 属性,该属性提供内置于包中的线程安全实现。
Ioc (控制) 的反转
服务注册
方式一
首先安装 Microsoft.Extensions.DependencyInjection包
第一步是声明实例 IServiceProvider ,并在启动时初始化所有必要的服务
public sealed partial class App : Application
{
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
/// <summary>
/// Gets the current <see cref="App"/> instance in use
/// </summary>
public new static App Current => (App)Application.Current;
/// <summary>
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
/// </summary>
public IServiceProvider Services { get; }
/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IFilesService, FilesService>();
services.AddSingleton<ISettingsService, SettingsService>();
services.AddSingleton<IClipboardService, ClipboardService>();
services.AddSingleton<IShareService, ShareService>();
services.AddSingleton<IEmailService, EmailService>();
return services.BuildServiceProvider();
}
}
在此,该 Services 属性在启动时初始化,所有应用程序服务和 viewmodel 都注册。 还有一个新 Current 属性可用于从应用程序中的其他视图轻松访问 Services 该属性。
IFilesService filesService = App.Current.Services.GetService<IFilesService>();
方式二
直接在App 构造函数注入到默认容器中
public App()
{
// IOC方式二,注入到默认容器中.
Ioc.Default.ConfigureServices(
new ServiceCollection()
.AddTransient<RelayCommandDemoCtlViewModel>()
.BuildServiceProvider());
this.InitializeComponent();
}
通过Ioc.Default.GetRequiredService 使用
DataContext = Ioc.Default.GetRequiredService<RelayCommandDemoCtlViewModel>();
构造函数注入
public class FileLogger : IFileLogger
{
private readonly IFilesService FileService;
private readonly IConsoleService ConsoleService;
public FileLogger(
IFilesService fileService,
IConsoleService consoleService)
{
FileService = fileService;
ConsoleService = consoleService;
}
// Methods for the IFileLogger interface here...
}
viewmodels 使用
/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
// Services
services.AddSingleton<IContactsService, ContactsService>();
services.AddSingleton<IPhoneService, PhoneService>();
// Viewmodels
services.AddTransient<ContactsViewModel>();
return services.BuildServiceProvider();
}
public ContactsView()
{
this.InitializeComponent();
this.DataContext = App.Current.Services.GetService<ContactsViewModel>(); // 这里获取Viewmodel实列
}