XAML 程序遵循 MVVM 的一些建议

本文来自:Recommendations and best practices for implementing MVVM and XAML/.NET applications

项目的文件(目录)结构

项目的文件结构

  • App.xaml
  • Controls: 重复使用的 UI(没有 ViewModel)
  • Localization: 用于程序本土化的类代码和资源文件
  • Models: Model and domain classes
  • ViewModels: View models classes
    • MainWindowModel.cs
    • MyViewModel.cs
    • Dialogs
      • SelectItemDialogModel.cs
  • Views: Contains the views
    • MainWindow.xaml
    • MyView.xaml
    • Dialogs
      • SelectItemDialog.xaml

View 的名称体是以它的类型(Window、View、Dialog)结尾的;ViewModel 的名称是 View名称 + Model 后缀(例如:MainWindow – MainWindowModel)。

View – ViewModel 原则

View – ViewModel 是一一对应的关系,只有 View 才能访问它的 ViewModel,别的 View 不能访问其它的 ViewModel。

ViewModel 不能直接访问它的 View,只能通过 messenger (或其它 IoC 机制)实现方法 调用。

如果 View 很大(复杂),应该把它拆分成多个 View。

ViewModel 的实例化和赋值

在 XAML 里以根元素 Resources 方式实例化 ViewModel,然后再分配给顶级子元素的 DataContext,像这样:

 <UserControl x:Class="My.Namespace.MySampleView" ...> 
    <UserControl.Resources> 
        <viewModels:MySampleViewModel x:Key="ViewModel" /> 
    </UserControl.Resources> 

    <Grid DataContext="{StaticResource ViewModel}"> 
        ... 
    </Grid> 
</UserControl> 

如果需要在 View 中访问 ViewModel,那么可以在 .xaml.cs 中这样实现:

 protected MySampleViewModel Model 
{
    get { return (MySampleViewModel)Resources["ViewModel"]; } 
}

这样做的好处是:

  • Visual Studio 的编辑器能识别到 Resource 的类型,这样就可以提供 IntelliSense 功能
  • 其它 UI 需要 ViewModel 的内容时,可以很方便通过 Resource 的 key 来访问。

不要在 View 实例化的地方设置 DataContext 属性, 只有 View 自己才能给它的 DataContext 赋值。 如下做法是错的:

<MySubView DataContext="{Binding MySubViewModel}" />

根元素需要绑定 ViewModel 属性怎么办

因为 ViewModel 只有在 Resources 之后才可用,所以我们就无法直接在根元素上使用属性绑定,需要换种方式,像这样:

 <Window ...> 
    <Window.Resources> 
        <viewModels:MyWindowModel x:Key="ViewModel" /> 
    </Window.Resources> 
    <Window.Title> 
        <Binding Source="{StaticResource ViewModel}" Path="Document.Title" /> 
    </Window.Title> 

    ...  

Command 实现

所有 UI 动作都在 ViewModel 中以 Command 实现 。

使用 Dependency Property 来给 View 传参

Dependency Property 这样实现:

public class MySubView
{
    public static readonly DependencyProperty ProjectProperty = DependencyProperty.Register( 
        "Project", typeof(Project), typeof(MySubView),  
        new PropertyMetadata(default(Project), OnProjectChanged)); 

    public Project Project 
    { 
        get { return (Project)GetValue(ProjectProperty); } 
        set { SetValue(ProjectProperty, value); } 
    } 

    private static void OnProjectChanged(DependencyObject obj,  
        DependencyPropertyChangedEventArgs args) 
    { 
        ((MySubView)obj).Model.Project = (Project)args.NewValue; 
    }  

    public ProjectPropertiesViewModel Model 
    { 
        get { return (ProjectPropertiesViewModel)Resources["ViewModel"]; } 
    } 
}

然后,在 xaml 中可以这样传参:

<MySubView Project="{Binding SelectedProject}" />

ViewModel 的生命周期

View 调用 ViewModel 的相关方法:

public MyView() 
{ 
    InitializeComponent(); 
    Model.Initialize();    
    Loaded += delegate { Model.OnLoaded(); }; 
    Unloaded += delegate { Model.OnUnloaded(); }; 
}

ViewModel 实现:

public class MyViewModel  
{ 
    private bool _isLoaded = false;  

    public MyViewModel()
    {
        // TODO: Add your constructor code here
        // The ctor is always called, initialize view model so that it also works in designer
    }

    public void Initialize()
    {
        // TODO: Add your initialization code here 
        // This method is only called when the application is running
    }

    public void OnLoaded() 
    { 
        if (!_isLoaded) 
        { 
            // TODO: Add your loaded code here 
            _isLoaded = true;  
        } 
    } 

    public void OnUnloaded() 
    { 
        if (_isLoaded) 
        { 
            // TODO: Add your cleanup/unloaded code here 
            _isLoaded = false; 
        } 
    }
    ... 

注意,我们在 xaml 中实例化了 ViewModel,这样 ViewModel 的构造函数在 Visual Studio 编辑器中就会被执行,没必要让代码在设计阶段就执行,所以我们独立出了 Initialize 方法。

XAML 的绑定一定要声明类型

这一条很重要:XAML 里的所有绑定(Binding)都要声明类型,通常在 Resources 会声明类型的,但是如果有其它的绑定方式,一定要手动声明绑定的类型:

<Window xmlns:MyNamespace="clr-namespace:MyNamespace"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        ...>
    ... 
    <StackPanel d:DataContext="{d:DesignInstance Type=MyNamespace:MyType}"> 
        ... 
    </StackPanel> 

发表评论

电子邮件地址不会被公开。 必填项已用*标注