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

WPF 中的 MVVM 模式:面向绝对初学者的简单教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (65投票s)

2010 年 11 月 10 日

CDDL

3分钟阅读

viewsIcon

478506

downloadIcon

17654

WPF 中的 MVVM 模式:面向绝对初学者的简单教程。

引言

作为学习 MVVM 模式的一部分,我尝试搜索了许多网站和博客,发现大多数都以复杂的方式解释了该模式。经过一些研究,我破解了 MVVM 模式中最基本的步骤,在这里我试图为绝对初学者编写一个 MVVM 教程。

我认为不需要花费更多的时间或文字来解释 MVVM 的各个部分以及 MVVM 和 WPF 之间的关系。如果你深入了解 WPF,你会意识到 MVVM 是最适合 WPF 的模式(你可能不理解这两者之间的区别)。

作为一种正式的程序,我给出了一个简单的 MVVM 图表和定义

我从两个例子开始本教程:WpfSimple.csprojWpfMvvmTest.csproj

为了简单起见,在第一个项目 (WpfSimple.csproj) 中,我们避免使用 Model 对象(稍后将提供一个带 Model 的示例)。

在示例 WpfSimple 中,View 仅包含一个 Button 并且没有代码隐藏,但按钮点击事件与 ViewModel 松散绑定。ViewViewModel 之间的绑定很容易构建,因为 ViewModel 对象被设置为 ViewDataContext。如果 ViewModel 中的属性值发生变化,这些新值会自动通过数据绑定传播到 View。当用户在 View 中单击按钮时,ViewModel 上的一个命令会执行以执行请求的操作。

View

以下代码片段来自 WpfSimple 应用程序(随教程提供)

<Window x:Class="WpfSimple.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfSimple"
        Title="MainWindow" Height="150" Width="370">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
        <Grid>
        <Button Content="Click" 
                Height="23" 
                HorizontalAlignment="Left" 
                Margin="77,45,0,0" 
                Name="btnClick" 
                VerticalAlignment="Top" 
                Width="203"
                Command="{Binding ButtonCommand}" 
                CommandParameter="Hai" />
    </Grid>
</Window>

这里使用的 ViewModel 类是 MainWindowViewModel,该对象被设置为 View 的 DataContext

ViewModel

这里使用的 ViewModel 类是 MainWindowViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Windows;

namespace WpfSimple
{
    class MainWindowViewModel
    {
        private ICommand m_ButtonCommand;
        public ICommand ButtonCommand
        {
            get
            {
                return m_ButtonCommand;
            }
            set
            {
                m_ButtonCommand = value;
            }
        }

        public MainWindowViewModel()
        {
            ButtonCommand=new RelayCommand(new Action<object>(ShowMessage));
        }

        public void ShowMessage(object obj)
        {
            MessageBox.Show(obj.ToString());
        }
    }
}

你可以在这里看到一个空的code-behind文件。如果你点击按钮,它将提示一个消息框,尽管 Views 中缺少事件处理方法。当用户点击按钮时,应用程序会做出反应并满足用户的请求。这之所以有效,是因为在 UI 中显示的 ButtonCommand 属性上建立了绑定。命令对象充当适配器,可以轻松地从 XAML 中声明的 View 中使用 ViewModel 的功能。

RelayCommand

RelayCommand 是在 ICommand 接口中实现的自定义类。你可以使用任何名称代替 RelayCommand
它的用法如下

private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}

ButtonCommand=new RelayCommand(new Action<object>(ShowMessage));

public void ShowMessage(object obj)
{
// do Something
}

在 View 中使用 ButtonCommand 如下所示

<Button Content="Click" 
Command="{Binding ButtonCommand}" 
CommandParameter="Hai" /> 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace WpfSimple
{
    class RelayCommand : ICommand
    {
        private Action<object> _action;

        public RelayCommand(Action<object> action)
        {
            _action = action;
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            if (parameter != null)
            {
                _action(parameter);
            }
            else
            {
                _action("Hello World");
            }
        }

        #endregion
    }
}

我认为这是一个简单且标准的方法。另一种常见的方法是使用嵌套的内部类。

接下来,我将通过一个带有 Model 对象的示例来讨论 MVVM。我还将简要介绍一下 INotifyPropertyChanged 接口。

INotifyPropertyChanged

INotifyPropertyChanged 接口用于在属性值发生更改时通知客户端(通常是绑定客户端)。INotifyPropertyChanged 接口包含一个名为 PropertyChanged 的事件。每当 ViewModel / Model 对象上的属性具有新值时,它都可以引发 PropertyChanged 事件以通知 WPF 绑定系统新值。在收到该通知后,绑定系统查询该属性,并且 UI 元素上的绑定属性将接收新值。

从示例 WpfMvvmTest 项目中,我将演示以下代码

public class Product:INotifyPropertyChanged
{
    private int m_ID;
    private string m_Name;
    private double m_Price;

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    public int ID
    {
        get
        {
            return m_ID;
        }
        set
        {
            m_ID = value;
            OnPropertyChanged("ID");
        }
    }
    public string Name
    {
        get
        {
            return m_Name;
        }
        set
        {
            m_Name = value;
            OnPropertyChanged("Name");
        }
    }
    public double Price
    {
        get
        {
            return m_Price;
        }
        set
        {
            m_Price = value;
            OnPropertyChanged("Price");
        }
    }
}

这里展示了另一种将 ViewModel 对象绑定为 ViewDataContext 的方法

public partial class App: Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        WpfMvvmTest.MainWindow window = new MainWindow();
        ProductViewModel VM = new ProductViewModel();
        window.DataContext = VM;
        window.Show();
    }
}

在以下代码片段中,我们使用内部类方法来实现 ICommand 接口;即,在 ViewModel 类中创建一个 private 嵌套类,以便命令可以访问其包含的 ViewModelprivate 成员,并且不会污染命名空间。

这种方法的缺点是,对于 ViewModel 公开的每个命令,都需要创建一个实现 ICommand 的嵌套类,这将增加 ViewModel 类的大小。

class ProductViewModel 
{
    private IList<Product> m_Products;
    public ProductViewModel()
    {
        m_Products = new List<Product>
        {
            new Product {ID=1, Name ="Pro1", Price=10},
            new Product{ID=2, Name="BAse2", Price=12}
        };
    }
    public IList<Product> Products
    {
        get
        {
            return m_Products;
        }
        set
        {
            m_Products = value;
        }
    }
    private ICommand mUpdater;
    public ICommand UpdateCommand
    {
        get
        {
            if (mUpdater == null)
                mUpdater = new Updater();
            return mUpdater;
        }
        set
        {
            mUpdater = value;
        }
    }
    private class Updater : ICommand
    {
        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return true;
        }
        public event EventHandler CanExecuteChanged;
        public void Execute(object parameter)
        {

        }
        #endregion
    }
}

从以下 View XAML,你可以了解如何将对象集合绑定到列表控件。

<Window x:Class="WpfMvvmTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid Height="314">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ListView Name="ListViewEmployeeDetails" Grid.Row="1" 
               Margin="4,109,12,23"  ItemsSource="{Binding Products}"  >
            <ListView.View>
                <GridView x:Name="grdTest">
                    <GridViewColumn Header="ID" 
                          DisplayMemberBinding="{Binding ID}"  Width="100"/>
                    <GridViewColumn Header="Name" 
                          DisplayMemberBinding="{Binding Name}"  Width="100" />
                    <GridViewColumn Header="Price" 
                          DisplayMemberBinding="{Binding Price}" Width="100" />
                </GridView>
            </ListView.View>
        </ListView>
        <TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" 
           Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" 
           Text="{Binding ElementName=ListViewEmployeeDetails,Path=SelectedItem.ID}" />
        <TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" 
           Margin="80,35,0,0" Name="txtName" VerticalAlignment="Top" Width="178" 
           Text="{Binding ElementName=ListViewEmployeeDetails,Path=SelectedItem.Name}" />
        <TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,61,0,0" 
           Name="txtPrice" VerticalAlignment="Top" Width="178" 
           Text="{Binding ElementName=ListViewEmployeeDetails,Path=SelectedItem.Price}" />
        <Label Content="ID" Grid.Row="1" HorizontalAlignment="Left" 
               Margin="12,12,0,274" Name="label1" />
        <Label Content="Price" Grid.Row="1" Height="28" 
           HorizontalAlignment="Left" Margin="12,59,0,0" 
           Name="label2" VerticalAlignment="Top" />
        <Label Content="Name" Grid.Row="1" Height="28" 
           HorizontalAlignment="Left" Margin="12,35,0,0" 
           Name="label3" VerticalAlignment="Top" />
        <Button Content="Update" Grid.Row="1" Height="23" 
           HorizontalAlignment="Left" Margin="310,40,0,0" Name="btnUpdate" 
           VerticalAlignment="Top" Width="141"
           Command="{Binding Path=UpdateCommand}"
                />
    </Grid>
</Window>

我希望你能够从这篇文章中对 MVVM 有一些了解。

© . All rights reserved.