- MVVM 教程
- MVVM - 首页
- MVVM – 简介
- MVVM - 优点
- MVVM - 职责
- MVVM - 第一个应用
- MVVM - 关联视图
- MVVM - 关联 ViewModel
- MVVM - WPF 数据绑定
- MVVM - WPF 数据模板
- MVVM - ViewModel 通信
- MVVM - 层次结构和导航
- MVVM - 验证
- MVVM - 依赖注入
- MVVM - 事件
- MVVM - 单元测试
- MVVM - 框架
- MVVM - 面试问题
- MVVM 有用资源
- MVVM - 快速指南
- MVVM - 有用资源
- MVVM - 讨论
MVVM – ViewModel 的关联
在本章中,我们将介绍如何关联 ViewModel。这是上一章的延续,在上一章中我们讨论了 View 优先的构建。现在,第一个构建的下一种形式是一个称为ViewModelLocator的元模式。它是一种伪模式,构建在 MVVM 模式之上。
在 MVVM 中,每个 View都需要与它的 ViewModel 关联。
ViewModelLocator 是一种简单的方法,可以集中代码并进一步解耦视图。
这意味着它不必明确知道 ViewModel 类型以及如何构建它。
有多种不同的方法可以使用 ViewModelLocator,但这里我们使用最类似于 PRISM 框架中的一部分的方法。
ViewModelLocator 提供了一种标准、一致、声明式和松耦合的方式来进行 View 优先的构建,它自动化了将 ViewModel 关联到 View 的过程。下图表示了 ViewModelLocator 的高级流程。
步骤 1 - 确定正在构建的 View 类型。
步骤 2 - 识别该特定 View 类型的 ViewModel。
步骤 3 - 构建该 ViewModel。
步骤 4 - 将 View 的 DataContext 设置为 ViewModel。
为了理解基本概念,让我们看看 ViewModelLocator 的简单示例,继续上一章中的相同示例。如果查看 StudentView.xaml 文件,您会看到我们已静态地关联了 ViewModel。
现在,如以下程序所示,注释这些 XAML 代码,并从代码隐藏中删除代码。
<UserControl x:Class = "MVVMDemo.Views.StudentView" 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:MVVMDemo.Views" xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300"> <!--<UserControl.DataContext> <viewModel:StudentViewModel/> </UserControl.DataContext>--> <Grid> <StackPanel HorizontalAlignment = "Left"> <ItemsControl ItemsSource = "{Binding Path = Students}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation = "Horizontal"> <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" Width = "100" Margin = "3 5 3 5"/> <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" Width = "100" Margin = "0 5 3 5"/> <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" Margin = "0 5 3 5"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Grid> </UserControl>
现在让我们创建一个新的文件夹 VML,并添加一个新的公共类 ViewModelLocator,它将包含一个附加属性(依赖属性)AutoHookedUpViewModel,如下面的代码所示。
public static bool GetAutoHookedUpViewModel(DependencyObject obj) { return (bool)obj.GetValue(AutoHookedUpViewModelProperty); } public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { obj.SetValue(AutoHookedUpViewModelProperty, value); } // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. //This enables animation, styling, binding, etc... public static readonly DependencyProperty AutoHookedUpViewModelProperty = DependencyProperty.RegisterAttached("AutoHookedUpViewModel", typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false, AutoHookedUpViewModelChanged));
现在您可以看到一个基本的附加属性定义。要向属性添加行为,我们需要为此属性添加一个更改事件处理程序,其中包含自动关联 View 的 ViewModel 的过程。执行此操作的代码如下所示:
private static void AutoHookedUpViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (DesignerProperties.GetIsInDesignMode(d)) return; var viewType = d.GetType(); string str = viewType.FullName; str = str.Replace(".Views.", ".ViewModel."); var viewTypeName = str; var viewModelTypeName = viewTypeName + "Model"; var viewModelType = Type.GetType(viewModelTypeName); var viewModel = Activator.CreateInstance(viewModelType); ((FrameworkElement)d).DataContext = viewModel; }
以下是 ViewModelLocator 类的完整实现。
using System; using System.ComponentModel; using System.Windows; namespace MVVMDemo.VML { public static class ViewModelLocator { public static bool GetAutoHookedUpViewModel(DependencyObject obj) { return (bool)obj.GetValue(AutoHookedUpViewModelProperty); } public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { obj.SetValue(AutoHookedUpViewModelProperty, value); } // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. //This enables animation, styling, binding, etc... public static readonly DependencyProperty AutoHookedUpViewModelProperty = DependencyProperty.RegisterAttached("AutoHookedUpViewModel", typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false, AutoHookedUpViewModelChanged)); private static void AutoHookedUpViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (DesignerProperties.GetIsInDesignMode(d)) return; var viewType = d.GetType(); string str = viewType.FullName; str = str.Replace(".Views.", ".ViewModel."); var viewTypeName = str; var viewModelTypeName = viewTypeName + "Model"; var viewModelType = Type.GetType(viewModelTypeName); var viewModel = Activator.CreateInstance(viewModelType); ((FrameworkElement)d).DataContext = viewModel; } } }
首先要做的是添加一个命名空间,以便我们可以在项目的根目录中访问该 ViewModelLocator 类型。然后在路由元素(一种视图类型)上,添加 AutoHookedUpViewModel 属性并将其设置为 true。
xmlns:vml = "clr-namespace:MVVMDemo.VML" vml:ViewModelLocator.AutoHookedUpViewModel = "True"
以下是 StudentView.xaml 文件的完整实现。
<UserControl x:Class = "MVVMDemo.Views.StudentView" 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:MVVMDemo.Views" xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" xmlns:vml = "clr-namespace:MVVMDemo.VML" vml:ViewModelLocator.AutoHookedUpViewModel = "True" mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300"> <!--<UserControl.DataContext> <viewModel:StudentViewModel/> </UserControl.DataContext>--> <Grid> <StackPanel HorizontalAlignment = "Left"> <ItemsControl ItemsSource = "{Binding Path = Students}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation = "Horizontal"> <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" Width = "100" Margin = "3 5 3 5"/> <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" Width = "100" Margin = "0 5 3 5"/> <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" Margin = "0 5 3 5"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Grid> </UserControl>
编译并执行上述代码后,您将看到 ViewModelLocator 正在为该特定 View 关联 ViewModel。
需要注意的关键一点是,视图不再以某种方式耦合到其 ViewModel 的类型或构建方式。所有这些都已移至 ViewModelLocator 内部的中心位置。