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 }
View Code

 

后台逻辑代码

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模式的整体结构如下所示

 

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

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逻辑命令事件和对模型的引用

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

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

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

 

在前面的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(关注点分离),代码的结构将会更清晰,同时也会提升可维护性。

 

posted @ 2024-09-13 11:37  zhaotianff  阅读(18)  评论(0编辑  收藏  举报