2016 年 2 月

第 31 卷,第 2 期

Windows 10 - 适合 Web 开发者构建的通用 Windows 平台应用

作者 Tim Kulp | 2016 年 2 月

作为企业级 Web 开发者,您对 HTML、CSS 和 JavaScript 有着深刻理解。您可以构建响应式 Web 应用程序,此类应用程序不仅能自适应屏幕尺寸,还能跨支持的浏览器或设备使用。您可以利用所掌握的 Web 技能来构建通用 Windows 平台 (UWP) 应用。无论您是要针对桌面设备、移动设备还是任何 Windows 10 平台构建应用,您从构建跨浏览器自适应 Web 应用程序中汲取的经验教训可以让您在 UWP 领域占据先机。

在本文中,我探讨了如何利用 Web 开发知识来构建跨浏览器应用程序,以及在任意 Windows 设备上运行的灵活 UWP 应用。为此,我首先要探究的是,如何将构建响应式接口的基础知识从适用于 CSS 和 HTML 转换成适用于 UWP。接下来,我将探究如何使用 VisualStates 和 XAML View,根据具体的设备功能构建应用。最后,我将使用自适应代码,根据普通的旧 JavaScript 用于定位具体浏览器的方式来定位设备。

为什么 Web 开发者要使用 XAML?

在本文中,我将探寻 Web 开发与 XAML 的相似之处。Web 开发者可能已熟练掌握一系列 HTML/CSS/JavaScript 技能,并希望利用这些技能来构建 UWP。如果您喜爱使用 HTML 和 JavaScript,也可以继续使用下去。如果您是第一次接触 UWP,不知道从何处入手,则可以使用 XAML。这是一款非常不错的工具,具有强类型性质,可为您提供帮助。

常见的示例为,在 XAML 与 HTML 中使用基于网格的布局。在 XAML 中,构建网格布局时,请先添加网格控件,定义列和行,然后向特定的单元格或行分配网格中的每个控件。在 HTML 中,构建网格布局的方式有许多种,如:

  • 结合使用 float 和定义的高度和宽度,以借助 clear 创建单元格,从而开始使用新的一行
  • 在容器元素上使用 display:grid,其中每个子元素都定义了列和行
  • 使用包含 tr 和 td 元素的表

具体实现哪种方法都取决于您的 CSS 或 HTML 知识储备,而且这些方法不会在您使用 IntelliSense 等工具时为您提供帮助,因为网格控件将在 XAML 中。借助 XAML 的强类型控件,您可以更轻松地了解如何通过 IntelliSense 构建 UI。这对于第一次接触 UWP 的开发者来说,极有帮助。

在进行疑难解答时,强类型代码也增加了许多价值。在 HTML/CSS/JavaScript 中,开发者可以非常灵活地掌握规则,从而满足使用松散类型代码的应用的要求。这对构建应用来说非常不错,但对提供应用支持来说可能会变成一场噩梦。在类型变化或对象动态变化时,对松散类型代码进行疑难解答可能充满挑战。作为企业级开发者,构建应用固然有趣,但有时也需要有人确保此应用能够一直运行下去。通过强类型对象和 IntelliSense 能够轻松导航应用功能,这有助于支持团队了解应用。

如果您非常崇尚 HTML/CSS/JavaScript,那么 UWP 也会为您提供平台,以供您使用现有代码来构建非凡的应用。HTML/CSS/JavaScript 和 XAML 都是非常不错的工具,兼具许多利弊。您可以参阅两篇文章,其中一篇介绍了为什么作者首选 XAML 而不是 JavaScript (bit.ly/1NxUxqh),另一篇则介绍了为什么作者首选 JavaScript 而不是 XAML (bit.ly/1RSLZ2G)。虽然我喜欢使用 HTML 构建 Web 应用程序,但如果您是第一次接触 UWP,那么我建议您探索 XAML,以了解 UWP 中的控件、借助强类型代码和充分的 IntelliSense 集成来降低您团队的支持费用,同时享受学习新知识所带来的乐趣。

利用现有知识进行构建

UWP 与 Web 设计的基础知识有许多相似之处。Web 开发中的 HTML 和 JavaScript 关注分离等基本概念在 UWP 中同样也适用于 XAML 和 XAML.cs 代码隐藏文件。所有逻辑都保留在代码隐藏文件中,而所有展示则都保留在 XAML 文件中(就像所有逻辑都保留在 JavaScript 中,而借助于 CSS 所有展示则都保留在 HTML 中一样)。而且,许多新型 Web 应用程序都利用 Knockout 和 AngularJS 等框架,从而通过“模型-视图-视图模型 (MVVM)”设计模式来实现数据绑定。这些框架和 MVVM 的知识基本上就是了解 UWP 中数据绑定的基础。虽然 Web 开发与 UWP 中的语法不一样,但对于基本概念,Web 开发者在构建跨设备、跨浏览器和跨功能的应用方面具有坚实基础。

在本文中,我不会介绍 Web 开发与 UWP 之间的区别,如状态管理和数据存储。对于文本,我将重点介绍如何构建 UI 以及确保应用可以与所在设备进行交互。

位置: 从 float 和 clear 到 RelativePanel

在 HTML 中,您是通过每个元素在文档对象模型中的位置来确定元素的位置。HTML 采用由上而下设计,每个元素都是按从声明的第一个元素到最后一个元素的顺序进行呈现。在引入 CSS 后,它通过设置元素的显示样式(内联、块等)、位置(相对或绝对)以及 float 和 clear,让元素有了复杂的布局。使用 float,Web 开发者可以将 HTML 元素移出由上而下的流,并将此元素放置到包含元素的左侧 (float: left) 或右侧 (float: right)。

假设简易布局包括标头、主要内容、边栏和页脚。使用 float 指示浏览器在容器的右侧呈现边栏,并在容器的左侧呈现主要内容。使用 float,可以在左侧或右侧并排放置元素,具体取决于所指定的 float 值。clear 用于停止元素浮动,并恢复到标准的由上而下 HTML 流。图 1 中的示例展示了如何使用 float 构建简易布局。

图 1:使用 float 构建简易布局

div {
  width: 100%;
}
mainContent {
  width: 60%; float: left;
}
  sidebar{
  width: 40%; float: right;
}
clearer {
  clear: both;
}
CSS for design
<header>
</header>
<div>
  <section class="content"></section>
  <section class="sidebar"></section>
  <div class="clearer"></div>
</div>
<footer>
</footer>
HTML for design

Web 开发者使用 float 和 clear 创建布局,而 UWP 则提供一种名为“RelativePanel”的控件。顾名思义,此控件是通过使用与其他控件的相对关系来定义布局。与 float 类似,RelativePanel 允许开发者管理控件与定位控件的相对位置。CSS 类用于确定网页元素的位置。为了重现相同的布局,请在控件内使用 RelativePanel 及其附加属性:

<RelativePanel>
  <!-- Header is the anchor object for the relative panel -->
  <TextBlock Text="Header" Name="tbHeader"></TextBlock>
  <TextBlock Text="Content" RelativePanel.Below="tbHeader"
    Name="tbContent"></TextBlock>
  <TextBlock Text="SideBar" RelativePanel.RightOf="tbContent"
    RelativePanel.Below="tbHeader" Name="tbSideBar"></TextBlock>
  <TextBlock Text="Footer" RelativePanel.Below="tbSideBar"
    Name="tbFooter"></TextBlock>
</RelativePanel>

在此代码块中,每个控件的位置与定位控件的位置是相对的(在此示例中,定位控件是标头 TextBlock)。使用 RelativePanel,可以为每个控件指定其在屏幕上相对于其他控件的位置。若要确定内容位置,Web 开发者会使用 float: left 或 float: right,而 UWP 开发者则会使用 RelativePanel.LeftOf 或 Relative­Panel.RightOf。虽然这与 float 的用法类似,但并无使用 clear 恢复正常流的概念;相反,请注意,每个元素都低于前一个元素。这就简化了布局问题疑难解答工作,因为对于未熟练掌握 CSS 技能的开发者来说,管理 float 和 clear 充满挑战。使用 RelativePanel 可以通过声明的方式指定控件相对于其他控件的位置。如果 RelativePanel 已关闭,那么应用会恢复 XAML 的正常呈现流(与 HTML 类似,也是由上而下流)。

缩放: 从百分比到像素

通过重设大小构建响应式 Web 应用程序意味着对元素使用相对尺寸。以包括标头、内容、边栏和页脚的页面布局为例,假设此 UI 最初是专为桌面屏幕而构建。Web 设计人员会先确定此布局的最佳页面宽度像素。在此示例中,页面宽度为 1000 像素。在设计构建每个元素时,以此宽度像素(紧记 1000 像素的容器)为标准构建元素。在 HTML 中,内容部分的宽度为 800 像素,而边栏部分的宽度则为 200 像素。按公式“目标/上下文 = 百分比”计算,内容部分在上下文中所占的百分比为 80%,而边栏在上下文中所占的百分比为 20%(鉴于上下文 = 1000 像素的页面)。

在 Web 设计中使用百分比可以像重设容器大小一样来重设布局大小。在此示例中,如果用户将 1000 像素的页面对象的大小重设为仅 659 像素,那么内容和边栏的大小会分别重设为 527 像素和 131 像素。同样,将样式构建为使用 em(而不是具体的点或像素尺寸)可以让字体根据上下文进行缩放。这些做法有助于确保设计成比例缩放,无论窗口大小如何。

虽然使用百分比就像是在做简单的数学运算,但元素缩放还涉及其他一些因素(如设备的像素密度和屏幕方向),这平添了设计的不可预测性。UWP 通过对所有度量值使用“有效像素”的概念,简化了缩放工作。有效像素并不等同于单一像素。有效像素使用 UWP 缩放算法,从而确定如何根据设备与用户之间的标准距离以及像素密度来呈现一个有效像素。

例如,Surface Hub 的像素密度要比平板电脑或手机大得多。UWP 开发者只需在 Blend 等工具中按有效像素进行构建即可,缩放算法可负责处理进行相应缩放时所必需的复杂计算。需要注意的一点是: 有效像素必须是 4 的倍数。根据缩放算法的工作原理,使用 4 的倍数可以确保在 UI 缩放时边界分明。

ViewState 和媒体查询

在 Web 开发中,CSS 提供应用程序的布局信息。虽然构建时使用百分比可以让应用程序重设大小,但有时也需要重构设计来满足不断变化的显示需求。专为平板电脑等移动设备构建的布局不会与专为 Surface Hub 等显示屏超过 80 英寸的设备构建的布局相同。对于 Web 设计,一个由来已久的类比是用户希望打印基于屏幕的设计。CSS 通过 CSS 媒体查询帮助设计人员走出了打印基于屏幕的设计的两难困境。在用户打印设计时,浏览器会使用打印 CSS,而不是屏幕 CSS。随着响应式 Web 设计不断发展,媒体查询已成熟到可以支持更详细的信息。下面是 CSS 媒体查询的一个示例:

<link type="text/css" rel="stylesheet" 
  href="styles/719style.css"
  media="screen and (max-device-width: 719px)"/>

在此查询中,如果显示 Web 应用程序的媒体是设备宽度不大于 719 像素的屏幕,那么会应用 719style.css 文件。例如,此媒体查询可用于清除 float 值,并采用堆叠设计与并排设计来展示内容和边栏元素。使用媒体查询,Web 开发者可以根据屏幕尺寸、分辨率、屏幕方向和其他许多选项来自定义显示(有关 CSS3 的完整列表,请访问 bit.ly/1riUA2h)。

在 UWP 中,ViewStateManager 可用作媒体查询,以便根据定义的参数来更改应用程序设计。VisualStateManager 包含一个或多个 VisualStateGroup,后者是包含多个 ViewState 的容器对象。每个 ViewState 均包含资源库(针对每个控件更新的属性)和触发器(触发资源库变化的因素)。ViewStateManager 管理触发器,以便确定何时应用特定的 ViewState 资源库值。就 CSS 而言,触发器就像是媒体查询,而资源库则是媒体查询中引用的样式表内的样式值。请参见图 2 中的示例。

图 2:基于 VisualState 触发器的重新定位控件

<VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="ResponseStateGroup">
    <VisualState x:Name="LessThan720">
      <VisualState.Setters>
      <Setter Target="tbSideBar.(RelativePanel.Below)" Value="tbContent"/>
        <Setter Target="tbSideBar.(RelativePanel.RightOf)" Value=""/>
      </VisualState.Setters>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="1"/>
      </VisualState.StateTriggers>
    </VisualState>
    <VisualState x:Name="GreaterThan720">
      <VisualState.Setters>
        <Setter Target="tbSideBar.(RelativePanel.Below)" Value="tbHeader"/>
        <Setter Target=" tbSideBar.(RelativePanel.RightOf)" Value="tbContent"/>
      </VisualState.Setters>
      <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="720"/>
      </VisualState.StateTriggers>
    </VisualState>
  </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

在此代码中,创建了两个 VisualState。如果窗口宽度介于 1 像素到 719 像素之间,则触发的是 LessThan720 ViewState。如果窗口展开至 720 像素或更大,则触发的是 GreaterThan720 ViewState。所有这些 ViewState 都与 tbSideBar 控件的 RelativePanel 设置兼容。如果窗口尺寸小于 720 像素,则屏幕不够大,无法支持内容与边栏并排的设计。在这种情况下,触发的是 LessThan720 ViewState,将边栏堆叠在主要内容 TextBlock 之下。这就类似于使用媒体查询,即可以在 LessThan719.css 文件中设置 float: none。

就像媒体查询一样,ViewState 可用于根据触发器来重新定位和重构控件。随着接口的复杂程度不断增加,管理 ViewState 和 ViewStateGroup 可能会变得非常复杂。管理复杂 ViewState 更改的最简单方法是使用 Blend for Visual Studio 2015。使用 Blend 的编辑器,您可以新建 VisualState,并能在更改应用设计的同时预览相应的更改。Blend 会为您处理所有 XAML 编写工作,并确保您提供触发 VisualState 更改所必需的数据。Microsoft Virtual Academy 在 bit.ly/1P94e32 中提供了许多有关使用 Blend 管理 ViewState 的演练视频。

使用 View 重构体验

有时,移动网站的 UI 并不相同,因为与完全的桌面体验相比,用例减少了,或者移动网站的焦点有所变化。在这种情况下,Web 开发者会为移动体验调整内容,以提供更加简化的体验,或突显移动设备的功能。使用各种检测方法,Web 开发者可以将用户重定向到专为其设备量身定制的重构体验站点,通常被称为 m.webapp.com 站点。

UWP 通过 View 提供相同功能。处理不同的设备系列可能需要完全不同的 UI(具体取决于应用),但这样一来,ViewStateManager 可能就不是最适合的工具了。借助 View,开发者可以结合使用现有的后端代码和新的 XAML UI。您可以通过使用结构良好的 ViewModel 来简化使用 View,尽管您可以通过多个 View 来利用一个 ViewModel 对象。Knockout 或 AngularJS 知识将有助于 Web 开发者在 UWP 中构建正确的 ViewModel,从而提供专为特定的设备系列量身定制的用户体验。

在应用的 View 文件夹中创建一个文件夹,即可为特定设备创建 View。

在 View 文件夹中,新建一个 Device­Family-Mobile 文件夹。这样就可以告知现代资源技术在移动设备系列中的设备(如手机)发出 MainPage 请求时,使用 DeviceFamily-Mobile 文件夹中的 MainPage.xaml 视图。如果 MainPage 请求来自其他任何设备系列,那么响应将是标准 MainPage。借助此功能,UWP 开发者可以为特定 Windows 设备系列专属用例构建有针对性的 UI。

自适应代码

在构建 Web 应用程序过程中,并不是所有浏览器都一样。在企业中,您可能需要遵循企业标准;但在为外部用户开发时,各种操作系统、浏览器类型和版本都会让复杂程度增加。Web 开发者有许多办法来适当地处理这些差异。虽然 Modernizr (modernizr.com) 等库降低了这些处理操作的复杂程度,但对 Web 开发者来说,根据设备或浏览器启用功能并不是什么新鲜事。

构建 UWP 应用与启用跨浏览器功能一样复杂。以记事应用程序为例。在此应用程序中,用户可以向备忘录中添加照片。此应用可以利用手机内置的照相机功能来拍摄照片,或者可以允许用户查看设备上的现有图像。

使用设备专用功能的第一步是确保项目中包含正确的扩展 API。例如,在记事应用中,必须能够在手机上使用硬件按钮。您必须先为 UWP 添加 Windows 移动扩展引用,然后才能使用这些按钮。为此,您可以添加项目引用(就像添加其他任何引用一样),然后依次选择“通用 Windows”和“扩展”。此时,系统会列出可能的扩展,如桌面、移动和团队(对于 Surface Hub)扩展。选择需要向项目添加的扩展,然后单击“确定”。

JavaScript 是通过一系列导航器检查来检测浏览器功能,而 UWP 应用则是通过 IsTypePresent 方法检查所需的 API 是否存在。在记事应用中,使用以下代码检查使用照相机所需的硬件按钮是否存在:

string apiName = "Windows.Phone.UI.Input.HardwareButtons";
if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(apiName))
{
  Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
    HardwareButtons_CameraPressed;
}

借助这段简短的代码,应用可以定位通过扩展 API 添加的特定设备功能。通过使用 IsTypePresent 方法包装 CameraPressed 事件处理程序声明,可确保您不会在没有 API 时注册此事件处理程序。有工具可帮助您确保执行 API 检查,这样应用就不会在没有 API 时发生故障了。PlatformSpecific 是一个绝佳的 NuGet 包,可简化识别和包装所有对扩展 API 的引用,此 API 最初未通过 ApiInformation.IsTypePresent 方法进行验证。若要详细了解此 NuGet 包,请访问 PlatformSpecific GitHub 站点 (bit.ly/1GvhkF0)。

就像在 Web 开发中一样,特定版本的浏览器有时也需要针对客户端或企业标准进行定位。在这种情况下,Web 开发者需要重点关注可能与 Internet 的其余部分使用的配置不匹配的特定浏览器配置。

同样,UWP 开发者可能需要定位扩展 API 的特定协定来维护现有代码。在企业应用程序中,这样做就非常有用,因为在此类应用程序中,IT 运营团队可能会通过快速循环和慢速循环向员工计算机部署更新。快速循环可能会利用一些扩展 API 的精彩新功能,这些功能必须立即在应用中实现。在这种情况下,慢速循环用户仍需要使用他们的功能。使用 IsApiContractPresent,UWP 可以检查扩展 API 是否存在,以及在执行代码之前所需的特定版本是否存在:

if(Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent(apiName, 3))
{
  newKillerFunction();
}

在此代码细分中,应用只有在提供的 apiName 为第 3 版时才运行 newKillerFunction。如果版本低于第 3 版,则不会运行 newKillerFunction。如果版本高于第 3 版(例如,第 4 版),则会执行 newKillerFunction。

总结

进行 UWP 开发时,需要用到 Web 开发者在构建响应式跨浏览器 Web 应用程序时掌握的许多技能和知识。设计布局、响应显示和系统功能差异(静态和动态)全都是 Web 开发者在处理各种 Web 浏览器时的常见任务。利用这些技能进行 UWP 开发将有助于您构建适应屏幕尺寸、设备和功能的丰富用户体验。


Tim Kulp是一名高级技术架构师,居住在美国马里兰州巴尔的摩。他身兼数职:Web 开发者、移动开发者、UWP 开发者、作者、画家、父亲以及“要成为疯狂科学家”奠基人。 请通过 Twitter @seccode 或 LinkedIn linkedin.com/in/timkulp 与他联系。

衷心感谢以下 Microsoft 技术专家对本文的审阅: Kevin Hill