WPF MVVM入门系列教程(一、MVVM模式介绍)

news2024/11/18 6:00:53

前言

还记得早些年刚工作的那会,公司的产品从Delphi转成了WPF(再早些年是mfc)。当时大家也是处于一个对WPF探索的阶段,所以有很多概念都不是非常清楚。

但是大家都想堆技术,就提出使用MVVM,我那会是第一次听到MVVM,在网上看了一些资料后,也难以理解,后面也是硬着头皮在写。

有意思的是其它年资高一点的同事,他们也不能很好的运行MVVM模式进行开发,写着写着,都变成了Code-Behind模式。

后面工作几年后,对WPF的一些技术点都逐渐熟练,再去学习MVVM模式的开发就变得相对容易 一些。

写本文的目的除了对自己 使用MVVM开发经验的一些总结 ,更多的是为了帮助有需要的小伙伴,特别是刚接触MVVM模式开发的。

一种惯用的模式

从Visual Basic, 到Delphi, Visual FoxPro,.NET Windows Forms, ASP.NET WebForms等,我们都是使用代码直接去操作界面元素。

这种代码也可以称之为意大利面条式代码(spaghetticode),指的是和UI捆绑在一起并且具有低内聚力的类和方法。

让我们先看看下面的示例

假设我们在界面放置一个文本显示 、 一个文本框和一个按钮控件,当按钮点击时,弹框显示文本框里的内容

Winform示例

首先我们使用Visual Studio创建一个Winform工程,从工具箱中拖入Label,TextBox和Button,界面布局如下:

设计器自动生成的界面代码

Form1.Designer.cs

 1 namespace WinformDemo
 2 {
 3     partial class Form1
 4     {
 5         /// <summary>
 6         ///  Required designer variable.
 7         /// </summary>
 8         private System.ComponentModel.IContainer components = null;
 9 
10         /// <summary>
11         ///  Clean up any resources being used.
12         /// </summary>
13         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
14         protected override void Dispose(bool disposing)
15         {
16             if (disposing && (components != null))
17             {
18                 components.Dispose();
19             }
20             base.Dispose(disposing);
21         }
22 
23         #region Windows Form Designer generated code
24 
25         /// <summary>
26         ///  Required method for Designer support - do not modify
27         ///  the contents of this method with the code editor.
28         /// </summary>
29         private void InitializeComponent()
30         {
31             label1 = new Label();
32             textBox1 = new TextBox();
33             button1 = new Button();
34             SuspendLayout();
35             // 
36             // label1
37             // 
38             label1.AutoSize = true;
39             label1.Location = new Point(155, 157);
40             label1.Name = "label1";
41             label1.Size = new Size(56, 17);
42             label1.TabIndex = 0;
43             label1.Text = "输入内容";
44             // 
45             // textBox1
46             // 
47             textBox1.Location = new Point(229, 151);
48             textBox1.Name = "textBox1";
49             textBox1.Size = new Size(100, 23);
50             textBox1.TabIndex = 1;
51             // 
52             // button1
53             // 
54             button1.Location = new Point(195, 200);
55             button1.Name = "button1";
56             button1.Size = new Size(75, 23);
57             button1.TabIndex = 2;
58             button1.Text = "获取输入";
59             button1.UseVisualStyleBackColor = true;
60             button1.Click += button1_Click;
61             // 
62             // Form1
63             // 
64             AutoScaleDimensions = new SizeF(7F, 17F);
65             AutoScaleMode = AutoScaleMode.Font;
66             ClientSize = new Size(532, 357);
67             Controls.Add(button1);
68             Controls.Add(textBox1);
69             Controls.Add(label1);
70             Name = "Form1";
71             Text = "Form1";
72             ResumeLayout(false);
73             PerformLayout();
74         }
75 
76         #endregion
77 
78         private Label label1;
79         private TextBox textBox1;
80         private Button button1;
81     }
82 }

后台逻辑代码

Form1.cs

 1 namespace WinformDemo
 2 {
 3     public partial class Form1 : Form
 4     {
 5         public Form1()
 6         {
 7             InitializeComponent();
 8         }
 9 
10         private void button1_Click(object sender, EventArgs e)
11         {
12             MessageBox.Show(this.textBox1.Text);
13         }
14     }
15 }

运行效果

WPF示例

首先我们使用Visual Studio创建一个WPF工程,在XAML中进行布局,放置Label,TextBox和Button控件

MainWindow.xaml

 1 <Window x:Class="WpfDemo.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:WpfDemo"
 7         mc:Ignorable="d"
 8         Title="MainWindow" Height="450" Width="800">
 9     <Grid>
10         <Label Content="输入内容" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="-100,0,0,0"></Label>
11         <TextBox Width="200" Name="tbox" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="384,0,0,0"></TextBox>
12         <Button Content="获取输入" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="400,252,0,0" Click="Button_Click"/>
13     </Grid>
14 </Window>

 MainWindow.xaml.cs

 1 using System.Windows;
 2 
 3 namespace WpfDemo
 4 {
 5     public partial class MainWindow : Window
 6     {
 7         public MainWindow()
 8         {
 9             InitializeComponent();
10         }
11 
12         private void Button_Click(object sender, RoutedEventArgs e)
13         {
14             MessageBox.Show(this.tbox.Text);
15         }
16     }
17 }

WPF MVVM示例

在正式开始学习MVVM之前,我们先通过这个示例简单感受一下。

MainWindow.xaml

 1 <Window x:Class="WpfMVVMDemo.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:WpfMVVMDemo"
 7         mc:Ignorable="d"
 8         Title="MainWindow" Height="450" Width="800">
 9     <Grid>
10         <Label Content="输入内容" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="-100,0,0,0"></Label>
11         <TextBox Width="200" Text="{Binding InputText}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="384,0,0,0"></TextBox>
12         <Button Content="获取输入" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="400,252,0,0" Command="{Binding GetInputCommand}"/>
13     </Grid>
14 </Window>

可以看到代码中加粗的部分,这里就是对文本框的文本和按钮的命令进行绑定。现在不理解没有关系,我们接着往下看。

此时我们不写任何后台代码,程序 也能运行起来,只是单击按钮没有反应。

我们为MainWindow增加一个ViewModel

MainWindowViewModel.cs

 1 using System.ComponentModel;
 2 using System.Windows;
 3 
 4 namespace WpfMVVMDemo
 5 {
 6     public class MainWindowViewModel : INotifyPropertyChanged
 7     {
 8         public event PropertyChangedEventHandler? PropertyChanged;
 9 
10         private string inputText;
11 
12         public string InputText
13         {
14             get => this.inputText;
15             set
16             {
17                 inputText = value;
18                 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("InputText"));
19             }
20         }
21 
22         public DelegateCommand GetInputCommand { get; set; }
23 
24         public MainWindowViewModel()
25         {
26             GetInputCommand = new DelegateCommand(GetInput);
27         }
28 
29 
30         private void GetInput()
31         {
32             MessageBox.Show(InputText);
33         }
34     }
35 }

将ViewModel绑定到界面的数据上下文(DataContext)

1   public partial class MainWindow : Window
2   {
3       public MainWindow()
4       {
5           InitializeComponent();
6 
7           this.DataContext = new MainWindowViewModel();
8       }
9   }

此时再执行,点击按钮,就可以弹框显示文本框的内容。

MVVM模式

MVVM的全称是:Model(模型)View(视图)ViewModel(模型视图)。MVVM是表现层(UI层)常用的一种设计模式。

在企业软件开发过程中,关注点分离(Separation of Concerns, SOC)是一个核心原则,它提供了许多好处,例如增强可维护性和提高系统的灵活性。而MVVM已成为在用户界面中实现 SOC 的惯用模式。

我记得刚接触MVVM的那会,在网上查的资料都会将MVVM与MVC、MVP模式进行对比。我觉得对于初学者来说,这个不是必须的,在引入较多的概念后,反而会引起混淆。

MVVM是在MVC和MVP的基础上,进行了优化,弥补了MVC和MVP在开发过程中的一些不足的地方。在后面的文章中,我会再单独进行讲解。

MVVM模式的整体结构如下所示

下面大概介绍一下各层的作用,在后面的文章中,会进行详细讲解

Model层:

模型是代表业务概念的实体;它可以是任何实体,从简单的客户实体到复杂的数据实体。

例如一个学生类

 1     public class Student
 2     {
 3         public int Id { get; set; }
 4 
 5         public string Name { get; set; }
 6 
 7         public DateTime Birthday { get; set; }
 8         
 9         public int Score { get; set; }
10     }

View层:

View是负责渲染Model的图形控件或控件集。屏幕上的View可以是WPF窗口WPF页,也可以是一个数据模板

在MVVM模式下,View仍然负责显示数据、收集用户输入并传递它,但是现在它被传递给ViewModel层。

ViewModel层:

ViewModel包含UI逻辑命令事件和对模型(Model)的引用

MVVM中,ViewModel不负责更新UI中显示的数据,因为WPF提供了数据绑定引擎,所以在ViewModel只需要处理逻辑,而不用去操作UI。

为了实现这一点,ViewModel必须实现INotifyPropertyChanged 接口并触发PropertyEvent事件。

本质上来说,这里是使用了观察者模式(一种设计模式),ViewViewModel的观察者,因此一旦ViewModel发生变化,UI就会自动更新。

说明:观察者模式可以访问以下链接了解:监视程序设计模式 - .NET | Microsoft Learn

在前面的MVVM示例代码中,我们新建了一个InputText属性,并在值更改时,触发PropertyEvent事件。

然后我们将InputText绑定到文本框上,当文本框的值发生变化时,WPF的(Binding)绑定功能,会更新绑定的属性值,也就是InputText。另一方面,当InputText属性值发生更改时,ViewViewModel的观察者,检查到值的变化,会更新UI。

除了InputText属性,还增加了一个GetInputCommand命令,将它绑定到ButtonCommand上,当按钮点击时,就会执行这个命令。

阅读到这里,有些小伙伴可能会有很多疑问,但是我们可以先不去看这些技术细节,只在意这样一种开发模式,后面我会将WPF MVVM开发中涉及的各个技术点进行详细讲解。

MVVM所带来的一些优点

1、良好的测试性

传统模式下,代码的可测试性较差,因为整个代码与UI紧密耦合,需要通过UI来驱动应用程序逻辑的事件,比如点击。

使用MVVM模式后,因为跟UI相关的值封装成了一个属性,事件封装成了命令,所以我们就可以单独进行单元测试,而不需要UI。

2、良好的扩展性和代码重用

由于跟UI解耦,所以ViewModel中的逻辑都可以单独进行开发,这就具备了良好的扩展性和代码重用功能。

我们可以将组件构建到单独的DLL中,还可以通过替换组件来提供不同的行为。

3、良好的代码结构以及可维护性

使用MVVM模式后,实现了SOC(关注点分离),代码的结构将会更清晰,同时也会提升可维护性。

MVVM所带来的一些缺点

1、缺乏官方的资料支持

虽然微软有Prism(以前由微软维护,现在由社区维护)/CommunityToolkit.Mvvm等MVVM相关的包,但在Microsoft Learn上并不能直接找到关于MVVM模式开发的介绍及使用文档。

2、开发模式的转变需要适应

对于习惯了Winform的开发人员来说,这种新的开发模式需要去适应。

3、对WPF的掌握程度要求较高

在项目中使用MVVM模式,需要熟练掌握WPF的绑定、命令、转换器、依赖属性、模板、样式等特性。

4、学习周期问题

理解 MVVM开发模式不难,短时间学习后也能运用到项目,但是要在企业应用中有效地实现该模式仍需要一段很长的学习时间。

何时使用MVVM模式

MVVM是UI层的一种设计模式,设计模式的诞生是为了解决问题,所以最核心的点在解决问题。在解决问题的基础上,我们再谈架构,谈可维护性。

关于何时使用MVVM模式,这个没有硬性要求,个人看来,在自己能力范围内,做自己力所能及的事情就好了。

比如当我们接到一个开发任务,领导下达的指令是快速完成。对于这种情况,我们可以直接使用传统操作控件的模式去进行开发,必要时,UI代码和业务代码混在一起也是可以的。相间相对充裕点,可以将业务逻辑和UI操作做一些隔离,使代码的层次更清晰。

又或者,我们对MVVM还不是很熟练,开发起来有一定的难度,我们也可以暂时先不采用MVVM开发模式。

对于长期项目,或者稍微偏大一点的项目,这种情况下,建议采用MVVM模式开发,并在前期建议做好充足的准备,进行统一的规划。

写在最后的话

我记得我当初学习WPF MVVM模式开发比较困难的地方就是,理解 了MVVM这种开发模式,但是却不能很好的和WPF结合起来。

总结了一下,原因有以下几点:

1、没有成套的资料可以供参考学习,网上流传的资料大多都是针对 MVVM模式,但是对于实现这种模式需要掌握的技术点却并没有很好的说明。

2、模式的转变一下难以适应。

随着学习的深入,我逐渐掌握了这种开发模式,所以才有这一系列的文章。

在后面的文章中,我会将MVVM开发中需要了解的点点滴滴都尽量覆盖到。本人能力有限,在文章中如果有错误之处,还恳请读者小伙伴可以帮忙指出,我会及时修正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2175530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

想做个WPS的自动化代码,参考如下:

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

水仙花数求解-C语言

1.问题&#xff1a; 输出100-1000之间所有的“水仙花数”。 2.解答&#xff1a; “水仙花数”是指一个3位数&#xff0c;其各位数字立方和等于该数本身&#xff0c;逐个位数判断即可&#xff0c;写代码的时候要考虑到每一位。 3.代码&#xff1a; #include<stdio.h>//…

9.28QT基础

widget.cpp widegt.h .pro main.cpp 一个仿QQ登录界面 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {this->setFixedSize(350,500);this->setStyleSheet("background-color:#e5f0ff;");QLineEdit *edit1 new QLine…

1.2.3 HuggingFists安装说明-MacOS安装

MacOS版安装说明 下载地址 【GitHub】GitHub - Datayoo/HuggingFists4Mac 【百度网盘】https://pan.baidu.com/s/12WxZ-2GgMtbQeP7AcmsyHg?pwd2024 安装说明 环境要求 操作系统&#xff1a;MacOS 硬件环境&#xff1a;至少4核8G 安装步骤 1. 安装Docker环境。若已安装Do…

kali linux 终端复用和界面移动

kali linux 终端复用和界面移动 经验和操作 渗透测试的工具选择考虑 01 能用命令行就不用图形ui 图形ui 容易对细节隐藏&#xff0c;命令行工具的在终端输出的日志相对透明&#xff0c;容易观察和调整 wireshark – tcpdump burpsuit – curl( web 访问相关), wfuzz(模糊测…

带你0到1之QT编程:十七、Http协议实战,实现一个简单服务器和一个客户端进行http协议通信

此为QT编程的第十七谈&#xff01;关注我&#xff0c;带你快速学习QT编程的学习路线&#xff01; 每一篇的技术点都是很很重要&#xff01;很重要&#xff01;很重要&#xff01;但不冗余&#xff01; 我们通常采取总-分-总和生活化的讲解方式来阐述一个知识点&#xff01; …

Python15行代码 tkinter模块 写计算机

之前去备战csp-j了 也有一段时间没更新了 结果白名单没捞着 还差点被我妈打喜 今天闲来无事 写个计算器玩玩 _____________________________________________________________________________ 老规矩 先放代码 from tkinter import *;from math import sqrt;aTk();a.geome…

机械键盘驱动调光DIY--【DAREU】

1 下载键盘对应的驱动&#xff0c;不要装到C盘 达尔优驱动下载中心 2 驱动更改教程 标准模式 键盘功能 鼠标功能 切换灯光 切换配置文件 多媒体 windows快捷键 禁用 Fn 启动程序 文本功能 光标定位 FN模式 灯光效果设置 注意 宏--自定义功能

【SpringCloud】多机部署, 负载均衡-LoadBalance

多机部署, 负载均衡-LoadBalance 1. 负载均衡介绍1.1 问题描述1.2 什么是负载均衡1.3 负载均衡的⼀些实现服务端负载均衡客⼾端负载均衡 2. Spring Cloud LoadBalancer 1. 负载均衡介绍 1.1 问题描述 观察上个章节远程调⽤的代码 根据应⽤名称获取了服务实例列表从列表中选择…

【SQL】未订购的客户

目录 语法 需求 示例 分析 代码 语法 SELECT columns FROM table1 LEFT JOIN table2 ON table1.common_field table2.common_field; LEFT JOIN&#xff08;或称为左外连接&#xff09;是SQL中的一种连接类型&#xff0c;它用于从两个或多个表中基于连接条件返回左表…

CSS布局中的定位

一、position 1.static position: static; 默认值&#xff0c;没有定位2 .relative 相对定位&#xff1a;相对自身原来的位置进行偏移 偏移设置&#xff1a;top、left、right、bottom 相对定位元素的规律&#xff1a; 设置相对定位的盒子会相对于它原来的位置&#xff0c;通…

Nature数据库介绍及个人获取Nature文献下载途径

Springer Nature集团是一家全球领先的从事科研、教育和专业出版的机构&#xff0c;也是世界上最大的学术图书出版公司&#xff0c;同时出版全球最具影响力的期刊&#xff0c;也是开放获取领域的先行者。Springer Nature在2015年由Nature出版集团&#xff08;Nature Portfolio&a…

JS加密=JS混淆?(JS加密、JS混淆,是一回事吗?)

JS加密、JS混淆&#xff0c;是一回事吗&#xff1f; 是的&#xff01;在国内&#xff0c;JS加密&#xff0c;其实就是指JS混淆。 1、当人们提起JS加密时&#xff0c;通常是指对JS代码进行混淆加密处理&#xff0c;而不是指JS加密算法&#xff08;如xor加密算法、md5加密算法、…

正点原子——DS100示波器操作手册

目录 基础按键&#xff1a; 快捷键 主界面&#xff1a; 垂直设置&#xff1a; 通道设置&#xff1a; 探头比列&#xff1a; 垂直档位&#xff1a; 垂直偏移&#xff1a; 幅度单位&#xff1a; 水平设置&#xff1a; 触发方式&#xff1a; 测量和运算: 光标测量&am…

面试题:MySQL你用过WITH吗?领免费激活码

感谢Java面试教程的Java多线程文章&#xff0c;点击查看>原文 Java面试教程&#xff0c;发mmm116可获取IDEA-jihuoma 在MySQL中&#xff0c;WITH子句用于定义临时表或视图&#xff0c;也称为公共表表达式&#xff08;CTE&#xff09;。它允许你在一个查询中定义一个临时结果…

二叉搜索树 K模型 和 KV模型

&#x1f33b;个人主页&#xff1a;路飞雪吖~ ✨专栏&#xff1a;C/C 目录 一、二叉搜索树&#xff08;K模型&#xff09;的模拟实现 &#x1f31f;二叉搜索树的概念 &#x1f31f;二叉搜索树的操作 &#x1f320;二叉搜索树的查找 &#x1f320;二叉搜索树的插入 &#x…

Docker安装与应用

前言 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言开发。Docker 可以让开发者打包他们的应用以及依赖包到一个轻 量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互 之间…

《程序猿之Redis缓存实战(1) · 基础知识》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

OpenGL ES 绘制一个三角形(2)

OpenGL ES 绘制一个三角形(2) 简述 本节我们基于Android系统&#xff0c;使用OpenGL ES来实现绘制一个三角形。在OpenGL ES里&#xff0c;三角形是一个基础图形&#xff0c;其他的图形都可以使用三角形拼接而成&#xff0c;所以我们就的案例就基于这个开始。 在Android系统中…

添加vscode插件C/C++ snippets,快速生成LVGL .c/.h文件模版

文章目录 一、安装插件二、在安装目录下添加c.json和cpp.json文件①在 C:/Users/yourname/AppData/Roaming/Code/User/snippets/ 目录下创建 c.json 并填入如下内容&#xff1a;②在 C:/Users/yourname/AppData/Roaming/Code/User/snippets/ 目录下创建 cpp.json 并填入如下内容…