如何在UWP中统一处理不同设备间的页面回退逻辑

在本篇缪文中,楼主打算给UWP开发的初学者讲述一个在开发中经常遇到的很现实的问题:页面回退逻辑 。

众所周知,UWP的应用程序理论上是可以运行在Windows上的各种设备上,其中包括Windows PC、WindowsMobile、XBox、IOT等。当我们的UWP应用程序运行在不同的设备上时,不同设备间的页面回退逻辑我们就要考虑周全,要考虑不同设备间的页面回退操作该如何设计才能更好的满足用户的使用需求。因此,我们有必要将不同设备间的页面回退逻辑进行统一封装,这样一来不仅有利于代码的维护,而且也有利于回退功能的扩充,实现了实现了“高内聚低耦合“。为了方便,楼主这里只简单论述一下当我们的UWP应用程序运行在PC上和Mobile上时该如何处理不同平台的页面回退逻辑。当应用程序运行在PC上时,页面回退常常是通过用户点击应用程序提供的一个回退按钮来进行页面回退,但是当我们的应用程序运行在Mobile上时,用户更愿意使用手机设备上提供的物理后退键来进行页面回退,这样一来,我们就需要使用封装的思想来进行封装。

1、理论分析:

在新的MSDN中,微软为我们提供了一套新的API:SystemNavigationManager 。当UWP应用程序在PC上运行的时候,通过此API,我们可以为应用程序提供一个回退按钮来向用户暗示此页面是可以回退的,当用户点击该按钮后,页面成功回退。但是当我们的UWP应用程序运行在Mobile上时,如果还是用这种方法来进行页面回退的的话,对用户来说就可能不是很友好,因此,我们要投其说好,用手机设备上的物理后退键来实现相应的页面回退逻辑,其对应的API为:HardwareButtons.BackPressed。分析到这,我们基本上明白该如何处理这两中设备间的回退逻辑的差异。So,问题来了我们该把这套逻辑放到哪里合适?何时使用这套逻辑较为合适? 这是两道主观题,仁者见仁智者见智。楼主这里抛砖引玉,为初学者论述一种方法。

由于应用程序刚启动的时候会触发App.OnLaunched()函数,所以我们需要修改OnLaunched()函数;其次,为了保证页面的唯一性,我们这里使用“框架页”的方法来承载不同的页面,通过Frame来完成页面的跳转;最后,我们还需要实现一个用户控件来方式应用程序的主题框架。

总结一句话就是:让应用程序来加载我们的用户控件,让用户控件来承载我们的框架页,让框架页来完成应用程序的页面跳转。

是不是感觉很绕口??没关系,接下来我们看看实际的代码该如何写………………

2、代码实现:

首先:

  我们需要为我们的应用程序创建一个页面跳转服务类:NavigationService,该类封装来不同平台间的页面回退逻辑。需要指出的是:由于该类使用来不同回退逻辑,因此我们使用哪个平台的回退逻辑就添加对哪个平台的扩展引用,我这里只添加来对Mobile的扩展引用。代码很简单,我相信你看一下就会的。

public class NavigationService
    {
        public static NavigationService Instance { get; protected set; }


        private Frame frame;


        public Stack<Type> PageStack { get; protected set; }


        public NavigationService(Frame frame)
        {
            if (Instance != null)
            {
                throw new Exception("Only one navigation service can exist in a App.");
            }
            Instance = this;
            this.frame = frame;
            this.PageStack = new Stack<Type>();


            SystemNavigationManager.GetForCurrentView().BackRequested += NavigationService_BackRequested;
            if (ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
            {
                HardwareButtons.BackPressed += HardwareButtons_BackPressed;
            }
        }


        public void NavigateTo(Type pageType, object parameter)
        {
            if (PageStack.Count > 0)
            {
                //返回位于Stack顶部的对象但不将其移除。
                if (PageStack.Peek() == pageType)
                {
                    return;
                }
            }
            PageStack.Push(pageType);
            frame.Navigate(pageType, parameter);
            UpdateBackButtonVisibility();
        }


        public void NavigateToHome()
        {
            var type = frame.BackStack.FirstOrDefault().SourcePageType;
            frame.Navigate(type, null);
            frame.BackStack.Clear();
            UpdatePageStack();
            UpdateBackButtonVisibility();
        }
        private void UpdatePageStack()
        {
            if (PageStack.Count > 0)
            {
                PageStack.Pop();
            }
        }


        private void UpdateBackButtonVisibility()
        {
            SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = frame.CanGoBack ?
                 AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
        }


        private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
        {
            if (frame.CanGoBack)
            {
                frame.GoBack();
                UpdatePageStack();
                e.Handled = true;
            }
        }


        private void NavigationService_BackRequested(object sender, BackRequestedEventArgs e)
        {
            if (frame.CanGoBack)
            {
                frame.GoBack();
                UpdatePageStack();
            }
            UpdateBackButtonVisibility();
        }
    }

其次:

页面跳转服务类算是已经封装完成,接下来我们就需要创建一个用户控件来承载应用程序的主体框架。

<UserControl
    x:Class="NavigationDemo.ShellView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NavigationDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    <UserControl.Resources>
        <x:String x:Key="home">首页</x:String>
        <x:String x:Key="article">文章</x:String>
        <x:String x:Key="question">问题</x:String>
        <x:String x:Key="thing">东西</x:String>
    </UserControl.Resources>
    <Grid>
        <SplitView IsPaneOpen="True" DisplayMode="CompactInline" OpenPaneLength="100">
            <SplitView.Pane>
                <ListView>
                    <ListViewItem x:Name="homeCmd" Content="{StaticResource home}">
                    <ListViewItem x:Name="articleCmd" Content="{StaticResource article}"/>
                    <ListViewItem x:Name="questionCmd" Content="{StaticResource question}"/>
                    <ListViewItem x:Name="thingCmd" Content="{StaticResource thing}"/>
                </ListView>
            </SplitView.Pane>
            <SplitView.Content>
                <Frame x:Name="frame" x:FieldModifier="public"/>
            </SplitView.Content>
        </SplitView>


    </Grid>
</UserControl>

然后:

  主体框架控件已经设计完成,接下来我们就修改改造App类。我们需要为应用程序提供一个全局的页面跳转,这样方便使用;其次我们需要将应用程序的初始页面改造为一个用户控件,这样就保证引用程序始终加载的是一个用户控件。


public static NavigationService NavService { get; set; }
protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            //ShellView是我们创建的用户控件
            ShellView rootFrame = Window.Current.Content as ShellView;


            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new ShellView();
                if (rootFrame.frame == null)//frame是我们在用户控件中创建的框架页面
                {
                    rootFrame.frame = new Frame();
                }
                NavService = new NavigationService(rootFrame.frame);


                rootFrame.frame.NavigationFailed += OnNavigationFailed;


                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: Load state from previously suspended application
                }


                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }


            if (rootFrame.frame.Content == null)
            {
                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter
                //确保应用程序初始加载的是指定的首页
                NavService.NavigateTo(typeof(MainPage), e.Arguments);
            }
            // Ensure the current window is active
            Window.Current.Activate();
        }

最后:

  代码敲到这儿算是已经完成的差不多,现在万事俱备,只欠东风,注册我们的跳转事件,我这里只简单跳转4个页面,脑洞大的朋友可以多设计几个。在我们的用户控件对应的后台代码中为应用程序的全局菜单注册页面跳转事件。

private void homeCmd_Tapped(object sender, TappedRoutedEventArgs e)
{
    App.NavService.NavigateToHome();
}


private void articleCmd_Tapped(object sender, TappedRoutedEventArgs e)
{
    App.NavService.NavigateTo(typeof(BlankPage1), null);
}


private void questionCmd_Tapped(object sender, TappedRoutedEventArgs e)
{
    App.NavService.NavigateTo(typeof(BlankPage2), null);
}


private void thingCmd_Tapped(object sender, TappedRoutedEventArgs e)
{
    App.NavService.NavigateTo(typeof(BlankPage3), null);
}

好了,写到这了算是已经大功告成了。我们还是看一下实际的运行效果吧。

  这是在PC上运行的效果,在手机上运行的效果和这类似,但是页面回退是使用物理后退键来完成的,感兴趣的朋友可以自行尝试一下。

需要指出的是,如果你在手机上运行的话,你会发现这种方法会给你额外赠送一个彩蛋:当我们需要对系统标题栏的颜色进行设置的时候,我们完全可以在我们的用户控件中实现,哪怕我们需要填充一种图片或者其他复杂的元素都可以通过简单几行XAML代码都是可以搞定的。

3、总结:

这种处理方法不知能否满足各位的某种实际需求? 需求千千万,代码改不断,所以作为一个程序猿,我们不仅要提高我们的编码能力,同时解决问题的能力也要不断提高。这里简要总结一下使用到的知识:

    封装的思想;

用户控件;

框架页;

    好像也没啥了:)

  废话说来这么多,不知各位看官是否看懂???俗话说得好:实践出真知。所以建议感兴趣的朋友还是亲自尝试一下比较好。

点击下载示例

http://files.cnblogs.com/files/hippieZhou/NavigationDemo.zip

再次感谢 youngytj,Leandro指正代码中写的不好的部分,欢迎各位大大继续拍砖。代码已修改。

原文发布于微信公众号 - 我为Net狂(dotNetCrazy)

原文发表时间:2016-02-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据小魔方

这个包绝对值得你用心体验一次!

这一段时间在研究R里面的数据抓取相关包,时不时的能发掘出一些惊喜。 比如今天,我找到了一个自带请求器的解析包,而且还是嵌入的pantomjs无头浏览器,这样就不...

31860
来自专栏破晓之歌

pycharm主题设置 原

File -> Settings -> IDE Settings -> Appearance -> Theme -> 选择主题

64330
来自专栏前端说吧

Html2canvas - 微信中长按存图 - 将h5活动结果保存到本地

 现在有很多在微信里流行的h5活动页。这些小h5大部分都是简单的交互然后得出一个abcd早就拟定好的结果,根据你的选项分几种情况,最终得到其中一个作为你测试的答...

88720
来自专栏木宛城主

SharePoint 2013 Troubleshooting——启用 Developer Dashboard

SharePoint 2010的管理员和开发者可能对SharePoint Developer Dashboard(开发人员仪表盘)很熟悉。在SharePoin...

220100
来自专栏CRPER折腾记

React 折腾记 - (1) React Router V4 和antd侧边栏的正确关联及动态title的实现

collapsed,onCollapse这些是控制侧边栏缩小的,接受的是外部的props

46130
来自专栏大前端开发

微信小程序填坑-Android真机环境下的bluebird.js

今天,有朋友反映说,我的微信小程序的例子在andriod真机环境下运行出错,研究调试了半天,发现原来是使用的bluebird.js(Promise实现库)导致的...

8630
来自专栏华仔的技术笔记

Xcode 7 自动测试XCTestCase

44370
来自专栏搞前端的李蚊子

微信小程序模板消息详解

先放代码 wxml: <form name='pushMsgFm' report-submit bindsubmit='orderSign'> <vi...

67090
来自专栏游戏杂谈

H5小游戏的坑点小结

1) iOS 9.1 的safari中,在onTouchBegan方法中调用cc.audioEngine.playEffect播放音效是没有效果的,如果在onT...

20710
来自专栏hightopo

基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通讯同步(一)

17620

扫码关注云+社区

领取腾讯云代金券