MVVM – Model和ViewModel的创建和配置

  • MVVM – Model和ViewModel的创建和配置已关闭评论
  • 64 次浏览
  • A+
所属分类:.NET技术
摘要

本文同时为b站WPF课程的笔记,相关示例代码MVVM:Model-View-ViewModel,是一种软件架构的模式。通过引入一个中间层ViewModel,分离用户界面的表示层(View)和业务逻辑层(Model)。


MVVM-Model和ViewModel的创建和配置

本文同时为b站WPF课程的笔记,相关示例代码

简介

MVVM:Model-View-ViewModel,是一种软件架构的模式。通过引入一个中间层ViewModel,分离用户界面的表示层(View)和业务逻辑层(Model)。

需要手动实现MVVM,可以通过以下方法。

定义Model

创建一个模型(Model)类,用来定义需要的数据结构。

这个类包含了想要在应用中使用和展示的数据。

这里就创建LoginModel

将需要的属性放到这个类当中

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace WPF_Study {     public class LoginModel     {         private string _UserName;          public string UserName         {             get { return _UserName; }             set             {                 _UserName = value;             }         }           private string _Password;          public string Password         {             get { return _Password; }             set             {                 _Password = value;             }         }     } } 

在这里,我放入了UserNamePassword用于存储账号密码,这两个属性会在xaml中绑定到TextBlockText上,方便与外界做交互。

定义ViewModel

创建ViewModel

创建一个ViewModel类(这里就叫做LoginVM),这个类将作为View(用户界面)和Model(数据)之间的桥梁。

在这个类中创建属性LoginModel

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace WPF_Study {     public class LoginVM     {         private LoginModel _loginModel;          public LoginModel loginModel         {             get             {                 return _loginModel;             }             set             {                 _loginModel = value;             }         }       } } 

指定MainWindow上下文

MainWindow.xaml.cs中,将ViewModel指定给当前界面的上下文:

LoginVM loginVM; public MainWindow() {     InitializeComponent();     loginVM = new LoginVM();     this.DataContext = loginVM; } 

绑定到xaml控件属性

同时修改xaml里面需要绑定的属性。别忘记在xaml中绑定的不再是UserNamePassword了,而是loginModel.UserNameloginModel.Password

后端代码访问属性

目前,loginVM是存放所有我们需要访问的属性的一个类,如果我们需要访问某个属性,那么就是到loginVM下面的loginVM.loginModel当中去访问UserNamePassword

也就是说,欲想访问这些属性,需要通过:

loginVM.loginModel.UserName = ""; loginVM.loginModel.Password = ""; 

这样的方法。

比如以下定义一个登录按钮:

private void Button_Click(object sender, RoutedEventArgs e) {      if (loginVM.loginModel.UserName == "wpf" && loginVM.loginModel.Password == "777")     {         //MessageBox.Show("Login");         Index index = new Index();         index.Show();         this.Hide();     }     else     {         MessageBox.Show("Error");         loginVM.loginModel.UserName = "";         loginVM.loginModel.Password = "";     } } 

这个时候尝试运行,会发现程序报错:

loginVM.loginModel.UserName:未将对象引用设置到对象的实例。

出现“未将对象引用设置到对象的实例”错误通常是因为尝试访问一个还未初始化的对象的属性或方法。

这是因为,我们确实在MainWindow.xaml.cs中实例化了loginVM = new LoginVM();,但是我们没有实例化loginModel。此时直接访问loginVM.loginModel的成员时,因为LoginVM类中的_LoginModel成员变量没有被初始化。

那么怎么办呢?只需要在loginModel的访问器中加入是否实例化的特判即可:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace WPF_Study {     public class LoginVM     {         private LoginModel _loginModel;          public LoginModel loginModel         {             get             {                  if(_loginModel == null)                      _loginModel = new LoginModel();                 return _loginModel;             }             set             {                 _loginModel = value;              }         }       } } 

(这个应该是更好的解决方案)也可以使用构造函数的方式,添加了一个构造函数LoginVM(),初始化_LoginModel对象。这样,创建一个LoginVM的实例时,它会自动拥有一个初始化了的LoginModel实例。

public LoginVM() {     _loginModel = new LoginModel(); } 

实现INotifyPropertyChanged接口

ViewModel应该实现INotifyPropertyChanged接口,这样当属性的值改变时能够通知UI进行更新。

ViewModel继承INotifyPropertyChanged类:

public class LoginVM:INotifyPropertyChanged ... 

以及INotifyPropertyChanged接口实现的核心:定义PropertyChanged事件、实现RaisePropertyChanged方法

public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyChanged) {     PropertyChangedEventHandler handler = this.PropertyChanged;     if (handler != null)         handler(this, new PropertyChangedEventArgs(propertyChanged)); } 

接下来在需要的地方调用RaisePropertyChanged(),就可以实现刷新UI

那么我们需要在什么时候刷新呢?我们需要在UserNamePassword发生了改变的时候对吧。或者简单一点,当LoginModel发生了变化的时候。(这是不太对的,后面会说)

那么我们在LoginMV.cs中的LoginModel loginModel访问器set中,设置RaisePropertyChanged(nameof(LoginModel));即可。

现在LoginMV.cs中的关于LoginModel数据结构的部分:

private LoginModel _loginModel;  public LoginModel loginModel {     get     {         if (_loginModel == null)             _loginModel = new LoginModel();         return _loginModel;     }     set     {         _loginModel = value;         RaisePropertyChanged(nameof(LoginModel));     } } 

但是这时候在代码中修改UserNamePassword,发现界面并不会刷新?

loginVM.loginModel.UserName = ""; loginVM.loginModel.Password = ""; 

这是因为我们确实添加了调用接口的代码,但是仅仅修改UserNamePassword并不会引起LoginModel对象本身的更改——UserNamePassword只是LoginModel的内部属性。

换句话说,仅仅改变LoginModel内部的UserNamePassword并不会触发INotifyPropertyChangedPropertyChanged事件,因为这个事件是和LoginModel对象的属性关联的,而不是和LoginModel内部的属性UserNamePassword关联的。

两种解决方法:

  1. 在修改完loginVM.loginModel.UserNameloginVM.loginModel.Password之后,手动“修改”loginVM.loginModel
loginVM.loginModel.UserName = ""; loginVM.loginModel.Password = ""; loginVM.loginModel = loginVM.loginModel; 
  1. LoginModel类中也实现INotifyPropertyChanged接口,并且给UserNamePasswordget也添加了调用接口的代码。这样,当UserNamePassword属性发生变化时,它们可以通知视图进行更新。

小结

到目前为止,我们已经创建了 Model (LoginModel) 和 ViewModel (LoginVM),并在 ViewModel 中处理了属性变化通知(通过实现INotifyPropertyChanged)。接下来需要完善 View 的部分了。这个对应接下来的课程,将在下一篇笔记中记录。MVVM 整个体系较为庞大,这两节课也主要从改编代码的角度切入,在之后我还会写一篇 MVVM 总结,从头开始理清楚 MVVM 该怎么构架。