zoukankan      html  css  js  c++  java
  • Windows Phone——MVVM模式下页面导航及导航事件处理

    问题

    在使用MVVM模式(使用了MvvmLight框架)开发Windows Phone应用的时候,遇到如下问题:

    1. ViewModel中如何实现导航?
    2. ViewModel中如何处理OnNavigatedTo事件?
    3. ViewModel中如何处理所有界面导航事件(OnNavigatedTo、OnNavigatingFrom及OnNavigatedFrom)?

    这里将第2和第3个问题分开,是因为如果只需处理OnNavigatedTo事件,有一个不得不提十分巧妙的解决办法,后文具体展开。

    ViewModel中实现导航

    在ViewModel中实现导航有多种办法,google一下就能搜到很多种做法,最简单的莫过于定义一个Navigator类,在类中获取PhoneApplicationFrame进行导航:

     public class Navigator
        {
            public Navigator()
            {
                _frame = ((App)App.Current).RootFrame;
            }
    
            public void Navigate(string uriString)
            {
                _frame.Navigate(new Uri(uriString, UriKind.Relative));
            }
    
            public void GoBack()
            {
                if (_frame.CanGoBack)
                {
                    _frame.GoBack();
                }
            }
    
            private PhoneApplicationFrame _frame;
        }

    然后在App.cs中定义一个静态Navigator类型的属性即可实现全局的调用。

    public partial class App : Application
        {
            private static Navigator _navigator = null;
            public static Navigator Navigator
            {
                get
                {
                    return _navigator ?? (_navigator = new Navigator());
                }
            }
    
            ...
        }

    如果你也一样使用了MvvmLight框架,可以参考Laurent Bugnion (GalaSoft) 的这篇博文《Navigation in a #WP7 application with MVVM Light》。

    想要实现基本的导航功能相对来说比较简单,核心就是使用PhoneApplicationFrame。

    ViewModel中处理OnNavigatedTo事件

    关于如何在ViewModel中实现导航以及处理OnNavigatedTo事件,目前个人觉得实现的最为巧妙的就是 Agile.Zhou(kklldog) 在博文《豆瓣电台WP7客户端 MVVM重构记录之使用MVVM Light的Message实现导航》中介绍的使用一个NavgationController对象来统一实现导航的方法。这里简单描述一下原理及用法。

    原文用一句话介绍了思路,这里引用一下:

    当一个VM需要导航的时候,Send一个Message把导航的URL传递出去,这个消息被一个NavgationController截获,执行导航操作,导航完成之后NavgationController会Send一个Message,通知导航到的View对应的ViewModel执行Navigated方法。

    具体步骤是:

    1. 定义NavigationHelper类,实现真正的导航功能以及导航消息的发送。
      NavigationHelper类的GetPhoneFrameRoot方法其实是实现了PhoneApplicationFrame的单例模式,通过注册PhoneApplicationFrameNavigated事件,达到了截获OnNavigatedTo事件的目的,同时在事件方法中发送导航完成的消息。
      private static void GetPhoneFrameRoot()
      {
          ......
          _root = Application.Current.RootVisual as PhoneApplicationFrame;
          ......
      
          _root.Navigated += new System.Windows.Navigation.NavigatedEventHandler(RootNavigated);
      
      }
      
      private static 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);// 发送导航完成的消息
      }

      NavigationMsgSend方法实现发送导航的消息
      /// <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);
      }

      NavigatedMsgReg方法实现接收导航完成的消息
      /// <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);// 调用实现INavigation接口的ViewModel的Navigated方法
          }
      }
    2. 定义INavigation接口,来约束ViewModel实现Navigated行为。
      public interface INavigation
      {
              /// <summary>
              /// 获取对应的View的Url
              /// </summary>
              /// <returns></returns>
              string GetViewUrl();
              /// <summary>
              /// 导航完成后发生
              /// </summary>
              /// <param name="uri"></param>
              void Navigated(Uri uri);
      }
    3. 定义NavigationController,接收导航消息,实现真正的导航动作。
          public class NavigationController
          {
              public NavigationController()
              {
                  Messenger.Default.Register<Uri>(this, MsgToken.Navigation, Navigation);
              }
      
              private void Navigation(Uri uri)
              {
                  NavigationHelper.NavigationTo(uri);
              }
          }

    具体用法详见源码,下载地址:http://dbfm7.codeplex.com/

    我在使用这种做法的时候,发现每次在ViewModel的构造函数中都要通过调用NavigationHelper的NavigatedMsgReg方法来注册接收导航完成的消息,这实在是繁琐,既然每次都要注册,能不能实现“自动”注册呢?

    我们都知道MVVM模式同MVC相同,提倡“约定大于配置”的思想,即把所有界面放在Views文件夹,页面的路径通常就是/Views/xxxx.xaml,那么就可以通过获取页面的强类型来得到页面的名称,从而到页面的路径。

    具体做法是,修改INavigation接口为NavigationViewModelBase基类,该基类直接继承MvvmLight的ViewModelBase

        public abstract class NavigationViewModelBase : ViewModelBase
        {        
            /// <summary>
            /// 注册导航
            /// 约定页面路径为/Views/...
            /// </summary>
            /// <param name="page">页面类型</param>
            protected NavigationViewModelBase(Type page)
            {
                var urlToken = string.Format("/Views/{0}.xaml", page.ToString().Split('.').LastOrDefault());
                Messenger.Default.Register<Uri>(this, urlToken, Navigationed);
            }
    
            /// <summary>
            /// 导航完成回调函数
            /// </summary>
            /// <param name="url"></param>
            protected abstract void Navigationed(Uri url);
        }

    ViewModel则这样写:

        public class MainViewModel : NavigationViewModelBase
        {
            public MainViewModel()
                : base(typeof(MainPage))
            {
                ...
            }
    		
    	...		
    		
            protected override void Navigationed(Uri url)
            {
    		...
            }
        }

    ViewModel中处理所有界面导航事件

    在Windows Phone开发中,除非项目实在非常简单,我们基本无法回避要使用页面的OnNavigatedTo、OnNavigatingFrom、OnNavigatedFrom事件,来处理一下进入或离开页面的逻辑,那么如何在ViewModel中直接使用这些事件呢?

    我在stackoverflow见到通过在页面的相关事件中发送消息到ViewModel的方式来实现在ViewModel处理这些事件(Handling the OnNavigatedFrom / OnNavigatedTo events in the ViewModel),这种做法虽然简单直接,但在每个页面的Code-behind里都这么发消息总感觉十分的蹩脚。

    那么使用一个继承自PhoneApplicationPage的页面基类来统一做这些事情是个不错的选择。顺着这个思路,一番google之后,在github的源码库里看到名为JoshClose已经这么做了,其实现简单明了,我根据需要修改如下:

    定义页面基类PhoneApplicationPageBase

    PhoneApplicationPageBase继承自PhoneApplicationPage,作为所有页面的基类,通过DataContext来调用自定义的NavigationViewModelBase基类中已定义的相关方法,实现ViewModel与页面的解耦。

        public class PhoneApplicationPageBase : PhoneApplicationPage
        {
            protected PhoneApplicationPageBase()
            {
                Loaded += PageBaseLoaded;
            }
    
            private void PageBaseLoaded(object sender, RoutedEventArgs e)
            {
                var viewModel = DataContext as NavigationViewModelBase;
                if (viewModel != null)
                {
                    viewModel.NavigationService = NavigationService;
                }
            }
    
            protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                var viewModel = DataContext as NavigationViewModelBase;
                if (viewModel != null)
                {
                    viewModel.NavigationContext = NavigationContext;
                    viewModel.OnNavigatedTo(e);
                }
            }
    
            protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
            {
                base.OnNavigatingFrom(e);
                var viewModel = DataContext as NavigationViewModelBase;
                if (viewModel != null)
                {
                    viewModel.NavigationContext = NavigationContext;
                    viewModel.OnNavigatingFrom(e);
                }
            }
    
            protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
            {
                base.OnNavigatedFrom(e);
                var viewModel = DataContext as NavigationViewModelBase;
                if (viewModel != null)
                {
                    viewModel.NavigationContext = NavigationContext;
                    viewModel.OnNavigatedFrom(e);
                }
            }
        }

    定义ViewModel基类NavigationViewModelBase

    通过定义ViewModel的基类,来实现行为约束和基本的实现,同时也可以直接获取并使用NavigationService和NavigationContext对象。

        public abstract class NavigationViewModelBase : ViewModelBase
        {
            protected bool RemoveBackEntry { get; set; }
    
            public NavigationService NavigationService { get; set; }
    
            public NavigationContext NavigationContext { get; set; }
    
            public virtual void OnNavigatedTo(NavigationEventArgs e) { }
    
            public virtual void OnNavigatingFrom(NavigatingCancelEventArgs e) { }
    
            public virtual void OnNavigatedFrom(NavigationEventArgs e)
            {
                if (RemoveBackEntry)
                {
                    RemoveBackEntry = false;
                    NavigationService.RemoveBackEntry();
                }
            }
        }

    具体用法

    1. 修改页面的XAML,使之继承自PhoneApplicationPageBase,同时修改.cs文件;
      xaml
      cs
    2. 所有作为页面DataContext的ViewModel继承基类NavigationViewModelBase;
      viewmodel
    3. 在ViewModel重写基类虚方法,实现相关处理逻辑。
      override

    第三种解决方案Demo:

    download

    Posted by Bryce Zhang
    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

  • 相关阅读:
    Linux 系统使用eclipse时顶部菜单栏不见解决办法
    DES 文件加密解密
    Java 做CRC -CCITT x16+x12+x5+1(0x1021),初值为0x0000 校验
    字节流排序低字节在前高字节在后
    burpsuite暴力破解之四种方式
    网络安全之信息收集部分内容
    渗透测试步骤
    PHP与Mysql之间的纠缠(超详细)
    MySQL知识汇总
    PHP文件上传、错误处理
  • 原文地址:https://www.cnblogs.com/brycezhang/p/2875150.html
Copyright © 2011-2022 走看看