MVVM 模式的精简实现






4.85/5 (14投票s)
分解 MVVM 模式
概述
互联网上有大量关于学习 MVVM 模式的教程,而且,恕我直言,其中许多并没有将该模式精简到最基本。我不会说他们教授模式的方法是错误的,也不会说我的方法是万能的。每个人学习方式不同,有时最基本的方法就是最好的。话虽如此,我将尽我所能,以最基本的实现方式来解释 MVVM 模式。
我所说的 MVVM 模式的基本实现,是指我不会使用 `ICommand` 接口,也不会使用像 MVVMLight 这样的外部库。唯一需要的是 `INotifyPropertyChanged` 接口。
本教程分为两部分。第一部分是理论讲解。第二部分是一个简单的项目,并 breakdown 了重要的代码片段。我下面写的代码是用 .Net 4.5.2 编写的,也用 .Net 4.6 测试过,以确保与 Visual Studio 2015 的发布兼容,从而使本文保持最新。
废话不多说,我们开始第一部分。
第1节
MVVM 模式是 MVC 模式的扩展,主要用于 Web 开发。它不是 Model View Controller,而是 Model(模型)、View(视图)和 ViewModel(视图模型)。因此,如果你是 MVVM 模式的初学者,你可能会想知道为什么这个模式如此重要。你这么想是正确的,原因很简单:代码分离。代码分离可以让你将 UI 和后端代码分开。这在各种情况下尤其有用。
一个主要例子是,你正在编写后端代码,而另一位开发人员正在编写 UI。如果你不使用 MVVM 模式,UI 开发人员所做的任何更改都可能破坏你的代码。MVVM 模式有助于消除这个问题。
此外,每当你使用 MVVM 模式时,重要的是要注意你正在采用一种声明式编程风格。
MVVM 模式的优点是什么?
1. 你的 UX 设计师和后端程序员可以同时处理同一个项目,而不会发生冲突。
2. 很容易为 ViewModel 和 Model 创建单元测试,而无需 View。
3. 轻松重新设计 UI,而不会影响你的后端代码。
4. 更容易将现有业务逻辑集成到 MVVM 解决方案中。
5. 更容易维护和更新。
6. 从长远来看,需要编写的代码更少。
MVVM 模型中的每个部分意味着什么?
Model 是一个暴露你想要使用的数据的类。此外,该类还实现了 `INotifyPropertyChanged` 接口。
View 是你的页面或用户控件(取决于你的选择)。这个类不应该有任何后端代码,但是,有一个例外。将 `DataContext` 设置为 ViewModel。
ViewModel 是一个类,它承担了所有繁重的工作。在这里,你可以调用数据库,创建 `ObservableCollection`,以及任何其他需要实现的后端代码。
MVVM 是如何工作的?
Model 类充当你的数据基础。它保存了你想在应用程序中展示的信息。ViewModel 充当你的数据和 UI 之间的粘合剂。而 View 就是你的 UI。
为什么数据绑定对 MVVM 很重要?
数据绑定允许用户绑定对象,以便当一个对象发生变化时,主对象会反映出它的变化。主要目的是确保 UI 始终自动与内部对象结构同步。
数据绑定有什么作用?
这绝不是数据绑定的全部功能,这更像是一个简要的概述。
数据绑定将两个属性连接在一起,如果一个属性发生变化,另一个属性也会发生变化。有很多方法可以做到这一点,例如我在第二部分中的方法。我在第二部分中使用的是 `PropertyChanged` 事件,另一种方法是 `Dependency Property`。
依赖属性与 `PropertyChanged` 事件
依赖属性和 `PropertyChanged` 事件之间有几个区别。
依赖对象不是可序列化的,具有线程关联性(这意味着它只能在创建它的线程中访问),并且会重写和密封 `Equals()` 和 `GetHashCode()` 方法。
然而,在测试中,依赖对象的性能比 `NotifyPropertyChanged` 好得多。如果你对测试结果感兴趣,另一篇文章详细介绍了这个问题。(https://codeproject.org.cn/Articles/62158/DependencyProperties-or-INotifyPropertyChanged)
哪些技术可以利用 MVVM 模式?
需要注意的是,使用这些技术并非必须使用 MVVM 模式。
可以使用 MVVM 配合 JavaScript 或 ASP.NET 等 Web 技术,但有一个前提。如果你正在为 Web 解决方案开发富 UI 应用程序,你可以使用 MVVM。Windows Phone 7、Windows Phone 8、Windows Phone 8.1、Windows Metro Applications(8 和 8.1)以及 Windows 10 Universal Projects 都是使用 MVVM 模式的绝佳选择。如果你仍然使用已弃用的 Silverlight,MVVM 在那里也同样出色。然而,最值得注意的是,Windows 桌面 WPF 应用程序通常是大多数人开始学习 MVVM 模式的地方。我也见过有人在 Unity 项目中使用 MVVM。
为什么我应该学习 MVVM 模式?
我个人认为,如果你使用支持实现 MVVM 模式的技术进行开发,那么你应该学习它,因为从长远来看,它会让你轻松得多。我的意思是,如果你有一位同事在你工作项目中实现了它,或者潜在的客户要求你为他们的项目实现该模式,那么了解它并且至少理解它的工作原理将非常有益。
如何使用数据绑定?
在 XAML 中,数据绑定非常简单。考虑以下代码片段。
<TextBox x:Name="textBox" Height="34" TextWrapping="Wrap" Text="{Binding Genre}" IsReadOnly="True"/>
这段 XAML 代码片段告诉我们,文本框的文本设置为 `Genre` 的绑定路径。
现在,如果你想用 C# 编写代码而不是使用 XAML 呢?好吧,考虑以下代码片段。
BindingSource binding = new BindingSource();
binding.Add(new Models.ModelClass("Action");
textbox.text = binding;
初步介绍完毕,让我们进入第二部分,通过代码来实践一下!
第2节
首先,我们来谈谈 Model 类。
在这个例子中,我们想暴露 `ReleaseDate`、`Name` 和 `Genre`。我们还想实现 `INotifyPropertyChanged` 接口,并在每个公共属性的 setter 中调用该事件。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace PracticeProject.Models
{
public class ModelClass : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
}
private string releaseDate;
public string ReleaseDate
{
get { return releaseDate; }
set
{
releaseDate = value;
NotifyPropertyChanged();
}
}
private string genre;
public string Genre
{
get { return genre; }
set
{
genre = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
接下来是 ViewModel 类。在示例中,我们将利用 `ObservableCollection`,并将参数设置为 Model 类。我们将使用 Linq 和 lambda 表达式来枚举 `ObservableCollection`。XML 文件嵌入在应用程序中,因此我们将使用反射和 `StreamReader` 类来调用这些数据并将其放入 `ObservableCollection`。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Xml.Linq;
using System.IO;
using System.Reflection;
using System.Windows;
namespace PracticeProject.ViewModels
{
public class ViewModelClass
{
public ObservableCollection<Models.ModelClass> Movies { get; set; }
StreamReader _textStreamReader;
public ViewModelClass()
{
LoadEmbeddedResource("PracticeProject.DataSources.Movies.xml");
XDocument xdoc = XDocument.Load(_textStreamReader);
var movies = xdoc.Descendants("Movies")
.Select(x =>
new Models.ModelClass
{
Name = (string)x.Element("Name"),
Genre = (string)x.Element("Genre").Value,
ReleaseDate = (string)x.Element("ReleaseDate").Value
}
).ToList();
Movies = new ObservableCollection<Models.ModelClass>(movies);
}
private void LoadEmbeddedResource(string resource)
{
try
{
Assembly _assembly;
_assembly = Assembly.GetExecutingAssembly();
_textStreamReader = new StreamReader(_assembly.GetManifestResourceStream(resource));
}
catch (Exception ex)
{
MessageBox.Show("Error Loading Embedded Resource: " + ex, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
第三项是 View 的 CodeBehind。本例将说明你不应该为它编写任何后端代码,尽管,像下面这样写一行是可以接受的。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PracticeProject.Views
{
/// <summary>
/// Interaction logic for MainView.xaml
/// </summary>
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
DataContext = new ViewModels.ViewModelClass();
}
}
}
最后是 View 的 XAML 代码。这实际上是使用 MVVM 模式中最难的部分。我们首先在 XAML 文档的命名空间中声明 ViewModel。我们创建一个列表框,并将其 `Movies` `ObservableCollection` 绑定到它。然后我们将 `UpdateSourceTrigger` 设置为 `PropertyChanged`。最后,我们将 `DisplayMemberPath` 设置为 `Name`。请确保 `IsSynchronizedWithCurrentItem` 设置为 `true`。
接下来,我们创建一个 StackPanel,并将它的 `DataContext` 设置为 ListBox,Path 设置为选中的项。
我们这样做是为了确保列表框中选中的任何项都会强制绑定对象相应地更新。
我们现在将在 StackPanel 中创建一个 Label 和一个 Textbox,我们将 Textbox 的文本绑定到 `Genre`,Label 的上下文绑定到 `ReleaseDate`。
<UserControl x:Class="PracticeProject.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PracticeProject.Views"
mc:Ignorable="d"
xmlns:vm="clr-namespace:PracticeProject.ViewModels"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ListBox x:Name="listBox" ItemsSource="{Binding Movies, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" Height="100" IsSynchronizedWithCurrentItem="True"/>
<StackPanel Margin="10" VerticalAlignment="Bottom" DataContext="{Binding ElementName=listBox, Path=SelectedItem}">
<Label x:Name="label" Content="{Binding ReleaseDate}" Height="36"/>
<TextBox x:Name="textBox" Height="34" TextWrapping="Wrap" Text="{Binding Genre}" IsReadOnly="True"/>
</StackPanel>
</Grid>
</UserControl>
现在,你可以运行程序了,你在列表框中选择的任何项都会更新相应的 Label 和 Textbox 中的值。
关注点
一旦你掌握了 MVVM 模式的基础知识,你就可以通过学习 `ICommand` 接口或使用像 MVVMLight 这样的外部库来进一步扩展它。
MVVM 模式还有许多其他用途,本文并未讨论。
致谢
老实说,我花了几个月的时间才完全理解 MVVM 模式是如何工作的,而且我无法独立完成,离不开 StackOverflow、CodeProject、Youtube 和 MSDN 论坛上的人们的帮助。所以,在这一部分,我想向所有帮助过我以及其他人的人致以最诚挚的感谢。人数和来源太多,无法一一列举,因此,如果你是花费时间回答我的问题、制作像我这样的教程,甚至制作过视频教程的个人;你们所有人都获得了我最诚挚的感谢,感谢你们帮助了我这样的其他开发人员。