- A+
MVVM-Model和ViewModel的创建和配置
简介
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; } } } }
在这里,我放入了UserName
和Password
用于存储账号
与密码
,这两个属性会在xaml
中绑定到TextBlock
的Text
上,方便与外界做交互。
定义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
中绑定的不再是UserName
和Password
了,而是loginModel.UserName
和loginModel.Password
。
后端代码访问属性
目前,loginVM
是存放所有我们需要访问的属性的一个类,如果我们需要访问某个属性,那么就是到loginVM
下面的loginVM.loginModel
当中去访问UserName
和Password
。
也就是说,欲想访问这些属性,需要通过:
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
那么我们需要在什么时候刷新呢?我们需要在UserName
和Password
发生了改变的时候对吧。或者简单一点,当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)); } }
但是这时候在代码中修改UserName
和Password
,发现界面并不会刷新?
loginVM.loginModel.UserName = ""; loginVM.loginModel.Password = "";
这是因为我们确实添加了调用接口的代码,但是仅仅修改UserName
和Password
并不会引起LoginModel
对象本身的更改——UserName
和Password
只是LoginModel
的内部属性。
换句话说,仅仅改变LoginModel
内部的UserName
和Password
并不会触发INotifyPropertyChanged
的PropertyChanged
事件,因为这个事件是和LoginModel
对象的属性关联的,而不是和LoginModel
内部的属性UserName
和Password
关联的。
两种解决方法:
- 在修改完
loginVM.loginModel.UserName
和loginVM.loginModel.Password
之后,手动“修改”loginVM.loginModel
loginVM.loginModel.UserName = ""; loginVM.loginModel.Password = ""; loginVM.loginModel = loginVM.loginModel;
- 在
LoginModel
类中也实现INotifyPropertyChanged
接口,并且给UserName
和Password
的get
也添加了调用接口的代码。这样,当UserName
或Password
属性发生变化时,它们可以通知视图进行更新。
小结
到目前为止,我们已经创建了 Model (LoginModel
) 和 ViewModel (LoginVM
),并在 ViewModel 中处理了属性变化通知(通过实现INotifyPropertyChanged
)。接下来需要完善 View 的部分了。这个对应接下来的课程,将在下一篇笔记中记录。MVVM 整个体系较为庞大,这两节课也主要从改编代码的角度切入,在之后我还会写一篇 MVVM 总结,从头开始理清楚 MVVM 该怎么构架。