Navigation可以很方便的在页面间进行切换,但是在MVVM模式下,使用Naviation会有一个问题,切换的逻辑需要在ViewModel层完成,但是Navigation需要知道页面的实例或者Uri才能进行切换,那我们如何在ViewModel与UI分离的情况下,用Navigation完成页面的切换呢?
假如有一个程序如下所示,点击Switch之后会从Summary
Page切换到另一个页面Detail
Page:
在MVVM中,我们需要有三个ViewModel,一个是SummaryViewModel对应SummaryPage,
一个是DetailViewModel对应DetailPage,再加上一个ControlViewModel负责通过改变CurrentPageViewModel来实现逻辑数据的切换并将其反应到UI,如下所示:
public class SummaryViewModel
{
public ObservableCollection<SummaryModel> Summaries { get; set; }public SummaryViewModel()
{
Summaries = new ObservableCollection<SummaryModel>();
}
}
public class DetailViewModel
{
public ObservableCollection<DetailModel> Details { get; set; }public DetailViewModel()
{
Details = new ObservableCollection<DetailModel>();
}
}
public class NavigationControlViewModelBase : ViewModelBase
{
private object currentPageViewModel;
public object CurrentPageViewModel
{
get
{
return currentPageViewModel;
}
set
{
currentPageViewModel = value;
RaisePropertyChanged(() => CurrentPageViewModel);
}
}
}public class ControlViewModel : NavigationControlViewModelBase
{
private SummaryViewModel summary;
public SummaryViewModel Summary { get { return summary; } set { summary = value; RaisePropertyChanged(() => Summary); } }private DetailViewModel detail;
public DetailViewModel Detail { get { return detail; } set { detail = value; RaisePropertyChanged(() => Detail); } }public ControlViewModel()
{
SwitchCommand = new RelayCommand(Switch);
Summary = new SummaryViewModel();
Detail = new DetailViewModel();
CurrentPageViewModel = Summary;GenerateData();
}public ICommand SwitchCommand {get;set;}
private void Switch()
{
if (CurrentPageViewModel == Summary)
{
CurrentPageViewModel = Detail;
}
else
{
CurrentPageViewModel = Summary;
}
}private void GenerateData()
{
var ran = new Random();
Summary.Summaries.Clear();
Detail.Details.Clear();for (int i = 0; i < 100; i++)
{
Summary.Summaries.Add(new SummaryModel() { ID = ran.Next(0, 100) });
}for (int i = 0; i < 100; i++)
{
Detail.Details.Add(
new DetailModel()
{
ID = i,
Data1 = Guid.NewGuid().ToString().Substring(0, 4),
Data2 = Guid.NewGuid().ToString().Substring(0, 4),
Data3 = Guid.NewGuid().ToString().Substring(0, 4),
Data4 = Guid.NewGuid().ToString().Substring(0, 4),
Data5 = Guid.NewGuid().ToString().Substring(0, 4),
});
}
}
}
添加一个类NavigationControlFrame继承Frame负责控制Navigation,
在这个类里有一个依赖属性CurrentPageObject,在XAML中会将它与ControlViewModel的CurrentPageViewModel绑定,CurrentPageViewMdoel改变时,触发OnCurrentPageObjectChanged。通过XAML里定义的ViewModel类型和Page
Uri对应关系找到相应的页面进行切换:
class NavigationControlFrame : Frame
{
public NavigationControlFrame()
{
Navigated += navigationFrame_Navigated;
}public static readonly DependencyProperty CurrentPageObjectProperty =
DependencyProperty.Register("CurrentPageObject", typeof(object), typeof(NavigationControlFrame), new PropertyMetadata(default(object), OnCurrentPageObjectChanged));private static void OnCurrentPageObjectChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var navigationFrame = (NavigationControlFrame)dependencyObject;
var newValue = dependencyPropertyChangedEventArgs.NewValue;if (newValue == null)
{
navigationFrame.Navigate(null);
return;
}var pageUri = (string)navigationFrame.TryFindResource(newValue.GetType());
navigationFrame.Navigate(new Uri(pageUri, UriKind.Relative), newValue);
}static void navigationFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
if (e.ExtraData != null)
{
var control = e.Content as Page;
control.DataContext = e.ExtraData;
}
}public object CurrentPageObject
{
get { return GetValue(CurrentPageObjectProperty); }
set { SetValue(CurrentPageObjectProperty, value); }
}
}
在XAML中在Resource中定义ViewModel与Page
Uri的对应关系,添加NavigtaionControlFrame,并将CurrentPageObject绑定到CurrentPageViewModel,这样ControlViewModel中的CurrentPageViewModel变化时,对应的页面也会进行切换:
<Window x:Class="NavigationApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:navigationApp="clr-namespace:NavigationApp"
xmlns:viewModels="clr-namespace:ViewModels;assembly=ViewModels"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="600" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.Resources>
<viewModels:ControlViewModel x:Key="Cvm1"></viewModels:ControlViewModel>
<sys:String x:Key="{x:Type viewModels:DetailViewModel}">/DetailPage.xaml</sys:String>
<sys:String x:Key="{x:Type viewModels:SummaryViewModel}">/SummaryPage.xaml</sys:String>
</Grid.Resources><StackPanel Grid.Row="0" Grid.Column="0" DataContext="{StaticResource Cvm1}" Background="LightGreen">
<navigationApp:NavigationControlFrame CurrentPageObject="{Binding CurrentPageViewModel}">
</navigationApp:NavigationControlFrame>
<Button Content="Switch" Command="{Binding SwitchCommand}"></Button>
</StackPanel>
</Grid>
</Window>
WPF: 在MVVM中使用Navigtaion