当前位置: 首页 > article >正文

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

前言

还记得早些年刚工作的那会,公司的产品从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.kler.cn/a/324108.html

相关文章:

  • MySQL缓存使用率超过80%的解决方法
  • Linux下编译安装Nginx
  • 【eNSP】企业网络架构实验——vlan间的路由通信(三)
  • GitHub新手入门 - 从创建仓库到协作管理
  • FlinkSql读取kafka数据流的方法(scala)
  • 使用--log-file保存pytest的运行日志
  • React 有哪些生命周期
  • 开源服务器管理软件Nexterm
  • 提取出来的ip与我原本的ip是在同一个区吗
  • Python编码系列—Python备忘录模式:掌握对象状态保存与恢复技术
  • 【成神之路】Ambari实战-013-代码生命周期-metainfo-configFiles详解
  • 【Linux】包管理器、vim详解及简单配置
  • 实战C++手写线程池
  • windows11下vscode配置lua环境
  • 1欧几里得聚类提取
  • WPF入门教学二十二 多线程与异步编程
  • Django——admin创建和使用
  • 【Python游戏开发】扫雷游戏demo
  • Linux云计算 |【第四阶段】RDBMS1-DAY2
  • 使用python获取百度一下,热搜TOP数据详情
  • 什么是聚类?
  • Docker数据卷有哪些常见的驱动类型?
  • K8S真正删除pod
  • SeeClick: Harnessing GUI Grounding for Advanced Visual GUI Agents论文学习
  • socket编程描述tcp的三次握手
  • Postman/Jmeter接口测试