1. 简介
1.1 目前处于WPF和MvvmLight学习阶段,如有错误,请批评指正
1.2 关于MvvmLight
MvvmLight是一个轻量级框架,使用Model-View-ViewModel模式,分离前后端,使开发更加的高效。
2. 创建WPF项目
框架选择.Net5.0
3. MvvmLight包安装
3.1 管理NuGet程序包,搜索mvvmlight,选择如图所示的包安装
4. 框架搭建
4.1 创建View、Model、ViewModel 3个文件夹
4.2 移动MainWindow.xaml
4.2.1 将MainWindow.xaml移动到View文件夹下,并修改其命名空间
4.2.2 修改MainWindow.xaml第一行为
<Window x:Class="WpfMvvmLightApp.View.MainWindow"
4.2.3 修改MainWindow.xaml.cs的命名空间为WpfMvvmLightApp.View
4.2.4 修改App.xaml的StartupUrl为"View/MainWindow.xaml"
4.3 创建ViewModelLocator类
4.3.1 在ViewModel文件夹下创建ViewModelLocator类
4.3.3 App.xaml添加资源
<Application x:Class="WpfMvvmLightApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMvvmLightApp"
StartupUri="View/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator xmlns:vm="clr-namespace:WpfMvvmLightApp.ViewModel" x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
</Application>
4.4 创建MainViewModel
4.4.1 在ViewModel文件夹下创建MainViewModel类,继承自ViewModelBase
4.5 在ViewModelLocator注册关联MainViewModel
using GalaSoft.MvvmLight.Ioc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfMvvmLightApp.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// 主页viewmodel
/// </summary>
public MainViewModel MainVM
{
get
{
return SimpleIoc.Default.GetInstance<MainViewModel>();
}
}
}
}
4.6 在MainWindow.xaml关联MainVM
4.6.1 在Windows标签添加属性DataContext
<Window x:Class="WpfMvvmLightApp.View.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:WpfMvvmLightApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
DataContext="{Binding Source={StaticResource Locator}, Path=MainVM}">
<Grid>
</Grid>
</Window>
5. 编写界面和数据逻辑
5.1 创建一个Model
5.1.1 在Model文件夹创建一个MainModel类,用于管理数据和操作,需要继承ObservableObject类,并创建一些属性和方法
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfMvvmLightApp.Model
{
public class MainModel : ObservableObject
{
private string msg = "hello";
public string Msg
{
get { return msg; }
set
{
msg = value;
//数据改变通知界面刷新,不加则界面不会改变,注意属性是Msg而不是msg,msg会导致递归死循环
RaisePropertyChanged(() => Msg);
}
}
private int val;
public int Val
{
get { return val; }
set
{
val = value;
RaisePropertyChanged(() => Val);
}
}
public RelayCommand AddCommand { get; set; }
public RelayCommand<string> ShowInfoCommand { get; set; }
public MainModel()
{
AddCommand = new RelayCommand(() => Add());
ShowInfoCommand = new RelayCommand<string>(info => ShowInfo(info));
}
private void Add()
{
Val++;
}
private void ShowInfo(string info)
{
MessageBox.Show(info, "ShowInfo被触发");
}
}
}
5.2 直接在ViewModel关联数据和操作
5.2.1 这个和在Model中分开写类似,就是将Model中的内容都写到ViewModel中,可以自行感受下区别。
5.2.2 如果是在View.xaml.cs中用WPF的方式进行binding,那么在使用string等非引用类型的时候不能实现关联,需要用类进行包装。
5.3 在ViewModel中加入Model对象
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfMvvmLightApp.Model;
namespace WpfMvvmLightApp.ViewModel
{
public class MainViewModel : ViewModelBase
{
private MainModel model;
public MainModel Model
{
get { return model; }
set { model = value; }
}
public MainViewModel()
{
model = new MainModel();
}
}
}
5.4 在View中关联Model对象数据
5.4.1 xaml代码
<Window x:Class="WpfMvvmLightApp.View.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:WpfMvvmLightApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
DataContext="{Binding Source={StaticResource Locator}, Path=MainVM}">
<Grid>
<!--定义列和列宽-->
<Grid.ColumnDefinitions>
<!--带*的表示比例-->
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!--定义行和行高-->
<Grid.RowDefinitions>
<!--带px或者只有数字的表示像素-->
<RowDefinition Height="80"/>
<RowDefinition Height="80"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<!--在第0行第0列创建textbox,字体大小50,内容垂直居中,关联到Model的Msg属性-->
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Model.Msg}" FontSize="50" VerticalContentAlignment="Center"/>
<!--创建Button,关联到Model的ShowInfoCommand方法,添加参数 "你好,世界!"-->
<Button Grid.Row="0" Grid.Column="1" Content="ShowInfo" Command="{Binding Model.ShowInfoCommand}" CommandParameter="你好,世界!"/>
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Model.Val}" FontSize="50" VerticalContentAlignment="Center"/>
<!--创建Button,关联到Model的ClickCommand方法-->
<Button Grid.Row="1" Grid.Column="1" Content="增加" Command="{Binding Model.AddCommand}"/>
</Grid>
</Window>
5.4.2 运行
点击ShowInfo按钮
点击增加按钮,左侧数值从0变成1
6. binding
6.1 binding翻译为绑定,容易造成误解,其实本意是关联
MvvmLight中使用binding更加的灵活,不像WPF需要在view.xaml.cs中的初始化方法中写界面和数据关联,MvvmLight直接可以在View.xaml中关联ViewModel中的model,因此也不一定要在view.xaml中指定控件的x:Name。
6.2 一些简单binding,比如5.4.1
Text="{Binding Model.Msg}"
Text="{Binding Model.Val}"
Command="{Binding Model.AddCommand}"
Command="{Binding Model.ShowInfoCommand}" CommandParameter="你好,世界!"
6.3 静态资源binding,如7.1.1
ItemsSource="{Binding Source={StaticResource MainTypeEnumKey}}"
7. resource
7.1 在view.xaml创建枚举类型的资源,ComboBox ItemSource关联此资源,DataGrid中的DataGridComboBoxColumn同理
7.1.1 代码
namespace WpfMvvmLightApp.Model
{
public enum MainType
{
A,B,C
}
}
<!--加到MainWindow的Window属性-->
xmlns:model="clr-namespace:WpfMvvmLightApp.Model"
xmlns:core="clr-namespace:System;assembly=mscorlib"
<!--加到MainWindow的Window Content-->
<Window.Resources>
<ResourceDictionary>
<!--MainType列表-->
<ObjectDataProvider x:Key="MainTypeEnumKey" MethodName="GetValues" ObjectType="{x:Type core:Enum}">
<ObjectDataProvider.MethodParameters>
<!--枚举类所在的命名空间和类名-->
<x:Type Type="model:MainType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>
</Window.Resources>
<!--关联到ComboBox ItemSource-->
<ComboBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding Source={StaticResource MainTypeEnumKey}}"/>
7.1.2 运行效果
8. 关联的ViewMode-View之间的消息传递
8.1 ViewMode->View
8.1.1 使用DataContext
MainViewModel vm = (MainViewModel)DataContext;
8.2 View->ViewMode
8.2.1 直接通过属性通知
public string Msg
{
get { return msg; }
set
{
msg = value;
//数据改变通知界面刷新,不加则界面不会改变,注意属性是Msg而不是msg,msg会导致递归死循环
RaisePropertyChanged(() => Msg);
}
}
9. 非关联的ViewMode-View或ViewMode-ViewMode或View-View之间的消息传递
9.1 使用Messenger,效率略低于关联,因此在关联的情况下不使用Messenger
9.1.1 在接收端初始化中注册方法
Messenger.Default.Register<string>(this, "AddValue", msg =>Model.Add());
9.1.2 在发送端调用
Messenger.Default.Send("", "AddValue");
9.1.3 注意,此处"AddValue"的作用相当于一个key,为了理解方便写了魔法值,建议用一个MessengerToken类去管理这些魔法值,增加程序可读性,同时方便查找引用,例
public class MessengerToken
{
/// <summary>
/// 增加值
/// </summary>
public const string AddValue = "AddValue";
}
Messenger.Default.Register<string>(this, MessengerToken.AddValue, msg =>Model.Add());
Messenger.Default.Send("", MessengerToken.AddValue);