如何在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,该类封装页面的回退逻辑。需要指出的是:由于SystemNavigationManager 可以实现不同平台的回退逻辑,因此我们没必要再单独将Mobile的物理后退键封装(谢谢yan_xiaodi的纠正)。代码很简单,我相信你看一下就会的。
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;
} public void NavigateTo(Type pageType, object parameter)
{
if (PageStack.Count > )
{
//返回位于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 > )
{
PageStack.Pop();
}
} private void UpdateBackButtonVisibility()
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = frame.CanGoBack ?
AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}
private void NavigationService_BackRequested(object sender, BackRequestedEventArgs e)
{
if (frame.CanGoBack)
{
frame.GoBack();
UpdatePageStack();
e.Handled = true;
}
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、总结:
这种处理方法不知能否满足各位的某种实际需求? 需求千千万,代码改不断,所以作为一个程序猿,我们不仅要提高我们的编码能力,同时解决问题的能力也要不断提高。这里简要总结一下使用到的知识:
封装的思想;
用户控件;
框架页;
好像也没啥了:)
废话说来这么多,不知各位看官是否看懂???俗话说得好:实践出真知。所以建议感兴趣的朋友还是亲自尝试一下比较好。
再次感谢 youngytj,Leandro指正代码中写的不好的部分,欢迎各位大大继续拍砖。代码已修改。
如何在UWP中统一处理不同设备间的页面回退逻辑的更多相关文章
- 想抛就抛:Application_Error中统一处理ajax请求执行中抛出的异常
女朋友不是想抛就抛,但异常却可以,不信请往下看. 今天在MVC Controller中写代码时,纠结了一下: public async Task<ActionResult> Save(in ...
- UWP: 在 UWP 中使用 Entity Framework Core 操作 SQLite 数据库
在应用中使用 SQLite 数据库来存储数据是相当常见的.在 UWP 平台中要使用 SQLite,一般会使用 SQLite for Universal Windows Platform 和 SQLit ...
- 在 UWP 中实现 Expander 控件
WPF 中的 Expander 控件在 Windows 10 SDK 中并不提供,本文主要说明,如何在 UWP 中创建这样一个控件.其效果如下图: 首先,分析该控件需要的一些特性,它应该至少包括如下三 ...
- UWP中MarkupExtension的使用
Xaml作为一种描述语言,在编程中极大地简化了页面开发的繁琐及时间消耗,这得益于它的多种特性:数据绑定.动画.资源文件等等.标记扩展作为其一个特性,在xaml中有不可替代的作用,今天分析下自定义标记扩 ...
- 如何在ASP.NET Web站点中统一页面布局[Creating a Consistent Layout in ASP.NET Web Pages(Razor) Sites]
如何在ASP.NET Web站点中统一页面布局[Creating a Consistent Layout in ASP.NET Web Pages(Razor) Sites] 一.布局页面介绍[Abo ...
- [UWP]UWP中获取联系人/邮件发送/SMS消息发送操作
这篇博客将介绍如何在UWP程序中获取联系人/邮件发送/SMS发送的基础操作. 1. 获取联系人 UWP中联系人获取需要引入Windows.ApplicationModel.Contacts名称空间. ...
- 如何在Eclipse中设置默认的JSP文件头部编码
如何在Eclipse中设置默认的JSP文件头部编码 一般,我们为了以后在导入和导出程序的时候(特别是项目较大,文件多)一般都默认文件编码格式为UTF-8 如果你通常都是通过Eclipse来编写程序,那 ...
- 浅析如何在Nancy中生成API文档
前言 前后端分离,或许是现如今最为流行开发方式,包括UWP.Android和IOS这样的手机客户端都是需要调用后台的API来进行数据的交互. 但是这样对前端开发和APP开发就会面临这样一个问题:如何知 ...
- 服务化改造实践 | 如何在 Dubbo 中支持 REST
什么是 REST REST 是 Roy Thomas Fielding [[1]](#fn1) 在 2000 年他的博士论文 [[2]](#fn2) “架构风格以及基于网络的软件架构设计” 中提出来的 ...
随机推荐
- ArcEngine不同种类的工作空间建立查询ICursor时“超出系统资源”
环境 这里我的工作空间有两种:mdb库和SDE库分别打开的工作空间. 查询语句:使用Field in ('1','2')查询方式来得到游标对象. 错误 当查询语句中in后面的条件值大于1500时,在I ...
- 开源播放器 ijkplayer (四) :Ijkplayer切换网络时停止播放的问题处理
问题起因: 在进行ijkplayer播放器的测试时,发现ijkplayer播放器在切换网络时出现直播画面停止的问题. 问题分析: 抓取日志发现:tv.danmaku.ijk.media.player. ...
- Java学习笔记37(字节流)
输出:程序到文件 输入:文件到程序 字节输出流:OutputStream类 作用:在java程序中写文件 这个类是抽象类,必须使用它的子类 方法: 写入: package demo; import j ...
- asp.net mvc 安全测试漏洞 "跨站点请求伪造" 问题解决
IBM Security Appscan漏洞筛查-跨站请求伪造,该漏洞的产生,有多种情况: 1.WebApi的跨站请求伪造,需要对WebApi的请求头部做限制(此文不做详细介绍): 2.MVC Act ...
- date内置对象
声明一个日期对像:var date=new Date(); 获取日:date.getDate() 1-31日 获取星期:date.getDay() 星期0-6 获取月: date.getMo ...
- python(30)——【random模块】【if __name__ =='__main__'】【os模块】
一.random模块(随机模块) 1.random 常用模块介绍 import random print(random.random()) #返回[0,1)之间的随机浮点数 print(random. ...
- [EXP]ThinkPHP 5.0.23/5.1.31 - Remote Code Execution
# Exploit Title: ThinkPHP .x < v5.0.23,v5.1.31 Remote Code Execution # Date: -- # Exploit Author: ...
- 国外程序员整理的 C++ 资源大全 (zt)
关于 C++ 框架.库和资源的一些汇总列表,由 fffaraz 发起和维护. 内容包括:标准库.Web应用框架.人工智能.数据库.图片处理.机器学习.日志.代码分析等. 标准库 C++标准库,包括了S ...
- 自动生成getter setter
如何使用java黑魔法给一个entity生成getter,setter方法? 由于java是一门静态语言,要给一个类动态添加方法,看似是不可能的.但牛B的程序员会让任何事情发生.我只知道有两种方式可以 ...
- Ext.Direct最新版源码下载地址
以前的地址用不了,现在地址更新为: 全平台: http://www.sencha.com/forum/showthread.php?67992-Ext.Direct-Server-side-Stack ...