这几天使用MVVM重构这个应用,发现一个严重的问题,那就是导航。基于MVVM的思想,View跟ViewModel之间依靠绑定等技术通信,而且是View可以拿到ViewModel,ViewModel不可以拿到View。本来用CodeBehind的时候很容易的导航,到这里就无从下手了。当然也是有办法把View传递到ViewModel的,不过这样就破坏了MVVM的初衷了。
解决这个问题,首先需要解决怎么在ViewModel中得到NavgationServices来导航。以下是解决办法:
root = Application.Current.RootVisual as PhoneApplicationFrame;
拿到这个root之后就可以导航了。
root.Navigate(pageUri);
MVVM Light的Message机制可以Send一个消息,它会被广播出去,然后被register的对象接收,然后调用指定的方法。
思路:
当一个VM需要导航的时候,Send一个Message把导航的URL传递出去,这个消息被一个NavgationController截获,执行导航操作,导航完成之后NavgationController会Send一个Message,通知导航到的View对应的ViewModel执行Navigated方法。
using System; using System.Collections.Generic; using System.Windows; using Microsoft.Phone.Controls; using GalaSoft.MvvmLight.Messaging; namespace MvvmLightNavgation { public class NavigationHelper { private static PhoneApplicationFrame root; public static PhoneApplicationFrame GetPhoneFrameRoot() {
if (root == null) { root = Application.Current.RootVisual as PhoneApplicationFrame; if (root == null) { throw new Exception("获取 ApplicationRootVisual 失败!"); } }
return root;
}
/// <summary> ///
根据url字符串导航
/// </summary> /// <param name="url"></param>
public static void NavigationTo(
string url) {
if (root==
null) GetPhoneFrameRoot();
if(root !=
null) {
var pageUri =
new
Uri(url,
UriKind.Relative); root.Navigate(pageUri); } }
/// <summary> ///
根据Uri导航
/// </summary> /// <param name="pageUri"></param>
public static void NavigationTo(
Uri pageUri) {
if (root ==
null) GetPhoneFrameRoot();
if (root !=
null) { root.Navigate(pageUri); } }
public static
Uri CreateUri(
string url) {
return new
Uri(url,
UriKind.RelativeOrAbsolute); }
/// <summary> ///
发送导航Msg
/// </summary> /// <param name="pageUri"></param>
public static void NavigationMsgSend(
Uri pageUri) {
Messenger.Default.Send(pageUri,
MsgToken.Navigation); }
/// <summary> ///
发送导航Msg
/// </summary> /// <param name="pageUrl"></param>
public static void NavigationMsgSend(
string pageUrl) {
Messenger.Default.Send(CreateUri(pageUrl),
MsgToken.Navigation); }
/// <summary> ///
注册导航完成MSG
/// </summary>
public static void NavigatedMsgReg(
object recipient) {
INavigation navigation = recipient
as
INavigation;
if (navigation!=
null) {
Messenger.Default.Register<
Uri>(recipient, navigation.GetViewUrl(), navigation.Navigated); } } } }
这个类提供了一堆静态方法来实现页面之间的导航。其中最重要的方法是:
/// <summary> /// 发送导航Msg /// </summary> /// <param name="pageUrl"></param> public static void NavigationMsgSend(string pageUrl) { Messenger.Default.Send(CreateUri(pageUrl), MsgToken.Navigation); }
/// <summary> /// 注册导航完成MSG /// </summary> public static void NavigatedMsgReg(object recipient) { INavigation navigation = recipient as INavigation; if (navigation!=null) { Messenger.Default.Register<Uri>(recipient, navigation.GetViewUrl(), navigation.Navigated); } }
NavigationController :
using GalaSoft.MvvmLight.Messaging; namespace MvvmLightNavgation { public class NavigationController { public NavigationController() { Messenger.Default.Register<Uri>(this, MsgToken.Navigation, Navigation);
NavigationHelper.GetPhoneFrameRoot().Navigated += new System.Windows.Navigation.NavigatedEventHandler(RootNavigated); } private void Navigation(Uri uri) { NavigationHelper.NavigationTo(uri); }
private void RootNavigated(object sender, System.Windows.Navigation.NavigationEventArgs e) { string token = e.Uri.OriginalString; if (token.Contains("?")) { int index = e.Uri.OriginalString.IndexOf('?'); token = token.Substring(0, index); } Messenger.Default.Send(e.Uri,token); }
} }
导航控制器,拦截所有导航的消息,然后导航,导航完成之后发送消息通知导航到的View对应的VM执行Navigated方法。
导航接口:
namespace MvvmLightNavgation { public interface INavigation { /// <summary> /// 获取对应的View的Url /// </summary> /// <returns></returns> string GetViewUrl(); /// <summary> /// 导航完成后发生 /// </summary> /// <param name="uri"></param> void Navigated(Uri uri); } } 让VM去实现这个接口,保证所有VM都具有这2个方法。
使用:
在App.xaml里添加一个NavigationController静态资源
<Application.Resources> <vm:MvvmViewModelLocator xmlns:vm="clr-namespace:DBFM7" x:Key="Locator" /> <nav:NavigationController x:Key="NavCtr"/> </Application.Resources>
在需要导航的地方发送导航消息:
string pageUrl = "/View/MainPage.xaml?Channle=" + hubTitle; NavigationHelper.NavigationMsgSend(pageUrl);
public ChannelTileViewModel()
{
NavigationHelper.NavigatedMsgReg(this);
}
VM去实现导航接口:
public class ChannelTileViewModel : ViewModelBase,INavigation {
。。。。。。。。。。。。。。。。。。。
/// <summary> /// 导航完成后发生 /// </summary> /// <param name="uri"></param> public void Navigated(Uri uri) { } /// <summary> /// 获取对应的View的Url /// </summary> /// <returns></returns> public string GetViewUrl() { return "/View/ChannelTile.xaml"; } }