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 该怎么构架。

MVVM - Model和ViewModel的创建和配置的更多相关文章

  1. MVVM模式中ViewModel和View、Model有什么区别

    Model:很简单,就是业务逻辑相关的数据对象,通常从数据库映射而来,我们可以说是与数据库对应的model. View:也很简单,就是展现出来的用户界面. 基本上,绝大多数软件所做的工作无非就是从数据 ...

  2. django 简易博客开发 1 安装、创建、配置、admin使用

    首先贴一下项目地址吧  https://github.com/goodspeedcheng/sblog 到现在位置项目实现的功能有: 1.后台管理使用Admin ,前端显示使用bootstrap 2. ...

  3. Servlet过滤器,Servlet过滤器创建和配置

    第一:Servlet的过滤器的创建和配置,创建一个过滤器对象需要实现javax.servlet.Filter接口,同时实现Filter的3个方法.        第一方法是过滤器中的init()方法用 ...

  4. IIS负载均衡-Application Request Route详解第二篇:创建与配置Server Farm(转载)

    IIS负载均衡-Application Request Route详解第二篇:创建与配置Server Farm 自从本系列发布之后,收到了很多的朋友的回复!非常感谢,同时很多朋友问到了一些问题,有些问 ...

  5. Acitivity创建与配置

    •Activity的创建和配置 –Activity提供了和用户交互的可视化界面.创建一个Activity一般是继承Activity(当然也可以继承ListActivity.MapActivity等), ...

  6. 什么是Servlet,Servlet的作用,生命周期,如何创建、配置Servlet

    什么是Servlet,作用是? servlet是一个基于java技术的WEB组件,运行在服务器端,我们利用 sevlet可以很轻松的扩展WEB服务器的功能,使它满足特定的应用需要.servlet由se ...

  7. ios中pch文件的创建与配置

     PCH文件(Precompile Prefix Header File),也就是预编译头文件,其作用就是,方便你一次性导入在多个文件中同时用到的头文件.宏或者URL地址等(全局使用),可以有效的帮你 ...

  8. IDEA如何创建及配置Web项目(多图)

    正文之前 在学习Java Web时,第一个遇到的问题就是如何创建或配置Web项目了,今天,就用IntelliJ IDEA 来进行Web项目配置: 创建Web项目 配置web项目 正文 创建Web项目 ...

  9. Spring Boot 多模块项目创建与配置 (一) (转)

    Spring Boot 多模块项目创建与配置 (一) 最近在负责的是一个比较复杂项目,模块很多,代码中的二级模块就有9个,部分二级模块下面还分了多个模块.代码中的多模块是用maven管理的,每个模块都 ...

  10. Spring Boot 多模块项目创建与配置 (一)

    最近在负责的是一个比较复杂项目,模块很多,代码中的二级模块就有9个,部分二级模块下面还分了多个模块.代码中的多模块是用maven管理的,每个模块都使用spring boot框架.之前有零零散散学过一些 ...

随机推荐

  1. 终于写完轮子一部分:tcp代理 了,记录一下

    24年终自己立了flag: 25年做些轮子玩(用于浪费生命,赚不了钱) 所以25年就准备用c#写一个网络代理NZOrz(nginx知道吧,就那玩意儿干的事),包含 udp/tcp/http1 2 3, ...

  2. 【Python】函数传参的方式

    学习笔记//20230402 edit 1.传参类型 值传递 引用传递 就像C++的参数传递: 值传递时值把实参的值传递给function, function 内对形参的修改不会影响实参; 引用传递时 ...

  3. 三分钟教学:手把手教你实现Arduino发布第三方库

    三分钟教学:手把手教你实现Arduino发布第三方库 原文链接: 手把手教你实现Arduino发布第三方库 摘要 Arduino 发布第三方库的流程包括:构建库的基本框架后将其打包并上传至 GitHu ...

  4. MongoDB 复制集机制及原理

    复制集的作用 MongoDB 复制集的主要意义在于实现服务高可用. 它的现实依赖于两个方面的功能: 数据写入时将数据迅速复制到另一个独立节点上 在接受写入的节点发生故障时自动选举出一个新的代替节点 在 ...

  5. APEX实战第2篇:构建自己第一个APEX程序

    为了尽快实现从0到1的突破,第一个程序就选择一个最简单易懂的生活场景. 后续随着APEX学习过程不断迭代增强这个程序. 现在就开始吧,我给孩子看一些公开网络课时,习惯把课程资源做分片,然后有计划的让孩 ...

  6. 网络设备开局配置生成器(第三次更新) QQ交流群:(4817315)

    下载:链接: https://pan.baidu.com/s/1BIvh3u7VfbaQtBsUOjl1IA?pwd=kgtw 提取码: kgtw 网络设备开局配置生成器(SecureCRT vbs脚 ...

  7. leetcode每日一题:向字符串添加空格

    题目 2109. 向字符串添加空格 给你一个下标从 0 开始的字符串 s ,以及一个下标从 0 开始的整数数组 spaces . 数组 spaces 描述原字符串中需要添加空格的下标.每个空格都应该插 ...

  8. Linux权限之基础权限

    介绍 Linux是多用户的操作系统,允许多个用户同时登录和工作,Linux权限是操作系统用来限制不同用户对资源的访问机制.这里暂且将Linux的权限分为三类: 基本权限:给文件和目录的所属者.所属组. ...

  9. 判断属性值,选择性执行下一步(get element attribute指令的用法)

    应用场景: 下图线下支付,在退款前需要勾选这种支付方式,否则无法实现支付. 如果在测试脚本内即加入勾选指令,那么在下次执行的时候就会再次勾选,从而造成去除勾选的操作 对比一下勾选前后,勾选框元素内容组 ...

  10. Web前端入门第 40 问:CSS float 浮动布局应用场景

    CSS float 浮动属性最早用于文字环绕图片效果,就像传统报纸上的印刷排版一样. CSS2 的规范推动了浮动用于其他元素,float 便被大范围应用于布局,当年常说的 div + css 布局,f ...