WPF MvvmToolkit入门
最新.net6 wpf MVVMToolkit 8.0 工程搭建。
MVVMToolkit是一个轻量级MVVM框架,在框架下我们第一个要做的就是搞清在此框架下的一些常规操作:属性绑定和通知,命令绑定,消息传递。搞懂这些处理流程,然后就可以写自己业务的逻辑。
1.安装mvvmtoolkit
1.1Nuget下载CommunityToolkit.Mvvm
1.2调整目录结构
添加3个文件夹:Views Models ViewModels。
Views下放界面的xmal文件
Models下放数据模型
ViewModels下放逻辑代码
并把MainWindow.xaml移动到Views文件夹下,需要修改3个地方:
App.xaml.cs:
Mainwindow.xaml:
MainWindow.xaml.cs
创建一个类处理逻辑,命名MainViewModel,放在ViewModels下
创建一个类描述数据模型,命名CalculateModel,放在Models下
UI界面设计:
CalculateModel类:
界面上的textbox分别绑定CalculateModel中的a b c
public class CalculateModel { //计算a+b=c public int a { get; set; } public int b { get; set; } public int c { get; set; } } }
布局代码:
<Grid> <StackPanel > <StackPanel Orientation="Horizontal"> <TextBlock Text=" a" Width="50"/> <TextBox Width="100" Text="{Binding calculateModel.a}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text=" b" Width="50"/> <TextBox Width="100" Text="{Binding calculateModel.b}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text=" c" Width="50"/> <TextBox Width="100" Text="{Binding calculateModel.c}"/> </StackPanel> <Button Content="计算" Height="30" Width="50" HorizontalAlignment="Left" /> </StackPanel> </Grid>
MainViewModel代码:
public class MainViewModel {
//在MianViewModel中写处理逻辑,这个类 类似于在不分mvvm模式下的后台的代码 public CalculateModel? calculateModel { get; set; } public MainViewModel() { calculateModel = new CalculateModel(); calculateModel.a = 1; calculateModel.b = 1; calculateModel.c = calculateModel.a + calculateModel.b; } }
在MainWindow.xaml.cs中添加DataContext
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MainViewModel(); } }
运行结果:可以看到能绑定成功了,然后就是介绍 属性绑定通知,命令绑定,消息传递的功能了。
需要注意的:
1.一个View对应一个ViewModel,usercontrol也算一种view
2.新建的MainViewModel是一个独立的类,和界面没有关联,为了能够让UI界面找到MainViewModel类中的属性,则需要设置DataContext
this.DataContext = new MainViewModel();这里的this就是指MainWindow。
2.属性的绑定和通知
属性想要实现通知功能,属性所在的类需要继承ObservableObject,并在属性的set访问器中添加SetProperty
把上面的CalculateModel修改为可通知的:
public class CalculateModel: ObservableObject { //计算a+b=c private int _a; private int _b; private int _c; public int a { get { return _a; } set { SetProperty(ref _a, value); } } public int b { get { return _b; } set { SetProperty(ref _b, value); } } public int c { get { return _c; } set { SetProperty(ref _c, value); } } }
通过调用add方法,修改a b c的值,界面也会自动通知
public void add() { calculateModel.a = 11; calculateModel.b = 11; calculateModel.c = calculateModel.a + calculateModel.b; }
默认:
调用add方法后:
可以看到实现了数据通知的功能,命令的使用方法在后面。
几个需要注意的地方:
1.可通知的属性必须是属性,不能是字段,由get set访问器的是属性,并且访问权限是public。
//定义属性 public int MyProperty { get; set; } //定义字段 public int MyProperty1;
2.被绑定的属性在哪个类中,该类就得继承ObservableObject
这个例子中a b c这3个属性在CalculateModel类中,所以是CalculateModel类继承ObservableObject
如果a b c这3个属性直接定义在MainViewModel中,则MainViewModel类需要继承ObservableObject
一个datacontext的问题:
目前这个例子的逻辑是这样:
假如现在MainViewModel中也有abc3个属性了,UI界面想绑定MainViewModel中的abc,而不是去绑定CalculateModel中的abc,则需要这样绑定
<TextBox Width="100" Text="{Binding b}"/>
3.命令的绑定
不使用mvvm模式时,都是直接处理各种事件,Click DoubleClick等等,在使用mvvm模式下,使用Command代替事件处理。
第2部分介绍了属性的绑定。命令的绑定也和属性的绑定一样简单,把命令当成属性来处理就行了,属性和命令都属于某个类。结合属性的绑定,命令也需要是public的,必须带get set访问器。
看一个简单的例子:
<Button Content="计算" Command="{Binding addCommand}" Height="30" Width="50" HorizontalAlignment="Left" />
当点击Button时执行 Command,Command绑定了一个addCommand(这个是我们自己写的),
定义命令:
public class MainViewModel { public CalculateModel? calculateModel { get; set; } public MainViewModel() { calculateModel = new CalculateModel(); calculateModel.a = 1; calculateModel.b = 1; calculateModel.c = calculateModel.a + calculateModel.b; //第三步:给命令赋值一个方法,当命令被触发时会自动调用赋值的方法 addCommand = new RelayCommand(add); }
//第一步:定义一个命令 public RelayCommand addCommand { get; set; }
//顶二步:定义一个普通的方法 public void add() { calculateModel.a = 11; calculateModel.b = 11; calculateModel.c = calculateModel.a + calculateModel.b; } }
总结就3点:定义一个方法,定义一个命令,把方法和命令关联起来。
当点击button时会调用add方法。
疑惑的点:命令要定义在哪里?
解释:可以定义在Model中,也可以定义在ViewModel中,都可以。
4.MvvmToolkit框架下任意命令的绑定
只有Button由Command,其他控件没有怎么处理?
参考这篇文章,多种方法
WPF 任意事件绑定 - 薛定谔的小灯泡 - 博客园 (cnblogs.com)
5.消息传递
在mvvm模式下,项目的组成可能是这样的:
一般是这样:
一个View对应一个ViewModel,操作View的数据的逻辑,写在对应的ViewModle中,操作View界面的和业务无关的逻辑写在View.xaml.cs中
ViewModel中定义Model,然后处理
View和ViewModel之间可以互相传递消息。在mvvmtoolkit框架下,提供了传递消息的机制
5.1 WeakReferenceMessenger基本理解
既然是传递消息,就会有2方:收消息的一方和发消息的一方
收消息:WeakReferenceMessenger.Default.Register
发消息:WeakReferenceMessenger.Default.Send
a给b发送消息,b必须提前注册好这个消息,也就是说b是提前知道a会给它发消息的。
假如上面的例子实现这样一个功能,
点击“计算”按钮后,计算出a+b的结果,并使用MessageBox弹出这个结果。
其中的逻辑:“计算”按钮的点击绑定了ViewModel中的命令,在ViewModel中计算结果,弹窗需要在View.xmal.cs中处理,所以在ViewModel中给View.xaml.cs发送结果,在View.xmal.cs中接收到消息后,弹出结果
ViewModel代码:
发消息:
MainWindow.xaml.cs代码:
收消息:
运行结果:
这里传递的消息类型必须是引用,int double之类的非引用类型无法传递
发消息和收消息的2方靠什么识别是否接收呢?
如果没有写Token,则以消息的类型判断,双方消息的类型一致则能成功发送消息
如果写了Token,则判断Token和消息类型
Token的写法:
6.MvvmToolkit框架下IOC的处理
mvvmtoolkit框架没有提供IOC容器,需要额外处理,这里使用微软的 SeverCollection (ServiceProvider)仿照mvvmlight的Locator写一个,引用Nuget包microsoft.extensions.dependencyinjection
第1步:添加microsoft.extensions.dependencyinjection,在ViewModels文件夹下创建一个Locator类
Locator代码:
public class Locator { public static IServiceProvider? ServiceProvide { get; set; } public Locator() { ServiceProvide = GetService(); } private IServiceProvider GetService() { var service = new ServiceCollection(); service.AddSingleton<MainViewModel>(); return service.BuildServiceProvider(); } public MainViewModel MainViewModel { get { return ServiceProvide.GetService<MainViewModel>(); } } }
第2步:修改dataContext
第3步:修改UI界面绑定时的层级关系
修改前:
修改后: