(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序

 

每次使用 Visual Studio 的模板创建一个 UWP 程序,我们会在项目中发现大量的项目文件、配置、应用启动流程代码和界面代码。然而这些文件在 UWP 程序中到底是如何工作起来的?

我从零开始创建了一个 UWP 程序,用于探索这些文件的用途,了解 UWP 程序的启动流程。


本文分为两个部分:

本文将从 Main 函数开始,一步步跑起来一个应用程序,显示一个窗口,并在窗口中显示一些内容。重点在了解在 UWP 中运行应用程序,并显示窗口。

 

启动应用

在上一篇文章中的末尾,我们成功启动了程序并进入了 Main 函数的断点,但实际上运行会报错。我们能看见一个窗口显示出来,随后提示进程已启动,但应用尚未运行。

The Walterlv.Demo.ZeroUwp.exe process started, but the activation request failed with error ‘The app didn’t start’.

是的,我们只有一个什么都没做的 Main 函数,进程当然能够成功启动;但我们需要能够启动应用。那么 UWP 的应用是什么呢?是 CoreApplication。

所以我们使用 CoreApplication 类型执行 Run 静态方法。

CoreApplication.Run

此方法要求传入一个 IFrameworkViewSource。事实上 UWP 已经有一个 IFrameworkViewSource 的实现了,是 FrameworkViewSource。不过,我希望自己写一个,了解其原理。

所以,就用 ReSharper 生成了 IFrameworkViewSource 的一个实现:

using Windows.ApplicationModel.Core;

namespace Walterlv.Demo.ZeroUwp
{
    internal sealed class WalterlvViewSource : IFrameworkViewSource
    {
        public IFrameworkView CreateView() => new WalterlvFrameworkView();
    }
}

IFrameworkViewSource 接口中只有一个方法 CreateView,返回一个新的 IFrameworkView 的实例。

只是写一个 NotImplementedException 的异常,当然是跑不起来的,得返回一个真的 IFrameworkView 的实例。UWP 自带的实现为 FrameworkView,那么我也自己实现一个。

这次需要实现的方法会多一些:

using Windows.ApplicationModel.Core;
using Windows.UI.Core;

namespace Walterlv.Demo.ZeroUwp
{
    internal sealed class WalterlvFrameworkView : IFrameworkView
    {
        public void Initialize(CoreApplicationView applicationView) => throw new System.NotImplementedException();
        public void SetWindow(CoreWindow window) => throw new System.NotImplementedException();
        public void Load(string entryPoint) => throw new System.NotImplementedException();
        public void Run() => throw new System.NotImplementedException();
        public void Uninitialize() => throw new System.NotImplementedException();
    }
}

因此,我们需要理解这些方法的执行时机以及含义才能正确实现这些方法。庆幸的是,这些方法的含义都能在官方文档中找到(其实就是平时看到的注释):

为了方便查看,我将其整理到这些方法上作为注释。

顺便的,下面这些方法刚好是按照应用生命周期的顺序被调用,也就是 Initialize->SetWindow->Load->Run->Uninitialize

/// <summary>
/// 当应用启动时将执行此方法。进行必要的初始化。
/// </summary>
public void Initialize(CoreApplicationView applicationView) { }

/// <summary>
/// 每次应用需要显示一个窗口的时候,此方法就会被调用。用于为当前应用程序显示一个新的窗口视图。
/// </summary>
public void SetWindow(CoreWindow window) { }

/// <summary>
/// 会在 <see cref="Run"/> 方法执行之前执行。如果需要使用外部资源,那么这时需要将其加载或激活。
/// </summary>
public void Load(string entryPoint) { }

/// <summary>
/// 当此方法调用时,需要让应用内的视图(View)显示出来。
/// </summary>
public void Run() { }

/// <summary>
/// 当应用退出时将执行此方法。如果应用启动期间使用到了外部资源,需要在此时进行释放。
/// </summary>
public void Uninitialize() { }

在此接口的所有方法留空地实现完以后,我们的 UWP 应用终于能跑起来了。当按下 F5 调试之后,不会再提示错误,而是依次执行这五个方法后,正常退出应用。

启动窗口

注意到以上所有方法都留空之后,应用程序很快就退出了。这与我们开发传统 Win32 应用时的效果是一致的 —— 是的,我们缺一个消息循环。我们需要一个不断处理的消息循环用来阻断主线程的退出,同时又能够不断响应消息。而这样的方法需要写到 Run() 方法里面。

UWP 中开启一个消息循环是非常容易的,不过我们需要一个 CoreDispatcher 对象。在我们目前的接口实现中,CoreDispatcher 对象可以从 CoreWindow 中获取到。所以我们需要在 SetWindow 方法中拿到 CoreWindow 的实例,然后在 Run 中使用它开启窗口消息循环。

public void SetWindow(CoreWindow window)
{
    _window = window;
}

public void Run()
{
    _window.Activate();
    _window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
}

private CoreWindow _window;

开启消息循环
▲ 开启了消息循环之后,应用不会直接退出了

在窗口中显示点东西

我们使用 CompositionAPI 可以在窗口中创建 Visual 并显示出来。

public void SetWindow(CoreWindow window)
{
    _window = window;

    var compositor = new Compositor();
    var root = compositor.CreateContainerVisual();
    var compositionTarget = compositor.CreateTargetForCurrentView();
    compositionTarget.Root = root;

    var child = compositor.CreateSpriteVisual();
    child.Size = new Vector2(100f, 100f);
    child.Brush = compositor.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x80, 0xFF));
    root.Children.InsertAtTop(child);
}

窗口中新增的 Visual

在窗口中做一些交互

CoreWindow 除了为我们提供了消息循环之外,也可以提供交互。监听 PointerMoved 事件,我们可以做一些简单的交互。

下面我用 Git 标准差异比较的方式添加了交互的代码 PointerMoved

  public void SetWindow(CoreWindow window)
  {
      _window = window;
+     _window.PointerMoved += OnPointerMoved;

      var compositor = new Compositor();
-     var root = compositor.CreateContainerVisual();
+     _root = compositor.CreateContainerVisual();
      var compositionTarget = compositor.CreateTargetForCurrentView();
-     compositionTarget.Root = _root;
+     compositionTarget.Root = _root;

      var child = compositor.CreateSpriteVisual();
      child.Size = new Vector2(100f, 100f);
      child.Brush = compositor.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x80, 0xFF));
-     root.Children.InsertAtTop(child);
+     _root.Children.InsertAtTop(child);
  }

+ private void OnPointerMoved(CoreWindow sender, PointerEventArgs args)
+ {
+     var visual = _root.Children.First();
+     var position = args.CurrentPoint.Position;
+     visual.Offset = new Vector3((float) (position.X - 50f), (float) (position.Y - 50f), 0f);
+ }

  private CoreWindow _window;
+ private ContainerVisual _root;

能够完成一些简单的交互。

窗口内的交互

总结

在本文中,我们了解到 UWP 的应用程序启动中也一样需要有窗口消息循环。不过 UWP 中创建消息循环还是非常简单的。

我们使用 CompositionAPI 进行了一些界面显示和简单的交互。了解到即便是如此复杂的 UWP 程序,其启动流程也没有那么复杂。

不过,如果你阅读了前面一篇 (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序,会发现复杂的部分都在项目文件和系统的部分。

转载于:https://www.cnblogs.com/walterlv/p/10236435.html

Contents 开发桌面应用 设置开发环境 系统要求 安装开发人员工具 启用设备进行开发 开发人员模式功能和调试 创建一个开发者帐户 入门 概述 适用于 Windows 应用的 Visual Studio 模板 生成适用于 Windows 11 的应用 让你的应用在 Windows 11 上出色表现 设计和 UI 概述 Windows 11 中的设计 Windows 11 设计原则 Windows 11 签名体验 Geometry 颜色 分层和提升 材料 图标 版式 设计基础知识 概述 应用设计简介 导航基础知识 概述 实现基本导航 导航历史记录和向后导航 命令基础知识 内容基础知识 教程 创建用户界面 创建自适应布局 设置控件的样式 布局 概述 页面布局 屏幕大小和断点 响应式设计技术 使用 XAML 的布局 显示多个视图 显示多个视图 使用 AppWindow 使用 ApplicationView 对齐、边距和填充 面板 概述 教程:使用布局面板 拆分视图 自定义面板 自定义面板示例:BoxPanel 附加的布局 转换 概述 3D 透视效果 Z 深度和阴影 控制 概述 控件和事件简介 命令处理简介 基本输入 按钮 复选框 组合框和列表框 超链接 单选按钮 评分控件 滑块 切换开关 集合 概述 列表视图和网格视图 翻转视图 PipsPager 树视图 ItemsRepeater 项目容器和模板 项目容器和模板 数据模板选择 列表视图项模板 网格视图项模板 选择和交互 集合命令处理 “选择模式”概述 轻扫 下拉刷新 筛选集合 其他集合选项和自定义 反转列表 嵌套 UI 对话框和浮出控件 概述 对话框 浮出控件 教学提示 窗体 媒体、图形和形状 动画图标 图像和图像画笔 墨迹 媒体播放 自定义传输控件 形状 Web 视图 菜单和工具栏 菜单和上下文菜单 命令栏 命令栏浮出控件 菜单浮出控件和菜单栏 导航 痕迹导航栏 列表/详细信息 导航视图 Pivot 选项卡视图 人员 联系人卡片 头像图片 选取器 颜色选取器 日期和时间控件 日历日期选取器 日历视图 日期选取器 时间选取器 滚动和布局 Expander 滚动和平移控件 语义式缩放 双窗格视图 状态和信息 进度 工具提示 信息栏 文本 概述 自动建议框 文本块 RTF 块 文本框 富编辑框 密码框 数字框 标签 内容链接 手写视图 样式 概述 颜色 版式 图标 概述 应用图标和徽标 Segoe MDL2 图标 亚克力 Mica 显示焦点 声音 写入样式 XAML 画笔 XAML 样式 XAML 控件模板 ResourceDictionary 和 XAML 资源引用 XAML 主题资源 间距 角半径 移动 概述 计时和缓动 方向性和引力 运动练习 页面过渡 连贯的动画 视差 XAML 中的动画 属性动画 情节提要动画 关键帧以及缓动函数动画 Shell Toast 通知 UX 指南 发送本地 Toast C# 应用 C++ UWP 应用 C++ WRL 应用 其他应用 Toast 内容 Content 架构 计划 toast 其他功能 自定义音频 进度条 挂起更新 自定义时间戳 集合 标头 通知侦听器 已过时 锁屏提醒通知 推送通知 概述 WNS 优先级 将 WNS 流量加入允许列表 由推送通知向导生成的代码 任务栏 将应用固定到任务栏 标题栏 动态磁贴 辅助磁贴 指南 固定到“开始”屏幕 固定到任务栏 桌面应用程序 磁贴内容 磁贴内容架构 特殊磁贴模板 发送本地磁贴通知 可追踪的磁贴通知 主要磁贴 API 磁贴和 Toast 通知的语言、比例和高对比度支持 杂项 通知可视化工具 通知传递方法 通知通道类型 使用 Webpush 和 VAPID 的备用通道 定期通知 输入和交互 概述 输入基础版 指针输入 凝视 笔和 Windows Ink 教程:向应用添加墨迹支持 识别笔划墨迹 存储和检索笔划墨迹 添加 InkToolbar 触摸 鼠标 Keyboard 访问键 键盘加速键 键盘事件 适用于键盘、手柄、遥控器和辅助功能工具的焦点导航 编程焦点导航 响应触摸键盘的存在 使用输入范围更改触摸键盘 文本输入 自定义文本输入 文本缩放 选择文本和图像 输入法编辑器 输入法编辑器要求 ......
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值