[2011.12.03] MVVM 模式开发Windows Phone程序处理页面导航
我们知道MVVM模式是SL和WP开发一种很重要的模式。
其宗旨是将View和Model分离。可是,这之间还是有少量的问题。比如说我们有时候还是需要一些界面的反馈等等。
一,页面导航事件
首先我们来谈谈页面导航事件。
WP里面页面导航事件的触发用得比较多的是OnNavigatedTo和OnNavigatedFrom。
这两个属于Page类的一个可重载函数。PhoneApplicationPage是继承的Page所以可以重写这两个函数。
如果不是采用MVVM模式的话,我们可以很容易的在这两个方法里面写一些例如处理QueryString的代码。
但是采用MVVM之后,VM里面没发获取导航事件的触发,也没法获取QueryString了。
很多朋友愿意用Messenger或Event之类的机制来处理这个问题,可是我个人还是比较喜欢直观的函数调用的形式。
那么我们怎么在ViewModel里面接收到页面导航的事件呢。
首先,我们建立几个辅助的类别。
一个标志接口:INavigationBase,只是用来继承的。
两个派生接口:
public interface INavigatedViewModel : INavigationBase
{
void OnNavigatedTo(NavigationEventArgs e);
void OnNavigatedFrom(NavigationEventArgs e);
}
public interface INavigatingViewModel : INavigationBase
{
void OnNavigatingFrom(NavigatingCancelEventArgs e);
}
一个用来Hook两个OnNavigated,另一个用来Hook OnNavigatingFrom。
建立一个NavigationController类。在RootFrame新建时将其初始化即可。
初始化时候最好做成Singleton形式。我自己是做成静态属性放在ViewModelLocator里面初始化的。
public class ViewModelLocator
{
private static NavigationController _navController;
public static void Init(PhoneApplicationFrame frame)
{
_navController = new NavigationController(frame);
}
}
public App()
{
// Global handler for uncaught exceptions.
// Note that exceptions thrown by ApplicationBarItem.Click will not get caught here.
UnhandledException += Application_UnhandledException;
// Standard Silverlight initialization
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();
ViewModelLocator.Init(RootFrame);
}
NavigationController类部分代码如下:
private INavigationBase _currentVM;
public NavigationController(PhoneApplicationFrame frame)
{
frame.Navigated += onRootFrameNavigated;
frame.Navigating += onRootFrameNavigating;
frame.NavigationFailed += onRootFrameNavigationFailed;
frame.NavigationStopped += onRootFrameNavigationStopped;
}
private void onRootFrameNavigated(object sender, NavigationEventArgs e)
{
if (_currentVM != null)
{
if (_currentVM is INavigatedViewModel)
{
(_currentVM as INavigatedViewModel).OnNavigatedFrom(e);
}
_currentVM = null;
}
var page = e.Content as PhoneApplicationPage;
if (page != null && page.DataContext is INavigationBase)
{
_currentVM = page.DataContext as INavigationBase;
if (_currentVM is INavigatedViewModel)
{
(_currentVM as INavigatedViewModel).OnNavigatedTo(e);
}
}
}
private void onRootFrameNavigating(object sender, NavigatingCancelEventArgs e)
{
if (_currentVM is INavigatingViewModel)
{
(_currentVM as INavigatingViewModel).OnNavigatingFrom(e);
}
}
这样如果你需要你某个ViewModel接收到页面导航信息,只需将其实现相应的接口即可。例如:
public class PictureVM : ViewModelBase, INavigatedViewModel
{
public void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
}
public void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
}
}
当页面导航发生时,NavigationController会判断当前页面和被导航页面是否需要触发相应的函数。
问题就这样丑陋的解决了。另外,这样VM中相应事件的触发会先于View里面哦,各位看官注意了~