上一篇是讲messager通信机制下的导航,但这种导航方式有诸多问题:
首先需要注册在IOC容器中的VM必须是Singleton模式,因为假如不是单例则每一次生成vm,每一次都会去消息列表中注册一个观察者,这样会导致当出现导航动作的时候,这个VM下的Navigated方法多次执行,你并不知道这个vm应该什么时间点去注销消息列表。
第二点是由于是单例模式下的vm,所以每次的跳出都要执行cleanup操作来重置当前view的数据,会带来一些不必要的麻烦,就好像C#的垃圾回收要你自己做一样。
第三点是有一些情况会出现这个view自跳转本身的view,就像一个ProfileView(个人主页)中会有好友列表,而这个好友列表又要调转到另一个ID的ProfileView,因为前一个view 跳出时已经清空了数据,在新view中重新生成数据,这就导致了返回的时候又要重新的去请求后台并渲染UI,除非你自己维护一个cache来储存这些数据,但是cache的维护导致应用的复杂度提高而且cache何时被合适的销毁又是一个问题。
所以我们对于有些像detailview的view就要采用非单例的模式,每一次的跳转都会生成一个新的vm,而垃圾回收让C#的runtime自己去执行。
好,先从ViewModelLocator开始入手。每次注入vm都采用全新的vm对象。
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This non-static member is needed for data binding purposes.")] public DetailViewModel DetailPage { get { return new DetailViewModel(); } }
属性每一次不会去IOC容器中取单例,而是new一个新的对象,(IOC的完整代码可以看前一篇博文http://leyteris.iteye.com/blog/1539608)
好,这下每次注入的都是新的对象,但是问题来了,要是像上面说的这样每个vm每次都去注册一个消息观察者,一会导致这些vm得不到垃圾回收,二是导致方法重复执行。
我们要看来要放弃这种以消息通信机制为基础的导航。
重新写一个接口取名为NavigationViewModel
public interface INavigationBase { }
public interface INavigatedViewModel : INavigationBase { void OnNavigatedTo(NavigationEventArgs e); void OnNavigatedFrom(NavigationEventArgs e); }
这个接口是要有导航动作的vm实现的。
现在我们改一下控制器:
public class NavigationController { private INavigationBase currentVM; private static PhoneApplicationFrame frame; public NavigationController() { } public NavigationController(PhoneApplicationFrame frame) { frame.Navigated += onRootFrameNavigated; frame.Navigating += onRootFrameNavigating; } 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); } } private static void GetPhoneFrameRoot() { if (frame == null) { frame = Application.Current.RootVisual as PhoneApplicationFrame; if (frame == null) { throw new Exception("获取 ApplicationRootVisual 失败!"); } } } public static void NavigationTo(string url) { if (frame == null) GetPhoneFrameRoot(); if (frame != null) { var pageUri = new Uri(url, UriKind.Relative); frame.Navigate(pageUri); } } public static void NavigationTo(Uri pageUri) { if (frame == null) GetPhoneFrameRoot(); if (frame != null) { frame.Navigate(pageUri); } } }
再在定位器中写一个初始化这个控制器的静态方法;
private static NavigationController navController; public static void InitNavigationController(PhoneApplicationFrame frame) { navController = new NavigationController(frame); }
在app.xaml.cs 中初始化,将RootFrame传入
ViewModelLocator.InitNavigationController(RootFrame);
最后直接在vm中实现NavigationViewModel即可:
public class DetailViewModel : BaseViewModel, INavigatedViewModel //, INavigation { public DetailViewModel() { //NavigationHelper.NavigatedMsgReg(this); } public void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { Uri uri = e.Uri; if (e.NavigationMode == System.Windows.Navigation.NavigationMode.Back) return; this.userId = NavigationHelper.GetQueryString(uri, "uid"); this.postId = NavigationHelper.GetQueryString(uri, "id"); //this.Cursor = 0L; this.LoadDetail(); } public void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { } }
我们可以取消任何的reset操作,现在已经不是单例了。
下面是导航代码
NavigationController.NavigationTo(String.Format("/View/ProfilePage.xaml?uid={0}", this.PostItem.Publisher.Id));
好,再大体上说一下过程,这次的过程比消息通信机制的导航要简单很多,最最重要的代码就下面这些
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); } }
就直接将当前的跳转后的DataContent取出来执行里面的OnNavigatedTo等方法,控制器这次的作用主要还是导航路由和触发导航事件后的执行路由分发事件方法。
这种导航方式在单例和非单例的vm中都可以使用,因为它不依赖与vm,而只是关联了view对应的作为DataContent的vm。目前的项目我都是采用这种导航方式,比较方便