构建MVVM应用程序时,通常会将复杂的信息屏幕分解为一组父视图和子视图,其中子视图包含在面板或容器控件的父视图中,并形成自己使用的层次结构.
分解复杂视图后,它并不意味着您将每个子内容分成自己的XAML文件必然需要成为MVVM视图.
内容块只提供了向屏幕呈现内容的结构,并且不支持任何输入或操作.该内容的用户.
它可能不需要单独的ViewModel,但它可能只是一个基于父类ViewModel公开的属性呈现的块XAML.
最后,如果你有一个Views和ViewModel的层次结构,那么父ViewModel可以成为一个通信中心,这样每个子ViewModel都可以保持不变尽可能从其他子ViewModels和他们的父母那里获得.
让我们看一下我们将在不同视图之间定义简单层次结构的示例.创建一个新的WPF应用程序项目 MVVMHierarchiesDemo
第1步 : 将三个文件夹(Model,ViewModel和Views)添加到项目中.
第2步 : 在Model文件夹中添加Customer和Order类,在Views文件夹中添加CustomerListView和OrderView,在ViewModel文件夹中添加CustomerListViewModel和OrderViewModel,如下图所示.
第3步 : 在CustomerListView和OrderView中添加文本块.这是CustomerListView.xaml文件.
<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView" 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:MVVMHierarchiesDemo.Views" mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300"> <Grid> <TextBlock Text = "Customer List View"/> </Grid> </UserControl>
以下是OrderView.xaml文件.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView" 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:MVVMHierarchiesDemo.Views" mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300"> <Grid> <TextBlock Text = "Order View"/> </Grid> </UserControl>
现在我们需要一些东西来托管这些视图,并且在我们的MainWindow中是一个好地方,因为它是一个简单的应用程序.我们需要一个容器控件,我们可以放置视图并以导航方式切换它们.为此,我们需要在MainWindow.xaml文件中添加ContentControl,我们将使用其content属性并将其绑定到ViewModel引用.
现在为每个视图定义数据模板在资源字典中.以下是MainWindow.xaml文件.请注意每个数据模板如何将数据类型(ViewModel类型)映射到相应的View.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:MVVMHierarchiesDemo" xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <Window.DataContext> <local:MainWindowViewModel/> </Window.DataContext> <Window.Resources> <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}"> <views:CustomerListView/> </DataTemplate> <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}"> <views:OrderView/> </DataTemplate> </Window.Resources> <Grid> <ContentControl Content = "{Binding CurrentView}"/> </Grid> </Window>
无论何时将当前视图模型设置为CustomerListViewModel的实例,它都会在ViewModel连接时呈现出CustomerListView.这是一个订单ViewModel,它将呈现OrderView等等.
我们现在需要一个具有CurrentViewModel属性和一些逻辑并命令能够切换当前引用的ViewModel属性中的ViewModel.
让我们为这个MainWindow创建一个名为MainWindowViewModel的ViewModel.我们可以从XAML创建一个ViewModel实例,并使用它来设置窗口的DataContext属性.为此,我们需要创建一个基类来封装我们的ViewModel的INotifyPropertyChanged实现.
这个类背后的主要思想是封装INotifyPropertyChanged实现并为派生提供辅助方法类,以便他们可以轻松触发相应的通知.以下是BindableBase类的实现.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace MVVMHierarchiesDemo { class BindableBase : INotifyPropertyChanged { protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null) { if (object.Equals(member, val)) return; member = val; PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged = delegate { }; } }
现在是时候实际开始使用CurrentViewModel属性进行一些视图切换了.我们只需要一些方法来驱动这个属性的设置.我们将做到这一点,以便最终用户可以命令进入客户列表或订单视图.首先在项目中添加一个新类,它将实现ICommand接口.以下是ICommand接口的实现.
using System; using System.Windows.Input; namespace MVVMHierarchiesDemo { public class MyICommand<T> : ICommand { Action<T> _TargetExecuteMethod; Func<T, bool> _TargetCanExecuteMethod; public MyICommand(Action<T> executeMethod) { _TargetExecuteMethod = executeMethod; } public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) { _TargetExecuteMethod = executeMethod; _TargetCanExecuteMethod = canExecuteMethod; } public void RaiseCanExecuteChanged() { CanExecuteChanged(this, EventArgs.Empty); } #region ICommand Members bool ICommand.CanExecute(object parameter) { if (_TargetCanExecuteMethod != null) { T tparm = (T)parameter; return _TargetCanExecuteMethod(tparm); } if (_TargetExecuteMethod != null) { return true; } return false; } // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command // Prism commands solve this in their implementation public event EventHandler CanExecuteChanged = delegate { }; void ICommand.Execute(object parameter) { if (_TargetExecuteMethod != null) { _TargetExecuteMethod((T)parameter); } } #endregion } }
我们现在需要为ViewModel设置一些顶级导航,并且该切换的逻辑应该属于MainWindowViewModel.为此,我们将使用一个名为on navigate的方法,它接受一个字符串目标并返回CurrentViewModel属性.
private void OnNav(string destination) { switch (destination) { case "orders": CurrentViewModel = orderViewModelModel; break; case "customers": default: CurrentViewModel = custListViewModel; break; } }
要导航这些不同的视图,我们需要在MainWindow.xaml文件中添加两个按钮.以下是完整的XAML文件实现.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local = "clr-namespace:MVVMHierarchiesDemo" xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "525"> <Window.DataContext> <local:MainWindowViewModel/> </Window.DataContext> <Window.Resources> <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}"> <views:CustomerListView/> </DataTemplate> <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}"> <views:OrderView/> </DataTemplate> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height = "Auto" /> <RowDefinition Height = "*" /> </Grid.RowDefinitions> <Grid x:Name = "NavBar"> <Grid.ColumnDefinitions> <ColumnDefinition Width = "*" /> <ColumnDefinition Width = "*" /> <ColumnDefinition Width = "*" /> </Grid.ColumnDefinitions> <Button Content = "Customers" Command = "{Binding NavCommand}" CommandParameter = "customers" Grid.Column = "0" /> <Button Content = "Order" Command = "{Binding NavCommand}" CommandParameter = "orders" Grid.Column = "2" /> </Grid> <Grid x:Name = "MainContent" Grid.Row = "1"> <ContentControl Content = "{Binding CurrentViewModel}" /> </Grid> </Grid> </Window>
以下是完整的MainWindowViewModel实现.
using MVVMHierarchiesDemo.ViewModel; using MVVMHierarchiesDemo.Views; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVVMHierarchiesDemo { class MainWindowViewModel : BindableBase { public MainWindowViewModel() { NavCommand = new MyICommand<string>(OnNav); } private CustomerListViewModel custListViewModel = new CustomerListViewModel(); private OrderViewModel orderViewModelModel = new OrderViewModel(); private BindableBase _CurrentViewModel; public BindableBase CurrentViewModel { get {return _CurrentViewModel;} set {SetProperty(ref _CurrentViewModel, value);} } public MyICommand<string> NavCommand { get; private set; } private void OnNav(string destination) { switch (destination) { case "orders": CurrentViewModel = orderViewModelModel; break; case "customers": default: CurrentViewModel = custListViewModel; break; } } } }
从BindableBase类派生所有ViewModel.编译并执行上述代码时,您将看到以下输出.
如您所见,我们在MainWindow上只添加了两个按钮和一个CurrentViewModel.如果单击任何按钮,则会导航到该特定视图.让我们点击Customers按钮,你会看到显示了CustomerListView.
我们建议您逐步执行上述示例,以便更好地理解.