# WPF-MVVMLight-Study **Repository Path**: momj/wpf-mvvmlight-study ## Basic Information - **Project Name**: WPF-MVVMLight-Study - **Description**: MVVMLight学习 - **Primary Language**: C# - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 6 - **Forks**: 2 - **Created**: 2022-07-20 - **Last Updated**: 2025-04-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MvvmLightDemo ## 1.MVVM概述 MVVM是Model-View-ViewModel的简写,主要目的是为了解耦视图(View)和模型(Model)。 ![img](images/1463878-20181122153357654-1043270966.png) MVVMLight是一个实现MVVM模式的轻量级框架(相对于Prism) 引用NuGet包: ![image-20220720213927149](images/image-20220720213927149.png) ![image-20220720214010713](images/image-20220720214010713.png) 安装成功后,会在我们新建的Wpf工程中自动生成ViewModel文件夹,里面包含**MainViewModel.cs**和**ViewModelLocator.cs**两个文件。 至此,一个基于MVVMLight框架的WPF项目基本搭建完成。 ## 2.MVVMLight 初探 #### 分层概述 MVVM中,各个部分的职责如下: **Model:**负责数据实体的结构处理,与ViewModel进行交互; **View:**负责界面显示,与ViewModel进行数据和命令的交互; **ViewModel:**负责前端视图业务级别的逻辑结构组织,并将其反馈给前端。 #### 框架初探 通过NuGet安装MVVM Light 框架后,我们新建的Wpf项目中会自动生成一个ViewModel文件夹,里面有MainViewModel.cs和ViewModelLocator.cs两个文件。 下面我们就首先分析下这两个文件的内容: MainViewModel.cs文件分析: MainViewModel.cs文件中只有一个类MainViewModel,该类是主窗口MainWindow对应的ViewModel,继承自类ViewModelBase ViewModelBase类又继承类ObservableObject,同时实现ICleanup接口 ObservableObject类实现INotifyPropertyChanged接口,用于通知属性的改变 由此分析,我们可以得出以下一般结论: **当我们定义一个自己的ViewModel时,一般让自定义ViewModel继承自ViewModelBase类,这样当ViewModel中属性发生变化的时候,就可以自动通知对应的VIew。** ViewModelLocator.cs文件分析: ViewModelLocator.cs文件中只有一个ViewModelLocator类,类中包括一个构造函数、一个类型为MainViewModel的Main属性、以及一个静态的Cleanup函数。 ```c# public class ViewModelLocator { /// /// Initializes a new instance of the ViewModelLocator class. /// public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); ////if (ViewModelBase.IsInDesignModeStatic) ////{ //// // Create design time view services and models //// SimpleIoc.Default.Register(); ////} ////else ////{ //// // Create run time view services and models //// SimpleIoc.Default.Register(); ////} SimpleIoc.Default.Register(); } public MainViewModel Main { get { return ServiceLocator.Current.GetInstance(); } } public static void Cleanup() { // TODO Clear the ViewModels } } ``` 在构造函数中,创建了一个SimpleIoc类型的单实例,用于注册ViewModel,然后用ServiceLocator对这个SimpleIoc类型的单实例进行包裹,方便统一管理。 观察App.xaml文件,我们会发现ViewModelLocator类被生成资源字典并加入到了全局资源,所以每次App初始化的时候,就会去初始化ViewModelLocator类。 实际上,他是一个很基本的视图模型注入器,在构造器中把使用到的ViewModel统一注册,并生成单一实例。然后使用属性把它暴露出来,每当我们访问属性的时候,就会返回相应的ViewModel实例。 ```xaml ``` 由此分析,我们可以得出以下一般结论: **当我们自定义一个ViewModel的时候,就可以在ViewModelLocator类的构造函数中对ViewModel进行注册,然后在该类中定义一个属性,用于返回我们的自定义ViewModel** ## 3.双向数据绑定 ```xaml ``` 在View中,我们分别让一个TextBox和一个TextBlock绑定WelcomeModel中的WelcomeMsg属性 当我们在TextBox中输入文本的时候,利用双向绑定更新WelcomeModel中的WelcomeMsg属性,WelcomeMsg属性又通过RaisePropertyChanged来激发属性变更的事件,通知UI,如此一来,界面上的TextBlock中的内容就会更新。 **但是**,当我们运行以上程序的时候,修改TextBox里的内容,TextBlock中的内容并没有实时更新,而是需要按键盘上的Tab键或者点击LostFocus按钮,让TextBox失去焦点。 这就引出了一个问题,当目标更新的时候,数据源是否更新以及更新的时机? 为了解决这个问题,我们可以使用Binding的UpdateSourceTrigger和Mode属性 UpdateSourceTrigger属性的作用:当做何种改变的时候通知数据源我们做了改变,默认值是LostFocus,这就解释了为什么我们上述程序需要TextBox失去焦点的时候,TextBlock的内容才会更新。 UpdateSourceTrigger可以取以下值: | **枚举类型** | **说明** | | --------------- | -------------------------------------- | | Default | 默认值(默认LostFocus) | | Explicit | 当应用程序调用 UpdateSource 方法时生效 | | LostFocus | 失去焦点的时候触发 | | PropertyChanged | 属性改变时立即触发 | 为了让TextBlock中的内容随着TextBox的内容实时变化,我们可以将绑定修改成如下格式: ```xaml ``` Mode属性取值如下: ![1463878-20181123130410811-1064690555](images/1463878-20181123130410811-1064690555.png) **通过以上Demo,我们应达成以下共识:** 1)ViewModel => View更新的时候,我们一般在属性的**set块**中加入**RaisePropertyChanged**,它的作用是当数据源改变的时候,会触发PropertyChanged事件,通知UI属性发生了变更; 2)View => ViewModel 更新的时候,我们一般利用**Binding**的**UpdateSourceTrigger**和**Mode**属性。 ## 4.RelayCommand初探 #### 概述 在MVVM Light框架中,主要通过命令绑定来进行事件的处理。 WPF中,命令是通过实现 ICommand 接口创建的。 ICommand 公开了两个方法(Execute 及 CanExecute)和一个事件(CanExecuteChanged)。 在MVVM Light框架中,RelayCommand类实现了ICommand 接口,用于完成命令绑定。 通过RelayCommand类的构造函数传入Action类型的Execute委托和Func类型的CanExecute委托,CanExecute委托用于表示当前命令是否可以执行,Execute委托则表示执行当前命令对应的方法。 通过命令绑定,解耦了View和ViewModel的行为交互,将视图的显示和业务逻辑分开。比如我们对界面上的某个按钮进行命令绑定,当点击按钮的时候,实际上进行操作是在对应的ViewModel下的所绑定的方法中执行的。 核心引用:**using GalaSoft.MvvmLight.CommandWpf;** ![image-20220720224751767](images/image-20220720224751767.png) ## 5.RelayCommand深究 #### (1).概述 有时候,单纯的命令绑定不一定能满足我们的开发需求,比如我们需要在命令绑定的时候传递一个参数,这个时候,我们就需要使用RelayCommand的泛型版本了。 RelayCommand的泛型版本的构造函数以下: public RelayCommand(Action execute, bool keepTargetAlive = false); public RelayCommand(Action execute, Func canExecute, bool keepTargetAlive = false); 构造函数传入的是委托类型的参数,Execute 和 CanExecute执行委托方法。 #### (2)带一个参数的命令绑定 ```xaml ``` ```C# private RelayCommand addUserCommand; public RelayCommand AddUserCommand { get { return addUserCommand ?? new RelayCommand(AddUser,P => IsCanAddUser); } } private void AddUser(object parpm) { UserList = UserList + " " + parpm.ToString(); IsCanAddUser = false; } ``` #### (3)带多个参数的命令绑定 给命令传递多个参数,建议使用一下方式: **使用MultiBinding将多绑定的各个值转换成我们所需的对象或者实例模型,再传递给ViewModel中的命令。** **转换器UserInfoConvert代码** ```C# namespace MvvmLightDemo.Base { public class UserInfoConvert : IMultiValueConverter { /// /// 对象转换 /// /// 所绑定的源的值 /// 目标的类型 /// 绑定时所传递的参数 /// <系统语言等信息 /// public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (!values.Cast().Any(text => string.IsNullOrEmpty(text)) && values.Count() == 4) { UserParam up = new UserParam() { UserName = values[0].ToString(), UserSex = values[1].ToString(), UserPhone = values[2].ToString(), UserAdd = values[3].ToString() }; return up; } return null; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } } ``` View代码: ```xaml xmlns:base="clr-namespace:MvvmLightDemo.Base" ``` ```xaml ``` ```xaml ``` ViewModel代码: ```C# #region 多参数,对象动态传递 private UserParam argsTo; public UserParam ArgsTo { get => argsTo; set => Set(ref argsTo, value); } private ICommand dynamicParamCmd; public ICommand DynamicParamCmd { get { return dynamicParamCmd ?? new RelayCommand(p => { ArgsTo = p; }); } } #endregion ``` 效果如下 ![](images/image-20220721002447006.png) 到这边,命令参数绑定相关的应该就比较清楚了,这种方式也比较好操作。 **个人观点:从MVVM的模式来说,其实命令中的参数传递未必是必要的。MVVM 的目标就是消除View和ViewModel开发人员之间过于频繁的数据交互。** **去维护一段额外的参数代码,还不如把所有的交互参数细化成在当前DataContext下的全局属性。View开发人员和ViewModel开发人员共同维护好这份命令清单和属性清单即可。** **而微软的很多控件也提供了类似 SelectedItem 和 SelectedValue之类的功能属性来辅助开发。** ### (4)传递原事件参数 如果在一些特殊环境里,我们需要传递原事件的参数,那也很简单,只要设置 PassEventArgsToCommand="True" 即可, 在ViewModel中对应接收参数即可。 **相关引用:** NuGet: System.Windows.Interactivity(已过时) 因为GalaSoft.MvvmLight.Command.EventToCommand 继承 Interactivity.TriggerAction 需要额外设置,否则报错 ![image-20220721142602925](images/image-20220721142602925.png) View代码: ```xaml xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:light="http://www.galasoft.ch/mvvmlight" ``` ```xaml ``` ViewModel代码: ```C# #region 传递原事件参数 private string fileAdd; public string FileAdd { get => fileAdd; set => Set(ref fileAdd, value); } private ICommand dropCommand; public ICommand DropCommand { get { return dropCommand ?? new RelayCommand(e => ExecuteDrop(e)); } } private void ExecuteDrop(DragEventArgs e) { FileAdd = ((Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString(); } #endregion ``` ### (5)事件转命令 EventToCommand ​ 在WPF中,并不是所有控件都有Command,例如TextBox,那么当文本改变,我们需要处理一些逻辑,这些逻辑在ViewModel中,没有Command如何绑定呢?这 个时候我们就用到EventToCommand,事件转命令,可以将一些事件例如TextChanged,Checked等转换成命令的方式。接下来我们就以下拉控件为例子,来看看具体的实例: **View代码:**(这边声明了i特性和light特性,一个是为了拥有触发器和行为附加属性的能力,当事件触发时,会去调用相应的命令,EventName代表触发的事件名称;一个是为了使用MVVMLight中 EventToCommand功能。) 这边就是当ListBox执行SelectionChanged事件的时候,会相应去执行ViewModel中 SelectionChangedCommand命令。 Model代码: ```C# public class DataModel { private string img; public string Img { get { return img; } set { img = value; } } private string info; public string Info { get { return info; } set { info = value; } } } ``` View代码: ```xaml xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:light="http://www.galasoft.ch/mvvmlight" ``` ```xaml ``` ViewModel代码: ```c# ``` ## 6.Dispatcher 多线程调度,更新UI ### (1)概述 在应用程序中,线程可以被看做是应用程序的一个较小的执行单位。每个应用程序都至少拥有一个线程,我们称为主线程。当调用和操作主线程的时候,该操作将动作添加到一个队列中,每个操作均按照将它们添加到队列中的顺序连续执行,但是可以通过为这些动作指定优先级来影响执行顺序,而负责管理此队列的对象称之为线程调度程序。 我们知道,WPF程序中,控件是属于UI线程的,如果试图在子线程中访问或者更新UI,就需要在子线程中通知主线程来处理UI, 通过向主线程的Dispatcher队列注册工作项,来通知UI线程更新结果。 Dispatcher提供两个注册工作项的方法:Invoke 和 BeginInvoke。 这两个方法均调度一个委托来执行。Invoke 是同步调用,也就是说,直到 UI 线程实际执行完该委托它才返回。BeginInvoke是异步的,将立即返回。 若子线程直接操作UI控件,则会报错: ![image-20220722174535527](images/image-20220722174535527.png) 代码片段如下: ```C# this.Dispatcher.BeginInvoke((Action)delegate() { 更新UI控件ing; }); ``` #### View => DispatchBasic.xaml 相关代码: ```c# //执行此方法的为子线程; _ = Thread.CurrentThread.ManagedThreadId; Dispatcher.BeginInvoke(new Action(() => { //这里变为主线程; _ = Thread.CurrentThread.ManagedThreadId; processBar.Value = args.process; processInfo.Text = String.Format("创建进度:{0}/100", args.process); if (args.isFinish) { if (args.userInfo != null) { ObservableCollection data = (ObservableCollection)dg.DataContext; data.Add(args.userInfo); dg.DataContext = data; } processPanel.Visibility = Visibility.Hidden; ClearForm(); } })); ``` ### (2)**MVVMLight模式下ViewModel中更新UI** 通常情况下,ViewModel 不从 DispatcherObject 继承,不能访问 Dispatcher 属性。这时候,我们需要使用DispatcherHelper 组件来更新UI。 实际上,该类所做的是将主线程的调度程序保存在静态属性中,并公开一些实用的方法,以便通过统一的方式访问。 为了实现正常功能,需要在主线程上初始化该类。 通常,在 MVVM Light 应用程序中,DispatcherHelper 可以在 App.xaml.cs 或者ViewModel的构造函数中进行初始化,App.xaml.cs 是定义应用程序启动类的文件。 在 WPF 中,该类一般是在 App 构造函数中进行初始化的。 DispatcherHelper组件初始化以后,DispatcherHelper 类的 UIDispatcher 属性包含对主线程的调度程序的引用。 但是一般很少直接使用该属性,虽然确实可以使用。通常我们会使用 CheckBeginInvokeOnUi 方法来更新UI。 在构造函数中初始化: ```C# DispatcherHelper.Initialize(); ``` 使用: ```C# private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args) { DispatcherHelper.CheckBeginInvokeOnUI(() => { if (args.isFinish) { if (args.userInfo != null) { UserList.Add(args.userInfo); } IsEnableForm = true; IsWaitingDisplay = false; } else { ProcessRange = args.process; } }); } ``` ## 7.Messenger MVVM的目标之一就是为了解耦View和ViewModel。View负责视图展示,ViewModel负责业务逻辑处理,尽量保证 View.xaml.cs中的简洁,不包含复杂的业务逻辑代码。 但是在实际情况中是View和ViewModel之间的交互方式还是比较复杂的,View和ViewModel的分离并不是界定的那么清晰。 **比如以下两种场景:** ​ 1、如果需要某张视图页面弹出对话框、弹出子窗体、处理界面元素,播放动画等。如果这些操作都放在ViewModel中,就会导致ViewModel还是要去处理View级别的元素,造成View和ViewModel的依赖。 最好的办法就是ViewModel通知View应该做什么,而View监听接收到命令,并去处理这些界面需要处理的事情。 ​ 2、ViewModel和ViewModel之间也需要通过消息传递来完成一些交互。 ​ 而MVVM Light 的 Messenger类,提供了解决了上述两个问题的能力: Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,另外目标类型可以被指定,用Send(TMessage message) 实现, 在这种情况下信息只能被传递如果接受者类型和目标参数类型匹配,message可以是任何简单或者复杂的对象,你可以用特定的消息类型或者创建你自己的类型继承自他们。 Messager类的主要交互模式就是信息接受和发送(可以理解为“发布消息服务”和“订阅消息服务”),是不是想到观察者模式了,哈哈哈。 MVVM Light Messenger 旨在通过简单的设计模式来精简此场景:任何对象都可以是接收端;任何对象都可以是发送端;任何对象都可以是消息。 如图: img ### **1、View和ViewModel之间的消息交互** 在View和ViewModel中进行消息器注册,相当于订阅服务.包含消息标志,消息参数,消息执行方法. 如下: **消息标志token:ViewAlert**,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致 **执行方法Action:ShowReceiveInfo**,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。 View.xaml.cs 代码如下: ```C# public partial class MessengerForView : Window { public MessengerForView() { InitializeComponent(); this.DataContext = new MessengerRegisterForViewModel(); /*void Register(object recipient, object token, Action action, bool keepTargetAlive = false); recipient - 接收者; token - 令牌;action - 执行的动作; */ //消息标志token:ViewAlert,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致 //执行方法Action:ShowReceiveInfo,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。 Messenger.Default.Register(this, "ViewAlert", ShowReceiveInfo); //卸载当前(this.)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); } /// /// 接收到消息后的后续工作L:根据返回来的消息,弹出消息框 /// /// private void ShowReceiveInfo(string msg) { MessageBox.Show(msg); } } ``` ViewModel代码: ```c# internal class MessengerRegisterForViewModel : ViewModelBase { private ICommand sendCommand; public ICommand SendCommand { get => sendCommand ?? new RelayCommand(o => ExcuteSendCommand(o)); } private void ExcuteSendCommand(object obj) { Messenger.Default.Send("ViewModel通知View弹出消息框", "ViewAlert"); //注意:token参数一致 } } ``` 结果: ![image-20220725145618422](images/image-20220725145618422.png) ### 2.ViewModel 和 ViewModel 之间通信 比如我打开了两个视图,一个视图是用户信息列表,一个视图是用户信息添加页面,如果想要达到添加信息之后,用户信息列表视图实时刷新,用消息通知无疑是一个很棒的体验。 我们来模拟一下: MessengerRegisterView.cs代码: ```c# public MessengerRegisterView() { InitializeComponent(); this.DataContext = new MessengerRegisterViewModel(); //View层不要侵入到 ViewModel层,保持整洁 Messenger.Default.Register(this, "MessengerSenderView", msg => { MessengerSenderView senderView = new MessengerSenderView(); senderView.Show(); }); //卸载当前(this)对象注册的所有MvvmLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); } ``` MessengerRegisterViewModel代码: ```C# public class MessengerRegisterViewModel : ViewModelBase { public MessengerRegisterViewModel() { Messenger.Default.Register(this, "Message", msg => { ReceiveInfo += msg + "\n"; }); } #region 属性 private string receiveInfo; public string ReceiveInfo { get => receiveInfo; set => Set(ref receiveInfo, value); } #endregion #region 命令.启动新窗口 private ICommand shwoSenderWindow; public ICommand ShowSenderWindow { get => shwoSenderWindow ?? new RelayCommand(o => ExcuteShwoSenderWindow(o)); } private void ExcuteShwoSenderWindow(object o) { //向View发送消息,启动新窗口 Messenger.Default.Send("message", "MessengerSenderView"); } #endregion } ``` MessengerSenderViewModel代码: ``` public class MessengerSenderViewModel : ViewModelBase { public MessengerSenderViewModel() { } #region 属性 private string sendInfo; /// /// 发送消息 /// public string SendInfo { get { return sendInfo; } set => Set(ref sendInfo, value); } #endregion #region 命令 private RelayCommand sendCommand; /// /// 发送命令 /// public RelayCommand SendCommand { get { if (sendCommand == null) sendCommand = new RelayCommand(() => ExcuteSendCommand()); return sendCommand; } } private void ExcuteSendCommand() { Messenger.Default.Send(SendInfo, "Message"); } #endregion } ``` 结果如下: ![img](images/167509-20170410155244860-1013593416.png) ## 8.Messenger深究 ### 1.Messenger交互结构和消息类型 衔接上篇,Messeger是信使的意思,顾名思义,他的目是用于View和ViewModel 以及 ViewModel和ViewModel 之间的消息通知和接收。 Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,另外目标类型可以被指定,用Send(TMessage message)实现,在这种情况下信息只能被传递如果接受者类型和目标参数类型匹配, message可以是任何简单或者复杂的对象,你可以用特定的消息类型或者创建你自己的类型继承自他们。 交互结构如下所示: ![](images/167509-20170511140041847-743758543.png) 消息类型如下表所示: | **message消息对象类型** | **说明** | | :--------------------------- | ------------------------------------------------------------ | | MessageBase | 简单的消息类,携带可选的信息关于消息发布者的 | | GenericMessage | 泛型消息 | | NotificationMessage | 用于发送一个string类型通知给接受者 | | NotificationMessage | 和上面一样是一个,且具有泛型功能 | | NotificationMessageAction | 向接受者发送一个通知,允许接受者向发送者回传消息 | | NotificationMessageAction | NotificationMessage的泛型方式 | | DialogMessage | 发送者(通常是View)显示对话,并且传递调用者得回传结果(用于回调),接受者可以选择怎样显示对话框,可以使是标准的MessageBox也可也是自定义弹出窗口 | | PropertyChangedMessage | 用于广播一个属性的改变在发送者里,和PropertyChanged事件有完全箱体内各的目的,但是是一种弱联系方式 | ### 2.注册消息类型 上篇给出了注册的方法,但是注册可以有很多种方式,最常见的就是命名方法调用和Lambda表达式调用的方式: #### 2.1.基本的命名方式注册 ```C# // 使用命名方法进行注册 Messenger.Default.Register(this, HandleMessage); //卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); private void HandleMessage(String msg) { //Todo } ``` #### 2.2.使用Lambda表达式注册 ```C# Messenger.Default.Register(this, message => { // Todo }); //卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); ``` ### 3.跨线程访问 在之前的**DispatchHelper** 在多线程和调度的使用中, 我们有讨论过在异步线程中使用事件来执行和获取相关的执行步骤。但是如果异步线程中的某个方法需要操作主线程(UI线程的时候)的UI是不允许的。 Windows 中的控件被绑定到特定的UI线程(主线程)中,其他线程是不允许访问的,因为不具备线程安全性和规范性。所以后来MVVM Light才有了调度帮助类(DispatchHelper)来处理不用线程中的调度方案。 从这边可以衍生到异步线程下,对UI线程(主线程)的信息发送和接收。所以之前的代码 DispatchHelper 可以改装如下: 注册模块(ViewModel中): ```C# public class MessengerForDispatchViewModel:ViewModelBase { /// /// 构造函数 /// public MessengerForDispatchViewModel() { InitData(); DispatcherHelper.Initialize(); ///Messenger:信使 ///Recipient:收件人 Messenger.Default.Register(this, "UserMessenger", FeedBack); } ... /// /// 创建进度 /// /// public void FeedBack(TopUserInfo top) { if (top.isFinish) { if (top.userInfo != null) { UserList.Add(top.userInfo); } User = new UserParam(); IsEnableForm = true; IsWaitingDisplay = false; } else { ProcessRange = top.process; } } } ``` 发送模块(异步线程中代码): ```C# public void Create() { Thread t = new Thread(Start);//抛出一个新线程 t.Start(); } private void Start() { TopUserInfo ui = new TopUserInfo(); //ToDo:编写创建用户的DataAccess代码 for (Int32 idx = 1; idx <= 9; idx++) { Thread.Sleep(1000); ui = new TopUserInfo() { isFinish = false, process = idx*10, userInfo =null }; DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send(ui, "UserMessenger"); }); } Thread.Sleep(1000); ui = new TopUserInfo() { isFinish = true, process = 100, userInfo = up }; DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send(ui, "UserMessenger"); }); } ``` 结果: ![img](images/167509-20170522213129757-356644662.png) ### 4.释放注册信息 #### 4.1.基于View界面内的UnRegister的释放 (为当前视图页面的UnLoad事件附加释放注册信息的功能): ```C# //卸载当前(this)对象注册的所有MvvmLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); ``` #### 4.2.基于ViewModel类中的UnRegister释放 (用户在关闭使用页面的时候同时调用该方法,释放注册,这个需要开发任意在关闭视图模型的时候发起): ```C# /// /// 手动调用释放注册信息(该视图模型内的所有注册信息全部释放) /// public void ReleaseRegister() { Messenger.Default.Unregister(this); } ``` ### 5.释放注册信息和内存处理 为了避免不必要的内存泄漏, .Net框架提出了比较实用的 WeakReference(弱引用)对象。该功能允许将对象的引用进行弱存储。如果对该对象的所有引用都被释放了,则垃圾回收机便可回收该对象。 类似将所有的注册信息保存在一个弱引用的存储区域,一旦注册信息所寄宿的宿主(View或者ViewModel)被释放,引用被清空,该注册信息也会在一定时间内被释放。 下面一个表格来源于 **[Laurent Bugnion](https://msdn.microsoft.com/zh-cn/magazine/ee532098.aspx?sdmr=laurentbugnion&sdmi=authors)** 对 MVVM 的说明文档: **未取消注册时的内存泄漏风险:** | **可见性** | **WPF** | **Silverlight** | **Windows Phone 8** | **Windows 运行时** | | ---------- | ------- | --------------- | ------------------- | ------------------ | | 静态 | 无风险 | 无风险 | 无风险 | 无风险 | | 公共 | 无风险 | 无风险 | 无风险 | 无风险 | | 内部 | 无风险 | 风险 | 风险 | 无风险 | | 专用 | 无风险 | 风险 | 风险 | 无风险 | | 匿名Lambda | 无风险 | 风险 | 风险 | 无风险 | ### 6.专业信道和广播信道 **令牌Token** 使用令牌(Token)区分和使用信道:这是最常用的方式。使用专属Token,可以区分不同的信道,并提高复用性。 Messenger中包含一个token参数,发送方和注册方使用同一个token,便可保证数据在该专有信道中流通,所以令牌是筛选和隔离消息的最好办法。 ```C# //以ViewAlert位Tokon标志,进行消息发送 Messenger.Default.Send("ViewModel通知View弹出消息框", "ViewAlert"); ``` ```C# public partial class MessagerForView : Window { public MessagerForView() { InitializeComponent(); //消息标志token: //ViewAlert,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致 //执行方法Action: //ShowReceiveInfo,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。 Messenger.Default.Register(this, "ViewAlert", ShowReceiveInfo); this.DataContext = new MessengerRegisterForVViewModel(); //卸载当前(this)对象注册的所有MVVMLight消息 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); } /// /// 接收到消息后的后续工作:根据返回来的信息弹出消息框 /// /// private void ShowReceiveInfo(String msg) { MessageBox.Show(msg); } } ``` ## 9.拓展参考 参考资料: [利刃 MVVMLight 10:Messenger 深入 - Hello-Brand - 博客园 (cnblogs.com)](https://www.cnblogs.com/wzh2010/p/6689423.html) **6、专有信道和广播信道** 6.1 过滤Messenger发送端(通过判断发送端来确认是否是发送给自己的): 6.2 开设专用的Messenger通道: **7、使用内置消息** 比如我们上面用到的 NotificationMessage ,以及PropertyChanged­Message。 Notification是一种消息通知机制;而PropertyChanged­Message主要指的是当属性改变的时候,执行通知操作。 相关代码:附件-利刃 MVVMLight相关代码