如何在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指正代码中写的不好的部分,欢迎各位大大继续拍砖。代码已修改。
- Oracle 12.2中的一个参数说明(r12笔记第76天)
- Golang语言社区--【游戏服务器知识】多线程并发
- 用100行Nodejs代码写微博爬虫
- MySQL无法创建表的问题分析(r12笔记第73天)
- Golang语言社区--【H5游戏开发基础知识】JavaScript 用法
- Oracle中的PGA监控报警分析二(r12笔记第87天)
- Oracle 12c PDB的数据备份恢复(r12笔记第84天)
- MySQL和Oracle中唯一性索引的差别(r12笔记第83天)
- 如何用JavaScript进行数组去重
- Oracle 12.1升级到12.2的两种方法(r12笔记第92天)
- Oracle数据库重启后密码失效的问题(r12笔记第91天)
- Oracle和MySQL竟然可以这么写这样的SQL?(r12笔记第99天)
- Golang语言社区--了解C++ 用libcurl库进行http通讯网络编程
- Golang语言-- 小技巧
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一键实现自动化部署(灰度发布)实践
- 一文读懂递归算法—程序员必会算法之一
- 原来在Android中请求权限也可以有这么棒的用户体验
- Linux系统SSH(Secure Shell Protocol)服务
- 全网最火Java面试题
- Redis数据备份,容灾恢复手段
- Sublime Text的使用
- JVM垃圾回收机制和算法详解
- 使用 zabbix 监控 tomcat(包含jvm监控)
- Python open 读和写
- Linux系统SSH服务之 sshkey密钥认证实战
- Redis工作中常用命令,看这一篇就够了
- String、StringBuffer、StringBuiler深入分析,看这一篇就够了
- Windows安装pip方法
- 通过jvm字节码研究Synchronized