65.9K
CodeProject 正在变化。 阅读更多。
Home

MVVM 模式的基本大纲和示例

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2015年1月5日

CPOL

5分钟阅读

viewsIcon

20357

downloadIcon

216

MVVM 模式的入门级示例,解释了将程序的图形界面与其业务逻辑分离的基本方法。

引言

在本文中,我们将探讨 MVVM 模式(模型-视图-视图模型)的一些基本概念,并尽可能坚持其范式,即大幅减少(甚至完全消除)控件的后端代码和图形。我们将通过 XAML 和一些为此目的准备的类以及数据表示来实现这一点。

MVVM 模式旨在将应用程序的图形部分与其业务逻辑分开。因此,其基本范式是通过开发人员的努力,在视图(即程序中所有可追溯到其 UI 的部分)和模型(或程序逻辑、其流程,独立于图形上下文)之间引入一个中间层。此定义还包括我们希望生成图形表示的数据管理。我将更注重清晰度而不是过多的技术性,力求做到尽可能简单。

第一个示例

在本节中,我们将看到 MVVM 模式的两个主要概念。首先,我们必须简要了解 DataContext 和 Binding 的概念。简而言之,DataContext 是元素的源或起点,我们可以在此基础上使用 Binding,Binding 是特定属性的值与视觉控件之间的链接。


例如,假设我们有一个 WPF 窗口,其中有一个 TextBox,我们希望在该 TextBox 中显示窗口的标题。其次,我们希望通过修改 TextBox 的内容来修改窗口的标题。换句话说,我们希望以双向方式绑定这两个控件。因此,我们需要告诉 TextBox 它的 DataContext 是窗口,并且要在 Text 属性上执行的 Binding 必须位于窗口的 Title 属性上。在我们的窗口的 XAML 中,我们可以通过以下代码实现这一点:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow"
    Title="MainWindow" Height="111.194" Width="295.149">
     
    <TextBox Name="TB1" Text="{Binding Title, ElementName=MainWindow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>
</Window>

简而言之,我们为窗口分配了一个 Name(示例中为 MainWindow)。之后,我们将 TextBox 的 Text 属性绑定到属于 MainWindow 的 Title 属性(在这种情况下,是 TextBox 的 DataContext),并采用双向模式(TwoWay),将属性修改指示为将启动对应更新操作(PropertyChanged)的事件。有关 Binding 操作语法,请参阅 WPF 数据绑定基础示例

运行这个小示例,我们将注意到窗口的标题会根据 TextBox 中的输入而修改。同样,如果我们修改代码以干预窗口的标题,我们将看到 TextBox 内容的更新。

绑定数据模型

假设我们需要管理与我们将在其中显示它的上下文相关的外部类的绑定。下面是一个类示例,用于假设的产品表示,具有产品代码和描述等属性。

Public Class ItemData
    Public _code As String
    Public _des As String
 
    Public Property Code As String
        Get
            Return _code
        End Get
        Set(value As String)
            _code = value
        End Set
    End Property
 
    Public Property Description As String
        Get
            Return _des
        End Get
        Set(value As String)
            _des = value
        End Set
    End Property
 
    Public Sub New(ByVal code As String, ByVal des As String)
        _code = code
        _des = des
    End Sub
End Class

作为一个可以定义新产品和实体的类,并配备一个初始化其基本属性的构造函数,该类不能直接用作 DataContext,除非它被引用在一个变量中。这意味着如果我们希望执行绑定(例如 Code 属性的绑定),就必须在代码隐藏中进行操作,并且仅在我们成功初始化 ItemData 类型变量后,才将 TextBox 的 DataContext 指定为该变量。

例如,如果我们决定管理窗口的 Loaded 事件,我们可以这样写:

Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs)
  Dim item As New ItemData("PRDCODE01", "TEST PRODUCT")
  TB1.DataContext = item
End Sub

相应的 XAML 将是:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation [This link is external to TechNet Wiki. It will open in a new window.] "
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml [This link is external to TechNet Wiki. It will open in a new window.] "
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow" Loaded="MainWindow_Loaded"
    Title="MainWindow" Height="111.194" Width="295.149">
     
    <TextBox Name="TB1" Text="{Binding Code}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>   
</Window>

在这里,我们将在 TextBox 的 Text 属性中指示一种比前面更简洁的语法。除了我们这里没有管理数据双向性之外,请注意仅存在 Code 属性,而没有提及它必须从中派生的元素(或 DataContext)。这是因为 DataContext 是在代码端指定的,初始化一个 ItemData 类型变量,然后将该新变量指定为 TextBox 的数据上下文。运行此示例,我们将注意到 TextBox 显示的值等于 PR_CODE_01,即我们用于初始化 ItemData 的 Code 属性的字符串。

正如开头所述,这种方法——尽管有效——但并未完全符合 MVVM 范式。在上面的示例中,我们有视图(我们的窗口)和模型(ItemData 变量)。在窗口的代码隐藏中,我们创建了对模型的引用,从而在两者之间产生了相互依赖:如果我们从 Loaded 事件中删除代码,TextBox 绑定自然会停止工作。为了使这两个实体保持分离,必须引入一个中间层,即 ViewModel:这将是一个类,我们将通过它将模型暴露给视图,使这两个层完全相互独立。让我们看看如何做到这一点。

一个简单的 ViewModel

ViewModel 封装了一个模型,公开了视图可以访问底层数据的所有有用属性。通常,它实现 INotifyPropertyChanged [此链接是 TechNet Wiki 的外部链接。它将在新窗口中打开] 接口,该接口将用作一个事件,用于跟踪特定属性的所有更改。在我们的例子中,假设我们想创建一个尽可能小的 ViewModel,我们可以写一个类似以下的类:

Imports System.ComponentModel
Public Class ItemDataView
    Implements INotifyPropertyChanged
 
    Dim item As New ItemData("PR_CODE_01", "TEST PRODUCT")
 
    Public Property Code
        Get
            Return item.Code
        End Get
        Set(value)
            If Not (item.Code = value) Then
                item.Code = value
                NotifyPropertyChanged("Code")
            End If
        End Set
    End Property
 
    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged
 
    Private Sub NotifyPropertyChanged(Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

此类初始化一个 ItemData,创建其新实例,并公开其 Code 属性。同时,它允许修改此属性,通过调用 PropertyChanged 事件来通知已发生的更改。

为了使此 ItemDataView 在 MainWindow 的整个上下文中可见并可用,我们可以在窗口的 XAML 中将 DataContext 指定为所有包含控件的全局数据上下文。TextBox 的 Binding 属性将继续是 ViewModel 公开的 Code。

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow"
    Title="MainWindow" Height="111.194" Width="295.149">
     
    <Window.DataContext>
        <local:ItemDataView x:Name="MyItemView"/>
    </Window.DataContext>
     
    <TextBox Name="TB1" Text="{Binding Code}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>   
</Window>

或者,如果我们希望指定特定的 TextBox DataContext,我们可以这样做:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow" Title="MainWindow" Height="111.194" Width="295.149">
        
    <TextBox Name="TB1" DataContext="{Binding Source=ItemDataView}" Text="{Binding Code}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>   
</Window>

在这两种情况下,运行时我们将看到 TextBox 的 Text 属性的值为 PR_CODE_01,该值是从公开的 ItemDataView 的 ItemData 中获取的。

历史

  • 2015-01-05:CodeProject 首发
© . All rights reserved.