WPF MVVM框架
一、MVVM简介
MVC Model View Control
MVP
MVVM即Model-View-ViewModel,MVVM模式与MVP(Model-View-Presenter)模式相似,主要目的是分离视图(View)和模型(Model),具有低耦合、可重用性、独立开发、可测试性等优点。
MVVM框架有很多,开源的主要有:
-
MVVM Light Toolkit:有visual Studio和Expression Blend的项目和项的模板。更多信息请看这里,另外可以参考VS和Expression Blend的使用教程。
-
Caliburn Micro:支持视图模型先行(ViewModel-First)和视图先行(View-First)两种开发方式,通过co-routine支持异步编程。
-
Simple MVVM Toolkit:提供VS项目和项的模板,依赖注入,支持深拷贝以及模型和视图模型之间的属性关联。
-
Catel:包含项目和项的模板,用户控件和企业类库。支持动态视图模型注入,视图模型的延迟加载和验证。还支持WP7专用的视图模型服务。
闭源框架主要有:
-
Intersoft ClientUI:付费的,只支持WPF和Silverlight,但是,除了MVVM框架,它还提供其它一些特性。
-
Vidyano:免费但不开源。带有实体映射/虚拟持久化对象(数据容器),业务规则以及内置基于ACL的安全特性。
二、原生版本的MVVM实例
Models 存放的是数据模型
Service存放的是业务逻辑
ViewModels存放的便是视图模型
Views存放WPF窗口
2.1 Models文件夹中创建一个用户模型User.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wpfmvvm_demo.Models
{
public class User
{
/// <summary>
/// 用户名
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 密码
/// </summary>
public string? Password { get; set; }
}
}
2.2 在Services文件夹中添加用户的业务逻辑UserService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wpfmvvm_demo.Models;
namespace Wpfmvvm_demo.Services
{
public class UserService
{
/// <summary>
/// 获取所有用户方法
/// </summary>
/// <returns></returns>
public List<User> GetAllUser()
{
List<User> users = new ();
for (int i = 0; i < 3; i++)
{
var user = new User();
user.Name = "用户" + i;
user.Password = "密码" + i;
users.Add(user);
}
return users;
}
}
}
2.3 在ViewModels中创建NotificationObject.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wpfmvvm_demo.ViewModels
{
public class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
2.4 在ViewModels文件中创建DelegateCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Wpfmvvm_demo.ViewModels
{
public class DelegateCommand : ICommand
{
public event EventHandler? CanExecuteChanged;
public Func<object?,bool>? DoCanExecute { get; set; }
public Action<object?>? DoExecute { get; set; }
public bool CanExecute(object? parameter)
{
if (DoCanExecute != null)
{
return DoCanExecute(parameter);
}
return true;
}
public void Execute(object? parameter)
{
if (DoExecute != null)
{
DoExecute.Invoke(parameter);
}
}
}
}
2.5 ViewModels中创建主窗口的视图模型MainWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wpfmvvm_demo.Models;
using Wpfmvvm_demo.Services;
namespace Wpfmvvm_demo.ViewModels
{
public class MainWindowViewModel : NotificationObject
{
/// <summary>
/// 用户List
/// </summary>
private List<User>? users;
public List<User>? Users
{
get { return users; }
set
{
users = value;
RaisePropertyChanged("Users");
}
}
/// <summary>
/// 程序名
/// </summary>
public string AppName { get; set; }
/// <summary>
/// 电话
/// </summary>
private string? phone;
public string? Phone
{
get { return phone; }
set
{
phone = value;
RaisePropertyChanged("Phone");
}
}
/// <summary>
/// 获取所有用户命令
/// </summary>
public DelegateCommand? GetAllUsersCommand { get; set; }
/// <summary>
/// 构造初始化
/// </summary>
public MainWindowViewModel()
{
AppName = "WPF MVVM 模式测试";
Phone = "123456";
GetAllUsersCommand = new DelegateCommand
{
DoExecute = new Action<object?>(GetAllUsersCommandExecute)
};
}
/// <summary>
/// 获取所有用户命令执行方法
/// </summary>
private void GetAllUsersCommandExecute(object? paramater)
{
Phone = Phone!.Equals("123456") ? "1234567" : "123456";
UserService userService = new UserService();
Users = userService.GetAllUser();
}
}
}
2.6 设计MainWindow.xaml界面
<Window x:Class="Wpfmvvm_demo.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:Wpfmvvm_demo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="550">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="程序名"></Label>
<Label Content="{Binding AppName}"></Label>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="电话"></Label>
<Label Content="{Binding Phone}"></Label>
</StackPanel>
<StackPanel>
<Button Content="获取所有用户" Command="{Binding GetAllUsersCommand}"></Button>
</StackPanel>
<DataGrid ItemsSource="{Binding Users}" AutoGenerateColumns="False" GridLinesVisibility="All" CanUserDeleteRows="False" CanUserAddRows="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="用户名" Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Header="密码" Binding="{Binding Password}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
</Window>
2.7 把ViewModel数据绑定到窗口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Wpfmvvm_demo.ViewModels;
namespace Wpfmvvm_demo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
}
三、MVVM Toolkit
微软虽然提出了 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',根正苗红。
这个包主要提供了如下的
Microsoft.Toolkit.Mvvm.ComponentModel
Microsoft.Toolkit.Mvvm.DependencyInjection
Microsoft.Toolkit.Mvvm.Input
Microsoft.Toolkit.Mvvm.Messaging
Microsoft.Toolkit.Mvvm.Messaging.Messages
3.1 可观察对象
public class UserVM : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
private int age;
public int Age
{
get => age;
set => SetProperty(ref age, value);
}
}
页面的类中添加
public partial class MainWindow : Window
{
private UserVM userVM = new UserVM();
public MainWindow()
{
InitializeComponent();
DataContext = userVM;
Task.Run(() =>
{
while (true)
{
userVM.Age += 1;
Thread.Sleep(1000);
}
});
}
}
页面中
<Grid>
<StackPanel>
<TextBlock FontSize="30" Text="{Binding Age}" />
</StackPanel>
</Grid>
我们就可以看到数字就会一直递增了。
3.2 命令
页面中我们添加命令
<StackPanel Height="40" Orientation="Horizontal">
<TextBlock FontSize="30" Text="{Binding Name}" />
<TextBlock FontSize="30" Text="{Binding Age}" />
<Button Command="{Binding IncrementAgeCommand}" Content="年龄递增" />
</StackPanel>
对应的ViewModel中添加命令及响应的事件
public class UserVM : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
private int age;
public int Age
{
get => age;
set => SetProperty(ref age, value);
}
public ICommand IncrementAgeCommand { get; }
public UserVM()
{
IncrementAgeCommand = new RelayCommand(IncrementAge);
}
private void IncrementAge() => Age++;
}
这样只要我们点击按钮,年龄就会递增1。
3.3 消息机制
3.3.1 注册与发送
添加传递消息的类
public class ZMessage
{
public int Code { get; set; }
public string Message { get; set; }
public ZMessage(int code, string msg)
{
Code = code;
Message = msg;
}
}
消息接收与发送
public MainWindow()
{
InitializeComponent();
initMessage();
}
private void initMessage()
{
WeakReferenceMessenger.Default.Register<ZMessage>(
this,
(r, m) =>
{
Console.WriteLine("接收到信息:" + m.Message);
}
);
WeakReferenceMessenger.Default.Send(new ZMessage(100, "Hello"));
}
3.3.2 可接收消息的类
当然我们也可以让我们的ViewModel接收消息
public class UserVM : ObservableRecipient, IRecipient<ZMessage>
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
private int age;
public int Age
{
get => age;
set => SetProperty(ref age, value);
}
public UserVM()
{
IsActive = true;
}
public void Receive(ZMessage message)
{
Name = message.Message;
Age = message.Code;
}
}
这里一定要注意
-
要继承
ObservableRecipient
类,实现IRecipient<>
接口。 -
只有设置
IsActive = true;
,才能接收消息。
我们还是在页面的类中发送消息
WeakReferenceMessenger.Default.Send(new ZMessage(18, "XiaoMing"));
页面也稍做修改
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="30" Text="{Binding Name}" />
<TextBlock FontSize="30" Text="{Binding Age}" />
</StackPanel>
我们会发现页面上已经变更为我们发送消息的数据了。
3.4 控制反转(IOC)
添加依赖库Microsoft.Extensions.DependencyInjection
Install-Package Microsoft.Extensions.DependencyInjection -Version 6.0.0
创建我们要自动注入的类
加入如下是我们用户相关的服务
public interface IUserService
{
string getUserName();
}
和
public class UserService : IUserService
{
public string getUserName()
{
return "XiaoMing";
}
}
添加注入的控制
public partial class App : Application
{
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
public new static App Current => (App)Application.Current;
public IServiceProvider Services { get; }
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<IUserService, UserService>();
// ViewModels
services.AddTransient<UserVM>();
return services.BuildServiceProvider();
}
}
其中
-
AddSingleton 单一实例服务
-
AddScoped 范围内的服务
-
AddTransient 暂时性服务
权重:
AddSingleton`→`AddTransient`→`AddScoped
生命周期:
-
AddSingleton 项目启动-项目关闭 相当于静态类 只会有一个 每一次获取的对象都是同一个
-
AddScoped 请求开始-请求结束 在这次请求中获取的对象都是同一个 请求时创建
-
AddTransient 请求获取-(GC回收-主动释放) 获取时创建 每一次获取的对象都不是同一个
注意:
由于AddScoped对象是在请求的时候创建的 所以不能在AddSingleton对象中使用 甚至也不能在AddTransient对象中使用
使用
private UserVM userVM = (UserVM)App.Current.Services.GetService(typeof(UserVM));
private IUserService userService = (IUserService)App.Current.Services.GetService(typeof(IUserService));
这样是不是感觉还麻烦了
但是如果我们的ViewModel是这样的
public class UserVM : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
private int age;
public int Age
{
get => age;
set => SetProperty(ref age, value);
}
public ICommand IncrementAgeCommand { get; }
public IUserService userService { get; }
public UserVM(IUserService _userService)
{
userService = _userService;
IncrementAgeCommand = new RelayCommand(IncrementAge);
}
private void IncrementAge() => Age++;
}
这里的IUserService
的实例并没有传入但是就可以用了,因为IOC框架已经自动注入了。
四、WPF Prism
4.1 常见的MVVM框架
众所周知, 如果你了解WPF当中的ICommand, INotifyPropertyChanged的作用, 就会发现众多框架都是基于这些进行扩展, 实现其通知、绑定、命令等功能。
对于不同的MVVM框架而言, 大体使用上会在声明方式上的差异, 以及特定功能上的差别。
下面列举了常用的3个MVVM框架,他们的一些差异。如下所示:
功能↓ / →框架名 | Prism | Mvvmlight | Micorosoft.Toolkit.Mvvm |
---|---|---|---|
通知 | BindableBase | ViewModelBase | ObservableObject |
命令 | DelegateCommand | RelayCommand | Async/RelayCommand |
聚合器 | IEventAggregator | IMessenger | IMessenger |
模块化 | √ | × | × |
容器 | √ | × | × |
依赖注入 | √ | × | × |
导航 | √ | × | × |
对话 | √ | × | × |
正如你所见, 各个框架之间都有各自的通知、绑定、事件聚合器等基础的功能, 而Prsim自带的依赖注入、容器、以及导航会话等功能, 可以为你提供更加强大的功能。
当然,在实际的开发过程当中, 可以根据实际的功能需求, 对不同的框架选型, 同时这也需要你对各个框架之间的优缺点进行判断。
那么, 下面将主要介绍Prism
4.2 BindableBase
在Prism当中, 你需要继承于BindableBase
public class TestViewModel : BindableBase
{
private string _message;
public string Message
{
get { return _message; }
set { _message = value; RaisePropertyChanged(); }
}
}
在Prism当中, 你可以使用DelegateCommand及带参数的Command
public class TestViewModel : ViewModelBase
{
public DelegateCommand SendCommand { get; set; }
public DelegateCommand<string> SendMessageCommand { get; set; }
}
4.3 CompositeCommand
对于单个Command而言, 只是触发单个对应的功能, 而复合命令是Prism当中非常强大的功能, CompositeCommand简单来说是一个父命令, 它可以注册N个子命令
当父命令被激活, 它将触发对所有的子命令, 如果任意一个命令CanExecute=false,它将无法被激活,如下所示:
public class MyViewModel : NotificationObject
{
private readonly CompositeCommand saveAllCommand;
public ArticleViewModel(INewsFeedService newsFeedService,
IRegionManager regionManager,
IEventAggregator eventAggregator)
{
this.saveAllCommand = new CompositeCommand();
this.saveAllCommand.RegisterCommand(new SaveProductsCommand());
this.saveAllCommand.RegisterCommand(new SaveOrdersCommand());
}
public ICommand SaveAllCommand
{
get { return this.saveAllCommand; }
}
}
4.4 IEventAggregator
松耦合基于事件通讯
多个发布者和订阅者
微弱的事件
过滤事件
传递参数
取消订阅
该功能主要作用为, 事件聚合器负责接收订阅以及发布消息。订阅者可以接收到发布者发送的内容。
//创建事件
public class SavedEvent : PubSubEvent<string> { }
//发布
IEventAggregator.GetEvent<SavedEvent>().Publish("some value");
//订阅
IEventAggregator.GetEvent<SavedEvent>().Subscribe(.Subscribe(message=>
{
//do something
});
4.5 Filtering Events
在实际的开发过程当中,我们往往会在多个位置订阅一个事件, 但是对于订阅者而言, 他并不需要接收任何消息, 如下所示:
在Prism当中, 我们可以指定为事件指定过滤条件, 如下所示:
eventAggregator.GetEvent<MessageSentEvent>()
.Subscribe(arg =>
{
//do something
},
ThreadOption.PublisherThread,
false,
//设置条件为token等于“MessageListViewModel” 则接收消息
message => message.token.Equals(nameof(MessageListViewModel)));
关于Subscribe当中的4个参数, 详解:
-
1.action: 发布事件时执行的委托。
-
2.ThreadOption枚举: 指定在哪个线程上接收委托回调。
-
3.keepSubscriberReferenceAlive: 如果为true,则Prism.Events.PubSubEvent保留对订阅者的引用因此它不会收集垃圾。
-
4.filter: 进行筛选以评估订阅者是否应接收事件。
4.6 Unsubscribe
为注册的消息取消订阅, Prism提供二种方式取消订阅,如下:
1.通过委托的方式取消订阅
var event = IEventAggregator.GetEvent<MessageSentEvent>();
event.Subscribe(OnMessageReceived);
event.Unsubscribe(OnMessageReceived);
2.通过获取订阅者token取消订阅
var _event = eventAggregator.GetEvent<MessageSentEvent>();
var token = _event.Subscribe(OnMessageReceived);
_event.Unsubscribe(token);
原文地址:https://blog.csdn.net/hccee/article/details/143799496
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!