每一个C# UWP应用中,必不可少的一个文件就是App.xaml.cs。App类是整个程序的入口,它继承了Application类。在App.xaml.cs中,我们可以通过重写Application类中的一些函数,来自定义程序启动时的行为。
在默认UWP模板中,OnLaunched函数担任了初始化程序窗口的工作。但实际上,一个UWP应用的生命周期中,OnLaunched函数可能被调用任意次(包括0次或者很多次)。所以,在初始化程序窗口的时候,必须考虑重复初始化的问题。新手很容易犯的一个错误是在OnLaunched函数中绑定OnBackRequested函数,这样会导致在某些情况下,按一次后退键后退多个页面,或是在某些情况下后退键无效的问题。
注意:本文中代码在Visual Studio 2015下测试过,面向的操作系统目标版本为10.0.14393,最低版本为10.0.10586。完整的示例代码在文末。
目录:
激活函数
处理非正常退出
预启动应用
帧频计数器
一个处理后退键的例子
总结
激活函数
App类中,通过不同方式启动或激活应用时会调用OnLaunched,OnFileActivated,OnFileSavePickerActivated等函数。在此我们将它们统称为“激活函数”。激活函数中必不可少的一项工作是初始化应用界面,如果不这么做的话,应用启动后会一直停留在初始界面没有反应。以下函数是最简单的初始化界面的函数:
private void CreateRootFrameAndNavigate(Type page, object parameter, bool forceNavigate)
{
Frame rootFrame = Window.Current.Content as Frame;
// 若不存在rootFrame,则创建一个rootFrame
if (rootFrame == null)
{
rootFrame = new Frame();
// OnNavigationFailed函数在默认UWP模板中的App.xaml.cs中有定义
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
}
// 若rootFrame是新创建的,或是强制要求导航到页面,则导航到指定的页面
if (rootFrame.Content == null || forceNavigate)
{
rootFrame.Navigate(page, parameter);
}
Window.Current.Activate();
}
然后,你可以将默认的OnLaunched函数替换成以下内容, 它的功能由与默认OnLaunched函数是几乎相同的:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// 若应用已经启动,则不强制导航到MainPage
CreateRootFrameAndNavigate(typeof(MainPage), null, false);
}
然后,如果你的应用支持打开文件的话,你的OnFileActivated函数应该是这样的:
protected override void OnFileActivated(FileActivatedEventArgs e)
{
// 即使应用已经启动,也要跳转到查看文件的页面
CreateRootFrameAndNavigate(typeof(ViewFilePage), e.Files, true);
}
注意:由于在应用程序生命周期中,应用有可能会被通过任一方式激活,所以每一个函数都可能会被调用一次或多次。例如OnLaunched函数在每次用户点击开始菜单的应用图标时都会执行。所以在这些激活函数中必须做一些判断,避免重复初始化。(上方的CreateRootFrameAndNavigate函数通过判断rootFrame是否存在的方式避免的重复初始化。)
处理非正常退出
上面说到的这些激活函数都只有一个参数,类型为xxxEventArgs,它们都实现接口IActivatedEventArgs接口,拥有PreviousExecutionState这个表明之前应用状态的成员。若应用上一次运行时被非正常结束(例如关机、断电、被任务管理器结束、因为内存不足被关闭等),PreviousExecutionState的值会是Terminated,按照微软默认UWP模板的做法,这时应该尝试为用户恢复上次退出时的状态。注意:因为应用被非正常结束后,下次启动的方式是不确定的,所以你应该在你支持的任何一个激活函数中判断PreviousExecutionState,并尝试恢复非正常退出的状态。
所以,你现在要将你的激活函数改成这样:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: 恢复非正常退出前的状态
}
// 若应用已经启动,则不强制导航到MainPage
CreateRootFrameAndNavigate(typeof(MainPage), null, false);
}
protected override void OnFileActivated(FileActivatedEventArgs e)
{
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: 恢复非正常退出前的状态
}
// 即使应用已经启动,也要跳转到查看文件的页面
CreateRootFrameAndNavigate(typeof(ViewFilePage), e.Files, true);
}
预启动应用
OnLaunched函数在Windows系统预启动应用时会执行。
预启动(Prelaunch)指的是Windows系统为了提高应用启动速度,在系统资源充足时主动预先启动一些应用。用户不会观察到预启动行为,预启动的应用在预启动后会马上被挂起(Suspend)。所以,预启动时,应当只加载程序,而不应该加载新闻、消息等容易变化的信息,否则用户真正启动应用时会看到过时的信息。若应用是被预启动的,args.PrelaunchActivated的值为true,否则为false。在Windows 10 1607(10.0.14393)以上版本中,只有你调用CoreApplication.EnablePrelaunch(true)表明应用支持预启动后,你的应用才会被预启动;在Windows 10 1511(10.0.10586)以下版本中,若你的应用不支持预启动,你应当在args.PrelaunchActivated为true时不执行任何操作,直接return。(参见处理应用预启动。)
为了正确处理预启动,或者是正确声明应用不支持预启动,OnLaunched函数应该改为:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
if (e.PrelaunchActivated)
{
// TODO: 若要支持预启动,在此处处理预启动,否则保留原状。
return;
}
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: 恢复非正常退出前的状态
}
// 若应用已经启动,则不强制导航到MainPage
CreateRootFrameAndNavigate(typeof(MainPage), null, false);
}
帧频计数器
默认UWP模板中,当通过调试器启动应用时,会显示帧频计数器。若要实现此功能,在你的激活函数中调用显示帧频计数器的代码:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// 调试状态下显示帧计数器
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
if (e.PrelaunchActivated)
{
// TODO: 若要支持预启动,在此处处理预启动,否则保留原状。
return;
}
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: 恢复非正常退出前的状态
}
// 若应用已经启动,则不强制导航到MainPage
CreateRootFrameAndNavigate(typeof(MainPage), null, false);
}
一个处理后退键的例子
下面这个例子能够实现当可以后退时,在桌面系统中左上角显示后退键,并且通过手机后退键、平板模式任务栏后退键、桌面窗口左上角后退键、Windows + Backspace快捷键都能够正常实现后退功能。
在每一个创建RootFrame的位置,绑定Navigated函数:在
rootFrame = new Frame();
后面加上
rootFrame.Navigated += OnNavigated;
然后定义OnNavigated函数,根据当前是否可以后退的状态来改变后退键的显示状态:
private void OnNavigated(object sender, NavigationEventArgs e)
{
Frame frame = sender as Frame;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
frame.CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
由于OnWindowCreated函数对于每一个应用窗口只会执行一次,而后退键的操作也是应用于每一个窗口的,所以OnWindowCreated函数是绑定后退键事件的绝佳位置:
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
rootFrame.GoBack();
e.Handled = true;
}
else
{
e.Handled = false;
}
}
这样,即使你有多个窗口,每个窗口也能很好地独立处理窗口内的后退键行为。
总结
综上所述,一个实现最简单功能,能够正确处理普通启动和文件启动,能够正确处理后退键的App类代码如下(如果你的应用不支持打开文件的话,将OnFileActivated函数删除即可)。它完成了一个基本的UWP应用在App类中该做的事。
public sealed partial class App : Application
{
public App()
{
this.InitializeComponent();
}
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// 调试状态下显示帧计数器
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
if (e.PrelaunchActivated)
{
// TODO: 若要支持预启动,在此处处理预启动,否则保留原状。
return;
}
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: 恢复非正常退出前的状态
}
// 若应用已经启动,则不强制导航到MainPage
CreateRootFrameAndNavigate(typeof(MainPage), null, false);
}
protected override void OnFileActivated(FileActivatedEventArgs e)
{
// 调试状态下显示帧计数器
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
// TODO: 恢复非正常退出前的状态
}
// 即使应用已经启动,也要跳转到查看文件的页面
CreateRootFrameAndNavigate(typeof(ViewFilePage), e.Files, true);
}
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
// 关联按下返回键的事件。
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
private void CreateRootFrameAndNavigate(Type page, object parameter, bool forceNavigate)
{
Frame rootFrame = Window.Current.Content as Frame;
// 若不存在rootFrame,则创建一个rootFrame
if (rootFrame == null)
{
rootFrame = new Frame();
rootFrame.Navigated += OnNavigated;
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
}
// 若rootFrame是新创建的,或是强制要求导航到页面,则导航到指定的页面
if (rootFrame.Content == null || forceNavigate)
{
rootFrame.Navigate(page, parameter);
}
Window.Current.Activate();
}
private void OnNavigated(object sender, NavigationEventArgs e)
{
Frame frame = sender as Frame;
// 若可以后退,则显示后退键,否则隐藏后退键。
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
frame.CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
// 若可以后退,则后退。若不能后退,则交给其他的程序处理。
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
rootFrame.GoBack();
e.Handled = true;
}
else
{
e.Handled = false;
}
}
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
}
留言
有想法?请给我们留言!您的留言不会直接显示在网站内。