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

最简单的 MVVM

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (30投票s)

2013年11月9日

CPOL

5分钟阅读

viewsIcon

59705

downloadIcon

1574

用通俗的语言介绍主要构建块,以便初学者更容易理解。

背景

接续我上一篇关于 MVVM 的原因和时机 的文章,这次,我将介绍如何创建一个复杂性最小的简单 MVVM 应用程序。我写这篇文章是为了那些想快速了解 MVVM 如何工作的人。它不会做什么花哨的事情,只使用一些基本的数据绑定和命令。这篇文章将对所有正在寻找如何将 View 连接到 ViewModel 以及命令发挥作用的快速示例的人有所帮助。

今天,如果你上网搜索,你会发现成千上万的文章讨论 MVVM 是什么以及它的主要构建块是什么。所以,我又要重复同样疯狂的事情了。但肯定的是,我会用通俗易懂的语言为你简要介绍主要构建块,以便初学者更容易理解。兴奋吗???

构建块的通俗介绍

那么,让我们快速开始介绍 MVVM 的三个主要层。

  • Model - Model 包含类似于现实世界实体的类,例如 ProductCustomerStudentInvoice 等。它封装了业务逻辑和数据。请注意,Model 类不直接引用视图,或者换句话说,Model 类对视图没有依赖,无论它们如何实现。从技术上讲,Model 类与封装数据访问的服务或存储库结合使用。
  • ViewModel - ViewModel 类的主要目的是将数据暴露给视图。它包括表示逻辑,并且可以独立于 Model 进行测试。与 Model 类似,ViewModel 从不引用 View ,但它公开属性和命令以绑定 View 数据。本质上,ViewModel 充当 View Model 之间的协调器。
  • View - View 仅处理屏幕上看到的外观。作为最佳实践,View 类中不应包含需要单元测试的逻辑代码。简单来说,Views 仅用于 UI 可视行为。

我希望到目前为止,你可能已经了解了 ViewViewModel Model 的职责。好了,在开始编码之前,我想再简要介绍一些其他需要处理的点,以便更好地、更清晰地实现 MVVM。

任何 MVVM 应用的重要组成部分

有两个主要组件在实现任何 MVVM 应用时都起着至关重要的作用。第一个是 ViewModel 的基类,第二个是 ICommand 接口。

基类 - 每个 ViewModel 都需要一些公共的东西。因此,为了遵循良好的设计,这些公共的东西应该放在一个基类中,然后这个类可以被各种 ViewModels 派生。现在的问题是,这个基类应该包含什么?

这个基类包含 INotifyPropertyChanged 接口的实现。正如我之前提到的,ViewModel 不能直接引用 View 。因此,这个接口连接了两者之间的差距,并允许消息传递到 ViewINotifyPropertyChanged 的示例实现是

public class ViewModelBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string prop)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }
} 

请注意,上面的片段是基础的。因此,如果需要,可以以各种方式扩展它,例如添加各种验证,如属性名称检查等。

好的,让我们转向 ICommand 接口。

ICommand 接口 - 这个接口提供了非常有用的方法,如 ExecuteCanExecute,它们对命令部分提供了完全的控制。View 使用命令与 ViewModel 交互。示例代码是

public class DelegateCommand : ICommand
{
     private Action<object> _action;
     public DelegateCommand(Action<object> action)
     {
         _action = action;
     }

    #region ICommand Members
    public event EventHandler CanExecuteChanged;
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _action(parameter);
    }

    #endregion
}

我希望这些知识足够用于一个简单的 MVVM 示例。

示例场景

这是 Student 实体的示例,为了简单起见,我只取学生的 FirstName 。学生列表将显示在屏幕上。如果用户从列表中选择学生姓名并单击给定的按钮,那么选定的姓名将显示在下面的 textbox 中。是不是很简单???

模型

正如我所说,我的模型类在这里非常简单,只有 2 行代码

public class Student
{
    public string FirstName { get; set; }
}

ViewModel

这个类比我们的 Model 复杂一些。首先让我们看一下下面的代码片段,然后我将详细介绍它

public class StudentsViewModel:ViewModelBase
{    
    public ObservableCollection<Student> StudentList { get; set; }
    public string SelectedStudent { get; set; }

    private ICommand _updateStudentNameCommand;
    public ICommand UpdateStudentNameCommand
    {
        get { return _updateStudentNameCommand; }
        set { _updateStudentNameCommand = value; }
    }       
    private string _selectedName;
    public string SelectedName 
    {
        get { return _selectedName; }
        set
        {
            if (_selectedName != value)
            {
                _selectedName = value;
                RaisePropertyChanged("SelectedName");
            }
        }
    }
    
    public StudentViewModel()
    {
        UpdateStudentNameCommand = new DelegateCommand(new Action<object>(SelectedStudentDetails));
        StudentList = new ObservableCollection<Model.Student> 
        { 
                new Student { FirstName = "Bruce" },
                new Student { FirstName = "Harry" },
                new Student { FirstName = "Stuart" },
                new Student { FirstName = "Robert" }
        };           
      }


    public void SelectedStudentDetails(object parameter)
    {
        if (parameter != null)
            SelectedName = (parameter as SimplestMVVM.Model.Student).FirstName;
    }
}

你可能已经注意到我们继承了 ViewModelBase 类,它提供了 INotifyPropertyChanged 的实现。在这里,我公开了 FirstNameStudentList UpdateDetailsCommand 等属性,视图可以绑定到这些属性。

好了,让我们一项一项来。

  • FirstName - 这个属性用于绑定我 ListView 的单个项目
  • StudentList - 这个属性包含名字列表,并设置为我的 ListView 的 ItemSource
  • UpdateDetailsCommand - 这个属性使用 DelegateCommand 类返回一个 ICommand。它可以绑定到任何东西,如按钮按下或键盘按下。我将此命令绑定到我的 UpdateStudentDetails 方法,在按钮按下时更新文本。

关于 ViewModel 的内容就这些。现在我们可以继续进行 UI 绘制。

视图

至于视图,代码隐藏是完全空的

public MainWindow()
{
    InitializeComponent();
}

大多数与 UI 相关的代码都放在 XAML 文件本身中

<Window x:Class="SimplestMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SimplestMVVM.ViewModel"
        Title="MVVM" Height="300" Width="300">
    <Window.DataContext>
        <local:StudentsViewModel x:Name="ViewModel"/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.878*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
         </Grid.ColumnDefinitions>
                <StackPanel Grid.RowSpan="2">
        <ListView Name="ListViewStudentDetails" 
               Grid.Row="2" ItemsSource="{Binding StudentList}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" 
                         DisplayMemberBinding="{Binding FirstName}"/>
                </GridView>
            </ListView.View> 
         </ListView>
        <Button Command="{Binding UpdateStudentNameCommand}" 
          CommandParameter="{Binding ElementName=ListViewStudentDetails,Path=SelectedItem}"  
          Content="Display selected student"/>
        <TextBlock FontWeight="Bold" Text="Selected student is: ">
          <Run Text="{Binding SelectedName, Mode=TwoWay}"/></TextBlock>
        </StackPanel>
    </Grid>
</Window>

如果你仔细查看代码,你将清楚地了解我在 ViewModel 中提到的属性。

这就是最简单的 MVVM 示例。总共只有很少的代码。是不是很好???

未来计划

在我的下一篇文章中,我将更详细地讨论 MVVM 的不同方面。

相关文章

© . All rights reserved.