孙广东 2015.8.24
介绍 XAML 语言和 XAML 概念,并介绍在使用 XAML 创建 Windows 运行时应用时,在 XAML 中声明对象和设置属性的不同方式。
什么是 XAML?
Extensible Application Markup Language (Extensible Application Markup Language, XAML) 是一种声明性语言。具体来讲,XAML 可初始化对象和设置对象的属性,使用一种可显示多个对象间分层关系的语言结构,还使用了一种支持类型扩展的支持类型约定。你可以在声明性的 XAML 标记中创建可视的 UI 元素。然后你可以为每个 XAML 文件关联一个独立的代码隐藏文件(.cs文件),以响应事件和处理最初在 XAML 中声明的对象。
在开发过程中,XAML 语言支持不同工具和角色之间的源代码交换,例如在设计工具与 IDE 或是主开发人员与本地化开发人员之间交换 XAML 源代码。通过将 XAML 用作交换格式,可以分开或整合设计人员角色和开发人员角色,并且设计人员和开发人员可以在开发应用期间迭代。
如果将它们视为 Windows 运行时应用项目的一部分,则 XAML 文件即是带 .xaml 扩展名的 XML 文件。
基本 XAML 语法
XAML 的基本语法基于 XML。依照定义,有效的 XAML 必须也是有效的 XML。但 XAML 也拥有可赋予不同且更加完整含义的语法概念,根据 XML 1.0 规范,它在 XML 中也有效。例如,XAML 支持属性元素语法,其中属性值可在元素中设置,而不是在属性中作为字符串值或内容进行设置。对于常规 XML 而言,XAML 属性元素是名称中带点号的元素,因此它对于纯 XML 有效,但具有不同的含义。
XAML 与 Microsoft Visual Studio
无论是在 XAML 文本编辑器中,还是在更为图形化的 XAML 设计界面中,Microsoft Visual Studio 都可以帮助你生成有效的 XAML 语法。因此在你使用 Visual Studio 为应用编写 XAML 时,不必时时担心语法问题。IDE 鼓励通过提供自动完成提示、在 Microsoft IntelliSense 列表和下拉列表中显示建议、在工具箱中显示 UI 元素库或其他技术等方式,来编写有效的 XAML 语法。如果这是你第一次使用 XAML,则当我们在参考或其他主题中介绍 XAML 语法时,了解语法规则可能仍然有用,特别是有时用于描述限制或选择的术语。
XAML 命名空间
在常规编程中,命名空间是一种组织概念,用于确定如何解释编程实体的标识符。通过使用命名空间,编程框架可将用户声明的标识符与框架声明的标识符区分开、通过命名空间限定消除标识符的歧义、强制执行范围名称规则等。XAML 具有为 XAML 语言实现此用途的其自己的 XAML 命名空间概念。下面是 XAML 应用并扩展 XML 语言命名空间概念的方式:
- XAML 可将保留的 XML 属性 xmlns 用于命名空间声明。该属性的值通常是一个统一资源标识符 (URI),它是从 XML 继承的约定。
- XAML 使用声明性前缀来声明非默认命名空间,并声明元素和属性中使用的前缀引用该命名空间。
- XAML 有一个叫做默认命名空间的概念,当不使用或声明任何现有前缀时,便会使用该命名空间。针对每一个 XAML 编程框架,可以对默认命名空间进行不同的定义。
- 在 XAML 文件或构造中,命名空间定义从父元素继承到子元素。例如,如果你在 XAML 文件的根元素中定义命名空间,该文件中的所有元素都将继承该命名空间定义。如果以后添加到该页面中的某个元素重新定义了命名空间,则该元素的后代将继承新定义。
- 一个元素的属性继承该元素的命名空间。XAML 属性很少有前缀。
XAML 文件几乎总是在其根元素中声明一个默认 XAML 命名空间。默认 XAML 命名空间定义了无需使用前缀来限定即可声明哪些元素。对于典型的 Windows 运行时应用项目,此默认命名空间包含用于 UI 定义的所有 Windows 运行时的内置 XAML 词汇:默认控件、文本元素、XAML 图形和动画、数据绑定和样式支持类型等。因此,为 Windows 运行时应用编写的大部分 XAML 在引用常见 UI 元素时都将避免使用 XAML 命名空间和前缀。
如下代码段显示了用模板创建的一个应用初始页面的 Page 根(仅显示了开始标记并且进行了简化)。它声明了默认命名空间,还有 x 命名空间(将在下面加以介绍)。
<Page
x:Class="Application1.BlankPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
XAML 语言 XAML 命名空间
一种在几乎每个 Windows 运行时 XAML 文件中都会声明的特定的 XAML 命名空间是 XAML 命名空间。此命名空间包括由 XAML 语言按其语言规范定义的元素和概念。根据约定,XAML 语言 XAML 命名空间会映射到前缀 "x"。Windows 运行时应用项目的默认项目和文件模板始终会将默认 XAML 命名空间(没有前缀,只有 xmlns=
)和 XAML 语言 XAML 命名空间(前缀为 "x")定义为根元素的一部分。
"x" 前缀/XAML 语言 XAML 命名空间包含你在 XAML 中经常使用的多种编程结构。下面是一些最常见的结构:
术语 |
描述 |
x:Key |
为 XAML ResourceDictionary 中的每个资源设置一个唯一的用户定义密钥。该密钥的令牌字符串是 StaticResource 标记扩展的参数,你可以在以后使用此密钥在应用 XAML 的其他位置检索其他 XAML 用法中的 XAML 资源。 |
x:Class |
为 XAML 页面提供代码隐藏的类指定代码命名空间和代码类名称。这可为构建你的应用时通过构建操作创建或加入的类命名。这些构建操作支持 XAML 标记编译器,并在编译应用时,将你的标记和代码隐藏文件组合到一起。你必须具有此类,才能支持对 XAML 页面实现代码隐藏。还需要 x:Class,才能使你的 XAML 内容在默认 Windows 运行时激活模型中初始化为 Window.Content。 |
x:Name |
在处理 XAML 中定义的对象元素后,为运行时代码中存在的实例指定一个运行时对象名。你可以将在 XAML 中设置 x:Name 看作是在代码中声明命名变量。稍后你会了解,这是将 XAML 加载为 Windows 运行时应用的一个组件时发生的实际情况。
注意
FrameworkElement.Name 是框架中的一个类似属性,并非所有元素都支持它。因此,当
FrameworkElement.Name 在该元素类型上不受支持时,你可以将
x:Name 用于元素标识。
|
x:Uid |
标识某些元素,应将本地化后的资源用于该元素的一些属性值。有关如何使用 x:Uid 的详细信息,请参阅快速入门:翻译 UI 资源。 |
XAML 固有类型 |
当属性或资源需要时,这些类型可以为简单的值类型指定值。这些固有类型与通常定义为每个编程语言固有定义的一部分的简单值类型相对应。例如,你可能需要一个表示 true 布尔值的对象,以便在 ObjectAnimationUsingKeyFrames 情节提要视觉状态中使用。对于 XAML 中的该值,可将 x:Boolean 固有类型用作对象元素,例如 <x:Boolean>True</x:Boolean> |
在 XAML 语言 XAML 命名空间中还存在其他编程结构,但没有这么常用。有关详细信息
将自定义类型映射到 XAML 命名空间
作为一种语言,XAML 功能最强大的方面之一在于,可以轻松地扩展 Windows 运行时应用的 XAML 词汇。你可以使用应用的编程语言定义自己的自定义类型,然后在 XAML 标记中引用你的自定义类型。从根本上讲,通过自定义类型提供的扩展支持已经内置于 XAML 语言的工作原理。框架或应用开发人员负责创建 XAML 引用的支持对象。框架和应用开发人员都不会因为这些对象所代表的规范或不符合基本 XAML 语法规则而受到限制(对于 XAML 语言 XAML 命名空间类型的行为有一些预期要求,但 Windows 运行时会提供所有必要支持)。
如果你为来自非 Windows 运行时核心库和元数据的类型使用 XAML,则必须声明和映射带前缀的 XAML 命名空间。在元素用法中使用该前缀以引用已在你的库中定义的类型。通常,你可以在根元素以及其他 XAML 命名空间定义中将前缀映射声明为 xmlns 属性。
要使你自己的命名空间定义引用自定义类型,你首先需要指定关键字 xmlns:,然后指定所需的前缀。 该属性的值必须包含关键字 using:,将其作为该值的第一部分。该值的其余部分为字符串令牌,它按名称引用特定的代码支持的命名空间,并且其中还包含你的自定义类型。
该前缀定义的标记令牌用于在该 XAML 文件中标记的剩余部分中引用该 XAML 命名空间。前缀与要在 XAML 命名空间中引用的实体之间有一个冒号字符 (:)。
例如,将前缀 myTypes
映射到命名空间 myCompany.myTypes
的属性语法为: xmlns:myTypes="using:myCompany.myTypes"
,而代表的元素用法为:<myTypes:CustomButton/>
有关为自定义类型映射 XAML 命名空间的详细信息,包括 Visual C++ 组件扩展 (C++/CX) 的特殊注意事项。
其他 XAML 命名空间
你常常会看到定义前缀 "d"(设计器命名空间)和 "mc"(用于实现标记兼容性)的 XAML 文件。它们通常用于基础结构支持,或者用于启用设计时工具中的方案。有关详细信息。
标记扩展
标记扩展是一个 XAML 语言概念,常常用在 Windows 运行时 XAML 实现中。标记扩展通常表示某种“快捷方式”,它允许 XAML 文件访问无法只根据支持类型声明元素的值或行为。某些标记可通过纯字符串或其他嵌套的元素设置属性,目的是简化不同 XAML 文件之间的语法或因素处理。
在 XAML 属性语法中,花括号 "{" 和 "}" 表示 XAML 标记扩展用法。此用法指示 XAML 处理从对属性值的一般处理转义为处理文字字符串,或者处理一个可直接进行字符串转换的值。相反,XAML 分析器将调用提供该特定标记扩展行为的代码,以及提供 XAML 分析器所需的备用对象或行为结果的代码。标记扩展可以具有参数,这些参数遵循标记扩展名称,也包含在花括号中。通常,评估的标记扩展可提供对象返回值。分析期间,该返回值将插入到对象树的某个位置中,在该位置中标记扩展用法已位于源 XAML 中。
Windows 运行时 XAML 支持在默认 XAML 命名空间下定义并可被 Windows 运行时 XAML 分析程序理解的标记扩展:
- Binding:支持数据绑定,它会延迟一个属性值,直到该值在仅在运行时存在的数据上下文下被解释。 此标记扩展支持很多参数。有关详细信息,请参阅绑定标记扩展。
- StaticResource:支持引用在一个 ResourceDictionary 中定义的资源值。这些资源可以位于不同的 XAML 文件中,但最终必须在加载时可供 XAML 分析器查找。
{StaticResource}
用法的参数标识 ResourceDictionary 中的键控资源的键(名称)。
- ThemeResource:类似于 StaticResource 但可响应运行时主题变更。ThemeResource 经常位于 Windows 运行时的默认 XAML 模板中,因为其中的大部分模板专门用于用户在应用运行时切换主题的兼容性。
- TemplateBinding:Binding 的一种特殊情况,它支持 XAML 中的控制模板及其在运行时的最终用法。
- RelativeSource:支持一种特定形式的模板绑定,其中的值来自于父模板。
- CustomResource:适用于资源查找的高级场景。
Windows 运行时还支持 x:Null 标记扩展。 你可以使用它在 XAML 中将 Nullable 值设置为 null。例如,你可以将其用于 CheckBox 的控制模板,这会将 null 解释为不确定的选中状态(从而引发 "Indeterminate" 视觉状态)。
标记扩展一般会从应用的对象图的其他某个部分返回一个现有实例,或者将一个值延迟到运行时。因为可以将标记扩展用作属性值,并且这是典型用法,所以你经常可以看到标记扩展被用来为引用类型的属性提供值,而采用其他方式时,这些属性可能需要使用属性元素语法。
例如,下面是用于从 ResourceDictionary 中引用可重用的 Style 的语法:<Button Style="{StaticResource SearchButtonStyle}"/>
。Style 是一种引用类型而非一个简单值,因此如果没有 {StaticResource}
用法,你需要使用 <Button.Style>
属性元素和其中的 <Style>
定义来设置 FrameworkElement.Style 属性。
通过使用标记扩展,在 XAML 中可设置的所有属性都可能采用属性语法进行设置。你可以使用属性语法提供一个属性的引用值,即使它不支持通过其他方法用于直接对象实例化的属性语法也是如此。或者可以实现一种特定的行为,通过值类型或新创建的引用类型来延迟需填充 XAML 属性的一般需求。
为举例说明,接下来的 XAML 示例使用属性语法来设置一个 Border 的 Style 属性的值。Style 属性接受 Style 类的一个实例,一个在默认情况下无法使用属性语法字符串创建的引用类型。但在本例中,该属性引用一个特定的标记扩展 StaticResource。当处理该标记扩展时,它返回对之前已被定义为资源字典中的键控资源的一个 Style 元素的引用。
<Canvas.Resources>
<Style TargetType="Border" x:Key="PageBackground">
<Setter Property="BorderBrush" Value="Blue"/>
<Setter Property="BorderThickness" Value="5"/>
</Style>
</Canvas.Resources>
...
<Border Style="{StaticResource PageBackground}">
...
</Border>
你可以嵌套使用标记扩展。会首先计算最里层的标记扩展。
由于标记扩展的缘故,你需要使用特殊语法来表示属性中的 "{" 文本值。
事件
XAML 是一种用于对象及其属性的声明性语言,但它也包含向标记中的对象附加事件处理程序的语法。XAML 事件语法可通过 Windows 运行时编程模型集成 XAML 声明的事件。在处理事件的对象上,将事件的名称指定为一个属性名称。对于属性值,指定你在代码中定义的一个事件处理函数的名称。XAML 处理器使用此名称在加载的对象树中创建一个委托表示,将指定的处理程序添加到内部处理程序列表中。 几乎所有 Windows 运行时应用都是同时通过标记和代码隐藏源文件定义的。
下面是一个简单示例。Button 类支持一个名为 Click 的事件。你可以为 Click 编写处理程序,用以运行在用户单击 Button 之后应当调用的代码。在 XAML 中,你可以将 Click 指定为 Button 上的一个属性。对于属性值,请提供一个表示你的处理程序的方法名称的字符串。
<Button Click="showUpdatesButton_Click">Show updates</Button>
当你进行编译时,编译器现在预期在代码隐藏文件中将有一个已定义的名为
showUpdatesButton_Click
的方法,位于 XAML 页的 x:Class 值中声明的命名空间中。另外,该方法必须满足
Click 事件的委派合约。例如:
namespace App1
{
public sealed partial class MainPage: Page {
...
private void showUpdatesButton_Click (object sender, RoutedEventArgs e) {
//your code
}
}
}
在一个项目中,将 XAML 编写为 .xaml 文件,使用你最喜欢的语言(C#、Visual Basic、C++/CX)编写一个代码隐藏文件。当在项目的生成操作中对 XAML 文件进行标记编译时,可以通过将一个命名空间和类指定为 XAML 页面根元素的 x:Class 属性来标识每个 XAML 页面的 XAML 代码隐藏文件的位置。 有关这些机制在 XAML 中如何工作以及它们与编程和应用程序模型之间的关系的详细信息。
注意 对于 C++/CX,存在两个代码隐藏文件,一个是标头 (.xaml.h),另一个是实现 (.xaml.cpp)。该实现引用标头,从技术上讲,标头表示代码隐藏连接的入口点。
资源字典
创建 ResourceDictionary 是一种常见任务,这通常是通过将资源字典创建为 XAML 页面的一个区域或创建为一个单独的 XAML 文件来完成的。资源字典以及如何使用资源字典是一个非常大的概念区域,不在本主题的讨论范围内。
XAML 语言基本上基于 XML 语言。
仅支持 UTF-8 和 UTF-16 编码。不支持 UTF-32 编码。
XAML 中区分大小写
XAML 中是区分大小写的。这是 XAML 基于 XML 的另一个后果,XML 是区分大小写的。XAML 元素和属性的名称是区分大小写的。属性的值可能区分大小写,这取决于为特定属性处理属性值的方式。例如,如果属性值声明一个枚举的成员名称,将成员名称字符串进行类型转换,以返回枚举成员值的内部行为是不区分大小写的。相反,Name 属性的值和基于 Name 属性声明的名称来处理对象的实用程序方法会对名称字符串区分大小写。
XAML 名称范围
XAML 语言定义了一个 XAML 名称范围的概念。XAML 名称范围概念会影响 XAML 处理器在处理可用于 XAML 的 x:Name 或 Name 值时的方式,特别是依赖名称成为唯一标识符的领域。XAML 名称范围将在一个单独的主题中详细介绍.
XAML 在开发过程中的作用
XAML 在应用开发过程中发挥着多项重要作用。
- 如果使用 C#、Visual Basic 或 C++/CX 编程,XAML 是声明应用的 UI 和该 UI 中各个元素的主要格式。通常,项目中至少有一个 XAML 文件表示应用中最初显示的 UI 的页面隐喻。更多的 XAML 文件可以为导航 UI 声明更多页面。其他 XAML 文件可声明资源,例如模板或样式。
- 使用 XAML 格式为应用的控件和 UI 声明样式和模板。
- 你可以使用样式和模板将现有控件模板化,或者定义一个控件,在一个控件包中提供默认模板。当使用 XAML 格式定义样式和模板时,相关的 XAML 常常使用 ResourceDictionary 根声明为独立的 XAML 文件。
- XAML 是设计器用于支持创建应用 UI 和在不同设计器应用之间交换 UI 设计的通用格式。最重要的是,应用的 XAML 可在不同的 XAML 设计工具(或工具中的设计窗口)之间交换。
- 其他一些技术也使用 XAML 定义基本 UI。相对于 Windows Presentation Foundation (WPF) XAML 和 Microsoft Silverlight XAML,Windows 运行时的 XAML 使用同一个适用于其共享的默认 XAML 命名空间的 URI。Windows 运行时的 XAML 词汇表与 Silverlight 使用的 XAML-for-UI 词汇表具有大量重复内容,与 WPF 所使用的词汇表的重复内容稍微少一些。因此,XAML 为最初为也使用 XAML 的早期技术定义的 UI 提供了一种有效的迁移路径。
- XAML 定义 UI 的可视外观,一个关联的代码隐藏文件定义逻辑。无需更改代码隐藏文件中的逻辑,即可调整 UI 设计。XAML 简化了设计人员与开发人员之间的工作流。
- 得益于丰富的可视设计器和对 XAML 语言的设计图面的支持,XAML 支持在早期开发阶段快速设计 UI 原型。
取决于你自己在开发流程中的角色,你可能不会与 XAML 进行太多交互。你与 XAML 文件的交互程度还取决于你使用哪个开发环境、是否使用交互式设计环境功能(例如工具箱和属性编辑器),以及 Windows 运行时应用的范围和用途。但是可能在应用开发期间,你将在元素级别使用文本或 XML 编辑器来编辑 XAML 文件。使用此信息,你可以以文本或 XML 表示形式自信地编辑 XAML,并在工具、标记编译操作或 Windows 运行时应用的运行时阶段使用 XAML 文件时保持该文件的声明和用途的有效性。
优化你的 XAML 以提高加载性能
在使用 XAML 定义 UI 元素时,对于使用有利于性能的最佳做法,有一些技巧。这些技巧中有许多与使用 XAML 资源有关,但为了方便起见,在此一般 XAML 概述中还是将它们列了出来。要了解更多有关性能的提示,包括特意展示在 XAML 中应避免的某些欠佳性能做法的 XAML。
- 如果你经常在 XAML 中使用相同的颜色画笔,请将 SolidColorBrush 定义为一个资源,而不是每次都使用某种已命名的颜色作为属性值。
- 如果你在一个 UI 页面上多次使用同一资源,请考虑在 Application.Resources 中而不是在每个页面上定义该资源。反之,如果只有一个页面使用某个资源,则不要在 Application.Resources 中定义该资源,而只为需要该资源的页面定义它。这对设计应用时的 XAML 构造和 XAML 分析过程中的性能都有好处。
- 对于你的应用打包的资源,请检查是否有未使用的资源(这类资源具有键,但在你的应用中没有 StaticResource 引用使用该资源)。在发布你的应用之前,请从 XAML 中删除这些资源。
- 如果你使用了提供设计资源的单独 XAML 文件 (MergedDictionaries),请考虑从这些文件中注释掉或者删除未使用的资源。即使你有在多个应用中使用的共享 XAML 起始点或者有为你的所有应用提供共用资源的共享 XAML 起始点,你的应用仍然每次都会打包 XAML 资源,并可能需要加载它们。
- 请不要定义组合所不需要的 UI 元素,并尽可能地使用默认的控件模板(这些模板已经过加载性能方面的测试和验证)。
- 请使用 Border 之类的容器而不要故意过度绘制 UI 元素。一般情况下,请不要多次绘制同一像素。有关过度绘制和如何进行相应测试的详细信息,请参阅 DebugSettings.IsOverdrawHeatMapEnabled。
- 针对 ListView 或 GridView 使用默认的项目模板;这些模板具有特殊的 Presenter 逻辑,用于在为大量列表项构建可视化树时解决性能问题。
调试 XAML
因为 XAML 是一种标记语言,所以用于在 Visual Studio 中调试的一些典型策略不可用。例如,无法在 XAML 文件中设置断点。不过,有其他的技术可帮助你调试与 UI 定义或其他 XAML 标记相关的问题,与此同时,你仍然可开发你的应用。
当 XAML 文件存在问题时,最典型的结果是某些系统或你的应用不会引发 XAML 分析异常。只要存在 XAML 分析异常,由 XAML 分析程序加载的 XAML 就无法创建有效的对象树。在某些情况下,比如当 XAML 声明作为根视觉对象加载的应用程序的第一“页”时,XAML 分析异常不可恢复。
通常在 IDE(例如 Visual Studio 及其某一个 XAML 设计图面)中编辑 XAML。Visual Studio 通常可提供设计时的验证,以及在编辑 XAML 源时的错误检查。例如,只要你键入无效的属性值,它可能在 XAML 文本编辑器中显示“曲线”,而且你甚至不必等待 XAML 编译通过,就可看到 UI 定义中的错误。
一旦应用真正运行,如果任何 XAML 分析错误在设计时未经检测,公共语言运行时 (CLR) 就会报告这些错误作为 XamlParseException。有关可以对运行时 XamlParseException 执行的操作的详细信息,请参阅采用 C# 或 Visual Basic 的 Windows 运行时应用的异常处理。
注意 针对代码使用 C++/CX 的应用不能获取特定
XamlParseException。但是该异常与
XamlParseException 一样,包含的消息可阐明错误源与 XAML 相关,并且在 XAML 文件中包括上下文信息(例如行数)。
基本 XAML 语法指南
XAML 词汇
XAML 与大部分 XML 用法之间的一大区别在于,XAML 通常并非通过架构(如 XSD 文件)执行。这是因为 XAML 具有固有的可扩展性,在 Extensible Application Markup Language (XAML) 的缩略词中,"X" 正是此意。在对 XAML 进行分析后,发现你在 XAML 中引用的元素和属性预期将以某种支持代码表示的形式存在,要么是由 Windows 运行时定义的核心类型,要么是扩展的类型或基于 Windows 运行时的类型。有时 SDK 文档引用的类型已经内置到 Windows 运行时,并且作为 Windows 运行时的 XAML 词汇,可以在 XAML 中使用。Microsoft Visual Studio 可帮助你生成在此 XAML 词汇中有效的标记。 Visual Studio 还可以包含适用于 XAML 的自定义类型,只需在项目中正确引用这些类型的源即可。有关 XAML 和自定义类型的详细信息。
声明对象
程序员常常从对象和成员方面思考,而标记语言已概念化为各种元素和属性。在大部分基本场景中,在 XAML 标记中声明的元素会变为支持运行时对象表示中的对象。若要为应用创建运行时对象,需要在 XAML 标记中声明一个 XAML 元素。当 Windows 运行时加载 XAML 时,会创建该对象。
一个 XAML 文件始终只有一个用作根的元素,该元素声明的对象将在概念上作为一些编程结构的根,这些结构包括页面或一个应用程序完整运行时定义的对象图等。
对于 XAML 语法,可采用 3 种方式在 XAML 中声明对象:
- 直接方式,使用对象元素语法:该方式使用起始和结束标记将一个对象实例化为 XML 格式的元素。你可以使用此语法声明根对象或创建可设置属性值的嵌套对象。
- 间接方式,使用属性语法:该方式使用一个内联字符串值,该值中包含有关如何创建对象的说明。XAML 分析程序使用该字符串将属性值设置为新创建的引用值。 对于它的支持仅限于某些常见对象和属性。
- 使用一种标记扩展。
这并不意味着你始终可以选择任何语法来在 XAML 词汇表中创建对象。一些对象只能使用对象元素语法创建,一些对象只能通过最初在属性中设置来创建。事实上,可使用对象元素或属性语法创建的对象在 XAML 词汇表中相对较少。即使这两种语法形式都可用,其中一种语法样式也将更常见。
还可在 XAML 中使用一些技术来引用现有的对象,而不是创建新值。现有对象可以在其他 XAML 区域定义,也可以通过平台的某种行为和它的应用程序或编程模型显式存在。
使用对象元素语法声明对象
若要使用对象元素语法声明对象,可以编写类似于 <objectName>< /objectName>
的标记,其中 objectName 是你希望实例化的对象的类型名称。下面说明如何使用元素来声明 Canvas 对象:
<Canvas>
</Canvas>
如果一个对象不包含其他对象,可以使用一个自结束标记代替起始/结束标记对来声明对象元素:<Canvas />
容器
许多用作 UI 元素的对象(例如 Canvas)可包含其他对象。 这些对象有时称作容器。下面的示例显示了一个 Canvas 容器,该容器仅包含一个元素对象,即 Rectangle。
<Canvas>
<Rectangle />
</Canvas>
使用属性语法声明对象
由于此行为绑定到属性设置,因此我们将在接下来的几个部分中深入讨论这一点。
初始化文本
对于某些对象,可以使用将用作构造初始化值的内部文本来声明新值。在 XAML 中,这种技术和语法称为初始化文本。在概念上,初始化文本与调用具有参数的构造函数相似。 初始化文本对设置某些结构的初始值很有用。
如果想要一个具有 x:Key 值并因此可存在于 ResourceDictionary 中的结构实例,通常会使用具有初始化文本的对象元素语法。如果你要在多个目标属性之间共享该结构值,你可能会这么做。对于某些结构,无法使用属性语法设置结构的值:初始化文本是生成有用而且可共享的 CornerRadius、Thickness、GridLength 或 Color 资源的唯一方式。
这个简短示例使用初始化文本来指定 Thickness 的值,在本例中指定的值将 Left 和 Right 都设置为 20,将 Top 和 Bottom 都设置为 10。此示例显示了创建为键资源的 Thickness,还给出了该资源的引用。
<UserControl ...>
<UserControl.Resources>
<Thickness x:Key="TwentyTenThickness">20,10</Thickness>
....
</UserControl.Resources>
...
<Grid Margin="{StaticResource TwentyTenThickness}">
...
</Grid>
</UserControl ...>
注意 一些结构可以声明为对象元素。初始化文本不受支持,而且不能用作资源。你必须使用属性语法才能在 XAML 中将属性设置为这些值。这些类型包括:
Duration、
RepeatBehavior、
Point、
Rect 和
Size。
设置属性
你可以在使用对象元素语法声明的对象上设置属性。可采用多种方式在 XAML 中设置属性:
- 使用属性语法。
- 使用属性元素语法。
- 使用元素语法,其中的内容(内部文本或子元素)用于设置对象的 XAML 内容属性。
- 使用集合语法(通常是隐式的集合语法)。
XAML 与空格
了解 XAML 中使用的空格处理规则。
空格处理
与 XML 一样,XAML 中的空格字符是空白、换行和制表符。它们分别对应于 Unicode 值 0020、000A 和 0009。 默认情况下,在 XAML 处理器遇到 XAML 文件中各元素之间的任何内部文本时,会执行以下空格规范化操作:
- 删除东亚字符之间的换行字符。
- 将所有空格字符(空白、换行、制表符)转换为空白。
- 删除所有连续的空格并替换为一个空格。
- 删除紧挨起始标记之后的空格。
- 删除紧挨结束标记之前的空格。
- 东亚字符定义为从 U+20000 到 U+2FFFD 和从 U+30000 到 U+3FFFD 的 Unicode 字符集。这个子集有时也称为“CJK 象形文字”。有关详细信息,请参阅 http://www.unicode.org。
“默认”对应于 xml:space 属性的默认值所表示的状态。
内部文本中的空格和字符串原语
上面的规范化规则适用于 XAML 元素中的内部文本。规范化后,一个 XAML 处理器将任何内部文本转换为合适的类型,如下所示:
- 如果属性的类型不是集合,但也不是一种直接的 Object 类型,XAML 处理器将尝试使用它的转换器转换为该类型。此处失败的转换将导致 XAML 分析错误。
- 如果属性的类型是一个集合,并且内部文本是连续的(中间没有元素标记),内部文本会分析为单个 String。 如果集合类型不能接受 String,这也会导致 XAML 分析器错误。
- 如果属性的类型是 Object,那么内部文本会分析为单个 String。如果中间没有元素标记,这将导致 XAML 分析器错误,因为 Object 类型标识单个对象(String 或其他对象)。
- 如果属性类型是一个集合,并且内部文本不是连续的,那么第一个子字符串转换为一个 String 并添加为一个集合项,中间元素添加为一个集合项,最后的结尾子字符串(如果有)作为第三个 String 项添加到集合中。
空格和文本内容模型
在实际中,保留空格仅是所有可能的内容模型的一个子集。该子集包含可接受某种形式的单独 String 类型的内容模型、一个专用的 String 集合,或者 String 和列表、集合或词典中的其他类型的组合。
甚至对于可接受字符串的内容模型,这些内容模型内的默认行为也不会重视任何保留的空格。
保留空格
可以采用多种技术在源 XAML 中保留空格,使最终表示不会受 XAML 处理器空格规范化的影响。
xml:space="preserve"
:在希望保留空格的元素级别上指定此属性。请注意,这会保留所有空格,包括可能由代码编辑器或设计界面添加的,使标记元素以一种直观嵌套方式对齐的空格。是否显示这些空格由包含元素的内容模型负责。我们不建议在根级别指定 xml:space="preserve"
,因为大部分对象模型都不会将空格视为一种重要的方式。一种更好的做法是,仅在呈现字符串内的空格的元素级别上专门设置该属性,或者在空格很重要的集合中设置该属性。
实体和不间断空白:XAML 支持在一个文本对象模型中放置任何 Unicode 实体。你可以使用专门的实体,例如不间断空白(在 UTF-8 编码中)。你也可以使用支持不间断空白字符的富文本控件。如果使用实体来模拟缩进等布局字符,请保持谨慎,因为不同于一般的布局工具(例如面板和边距的适当使用),基于大量因素,实体的运行时输出将不同。
XAML 名称范围
XAML 名称范围存储 XAML 定义的对象名称和它们的对等实例之间的关系。此概念类似于其他编程语言和技术中的术语名称范围的更广泛的含义。
定义 XAML 名称范围的方式
XAML 名称范围中的名称使用户代码能够引用最初在 XAML 中声明的对象。分析 XAML 的内部结果是,运行时创建一组对象,保留这些对象在 XAML 声明中拥有的部分或所有关系。这些关系作为所创建对象的特定对象属性来维护,或者向编程模型 API 中的实用工具方法公开。
对于 XAML 名称范围中的名称,其最典型的用途是作为对象实例的直接引用,由标记编译过程以一种项目生成操作的形式,结合分部类模板中生成的 InitializeComponent 方法来实现。
你也可以在运行时自行使用实用工具方法 FindName 返回对象的引用,该对象使用 XAML 标记中的名称定义。
有关生成操作和 XAML 的详细信息
从技术上讲,所发生的事情是,在 XAML 和它为代码隐藏定义的分部类一起编译时,XAML 本身也会经历标记编译器过程。每个在标记中定义了 Name 或 x:Name 属性的对象元素都会生成一个内部字段,该字段的名称与 XAML 名称相匹配。此字段最初是空的。然后,该类生成一个 InitializeComponent 方法,只有在加载了所有 XAML 之后才会调用该方法。在 InitializeComponent 逻辑内,然后会向每个内部字段填充每个等效名称字符串的 FindName 返回值。 要自行查看此基础结构,可以在编译后查看 Windows 运行时应用项目的 /obj 子文件夹中为每个 XAML 页面创建的“.g”(生成的)文件。如果反射你最终的程序集或检查它们的接口语言内容,也可以看到字段和 InitializeComponent 方法是这些结果程序集的成员。
注意 特别是对于 Visual C++ 组件扩展 (C++/CX) 应用,不为 XAML 文件的根元素创建
x:Name 引用的支持字段。如果你需要从 C++/CX 代码隐藏来引用根对象,请使用其他 API 或树形遍历。例如,你可以为已知的命名子元素调用
FindName,然后调用
Parent。
在运行时使用 XamlReader.Load 创建对象
XAML 也可用作 XamlReader.Load 方法的字符串输入,该方法的行为类似于最初的 XAML 源代码分析操作。XamlReader.Load 在运行时创建一个断开连接的新对象树。然后可将断开连接的树附加到主要对象树上的某个点。必须显式连接你创建的对象树,无论是通过将它添加到一个内容属性集合(例如 Children),还是设置其他某个接受对象值的属性(例如为 Fill 属性值加载一个新的 ImageBrush)。
XamlReader.Load 对 XAML 名称范围的意义
XamlReader.Load 创建的新对象树所定义的初步 XAML 名称范围会在所提供的 XAML 中计算任何已定义的名称,以确定其是否唯一。如果所提供的 XAML 中的名称此时在内部不是唯一的,XamlReader.Load 会抛出一个异常。如果或当断开的对象树连接到主要应用程序对象树时,它不会尝试将它的 XAML 名称范围与主要应用程序 XAML 名称范围合并。连接树后,你的应用有一个统一的对象树,但该树中具有离散 XAML 名称范围。这种分歧发生在对象之间的连接点上,你在这些连接点将一个属性设置为从一个 XamlReader.Load 调用返回的值。
拥有离散且断开连接的 XAML 名称范围的复杂性在于,调用 FindName 方法以及直接管理的对象引用不再针对一个统一的 XAML 名称范围来执行。相反,在其上调用 FindName 的特定对象将指定范围,该范围就是调用对象所在的 XAML 名称范围。在直接管理的对象引用情况中,该范围由代码所在的类指定。通常,用于一个应用内容“页面”的运行时交互的代码隐藏位于支持根“页面”的分部类中,因此 XAML 名称范围是根 XAML 名称范围。
如果调用 FindName 获得根 XAML 名称范围中的一个命名对象,该方法不会找到来自 XamlReader.Load 创建的离散 XAML 名称范围的对象。相反,如果调用的 FindName 来自从该离散 XAML 名称范围获得的对象,该方法不会找到根 XAML 名称范围中的命名对象。
这个离散 XAML 名称范围问题只会影响在使用 FindName 调用时按 XAML 名称范围中的名称查找对象的操作。
要获得在不同 XAML 名称范围中定义的对象的引用,你可以使用多种技术:
- 使用 Parent 和/或已知存在于你的对象树结构中的集合属性(例如 Panel.Children 返回的集合)在离散的步骤中遍历整个树。
- 如果从一个离散 XAML 名称范围调用并且希望使用根 XAML 名称范围,始终可轻松获得当前显示的主要窗口的引用。只需使用一行包含调用
Window.Current.Content
的代码,即可获得当前应用程序窗口的可视根(根 XAML 元素,也称为内容源)。然后可转换为 FrameworkElement 并从此范围调用 FindName。
- 如果从根 XAML 名称范围调用并且希望一个离散 XAML 名称范围中的对象,最好在你的代码中提前计划,保留对 XamlReader.Load 返回并随后添加到主要对象树的对象的引用。此对象现在是一个可在离散 XAML 名称范围中调用 FindName 的有效对象。你可以保持此对象用作全局变量,或者使用方法参数传递它。
- 你可以通过检查可视树来完全避免名称和 XAML 名称范围考虑因素。VisualTreeHelper API 支持单独基于位置和索引,遍历可视树以查找父对象和子集合。
模板中的 XAML 名称范围
XAML 中的模板提供了以一种直观方式重用和重新应用内容的能力,但模板可能还包含各种元素,这些元素具有在模板级别定义的名称。该名称模板可在一个页面中多次使用。出于此原因,模板定义它们自己的 XAML 名称范围,不依赖于样式或模板所应用到的包含页面。 考虑此示例:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Page.Resources>
<ControlTemplate x:Key="MyTemplate">
....
<TextBlock x:Name="MyTextBlock" />
</ControlTemplate>
</Page.Resources>
<StackPanel>
<SomeControl Template="{StaticResource MyTemplate}" />
<SomeControl Template="{StaticResource MyTemplate}" />
</StackPanel>
</Page>
此处同一个模板被应用于两个不同的控件。如果模板没有离散 XAML 名称范围,模板中使用的 "MyTextBlock" 名称将导致名称冲突。模板的每次实例化都拥有自己的 XAML 名称范围,所以在本例中,每个已实例化模板的 XAML 名称范围将仅包含一个名称。但是,根 XAML 名称范围不包含来自每个模板的名称。
由于采用了不同的 XAML 名称范围,需要一种不同技术来从一个模板所应用到的页面范围中查找该模板内的指定元素。无需在对象树中的某个对象上调用 FindName,你首先获得已应用了模板的对象,然后调用 GetTemplateChild。 如果你是一位控件作者并且正在生成一种约定,其中已应用的模板中一个特定的命名元素是控件本身所定义的一种行为的目标,你可以使用你的控件实现代码中的 GetTemplateChild 方法。GetTemplateChild 方法是受保护的,所以只有控件作者能访问它。另外,控件作者应该遵守一些约定来命名各部分和为各部分创建模板,并以应用到控件类的属性值的形式报告这些部分。此技术使重要部分的名称可供那些可能希望应用不同模板的控件用户发现,这将需要替换已命名的部分才能维护控件功能。
TemplateSettings 类
我们假定你可以将控件添加到 UI,设置控件的属性以及连接事件处理程序。我们还假设你了解如何通过创建默认模板的副本并对其进行编辑来定义控件的自定义模板的基础知识。
面向 TemplateSettings 类的方案
TemplateSettings 类提供一组可在为控件定义新控件模板时使用的属性。这些属性具有诸如 UI 元素部件的大小的像素度量之类的值。这些值有时是来自于通常难以覆盖甚至访问的控件逻辑的经计算的值。其中一些属性旨在作为控制部件的过渡和动画的 FromTo 值,因此也是成对出现的相关 TemplateSettings 属性。
有多个 TemplateSettings 类。它们全部都位于 Windows.UI.Xaml.Controls.Primitives 命名空间中。下面是类的列表,以及指向相关控件的 TemplateSettings 属性的链接。此 TemplateSettings 属性是你访问控件的 TemplateSettings 值的方式,并且可以建立对其属性的模板绑定:
- ComboBoxTemplateSettings:ComboBox.TemplateSettings 的值
- GridViewItemTemplateSettings:GridViewItem.TemplateSettings 的值
- ListViewItemTemplateSettings:ListViewItem.TemplateSettings 的值
- ProgressBarTemplateSettings:ProgressBar.TemplateSettings 的值
- ProgressRingTemplateSettings:ProgressRing.TemplateSettings 的值
- SettingsFlyoutTemplateSettings:SettingsFlyout.TemplateSettings 的值
- ToggleSwitchTemplateSettings:ToggleSwitch.TemplateSettings 的值
- ToolTipTemplateSettings:ToolTip.TemplateSettings 的值
TemplateSettings 属性始终旨在在 XAML 中使用,而不是在代码中使用。它们是父控件的只读 TemplateSettings 属性的只读子属性。对于高级自定义控件方案,在此方案中你要创建新的基于 Control 的类并且可以影响控件逻辑,请在控件上定义一个自定义 TemplateSettings 属性,以传送可能对任何正在为控件重新创建模板的人有用的信息。作为该属性的只读值,请定义与你的控件相关的新 TemplateSettings 类,此类具有与模板度量、动画位置等相关的每个信息项的只读属性,并且向调用方提供使用你的控件逻辑初始化的该类的运行时实例。TemplateSettings 类派生自 DependencyObject,因此这些属性可以使用依赖项属性系统进行更改属性的回调。但是这些属性的依赖项属性标识符将不公开为公共 API,因为 TemplateSettings 属性旨在对调用方只读。
如何在控件模板中使用 TemplateSettings
下面是来自初始默认 XAML 控件模板的示例。此特定的示例来自 ProgressRing 的默认模板:
<Ellipse
x:Name="E1"
Style="{StaticResource ProgressRingEllipseStyle}"
Width="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}"/>
ProgressRing 模板的完整 XAML 有数百行,因此这只是一个小小摘要。此 XAML 定义作为 6 个
Ellipse 元素之一的控件部件,此元素描绘不确定进度的旋转动画。作为开发人员,你可能不喜欢圆形,而且可能针对动画处理方式使用不同的图形基元或不同的基本形状。例如,你可能撰写一个使用一组以方形排列的
Rectangle 元素的
ProgressRing。如果如此,则你的新模板的单个
Rectangle 组件可能外观如下:
<Rectangle
x:Name="R1"
Width="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.EllipseDiameter}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.EllipseDiameter}"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.EllipseOffset}"
Fill="{TemplateBinding Foreground}"/>
TemplateSettings 属性在此处有用的原因是,它们是来自 ProgressRing 的基本控件逻辑的经计算的值。计算划分ProgressRing 的 ActualWidth 和 ActualHeight,并且为其模板中的每个动作元素分配经计算的度量,以便模板部件可以针对内容设置大小。
下面是来自另一个来自默认 XAML 控件模板的示例用法,这次显示作为动画的 From 和 To 的属性集之一。它来自 ComboBox 默认模板:
<VisualStateGroup x:Name="DropDownStates">
<VisualState x:Name="Opened">
<Storyboard>
<SplitOpenThemeAnimation
OpenedTargetName="PopupBorder"
ContentTargetName="ScrollViewer"
ClosedTargetName="ContentPresenter"
ContentTranslationOffset="0"
OffsetFromCenter="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.DropDownOffset}"
OpenedLength="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.DropDownOpenedHeight}"
ClosedLength="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=TemplateSettings.DropDownClosedHeight}" />
</Storyboard>
</VisualState>
...
</VisualStateGroup>
同样,模板中有许多 XAML,因此我们只显示摘要。而且这仅仅是分别使用相同 ComboBoxTemplateSettings 属性的多个状态和主题动画之一。对于 ComboBox,通过绑定使用 ComboBoxTemplateSettings 值强制模板中的相关动画停止,并在基于共享值的位置上开始,以便平稳地过渡。
注意
当你使用 TemplateSettings 值作为控件模板的一部分时,请确保你设置的是与值的类型匹配的属性。如果不是,你可能需要为绑定创建一个值转换器,以便可以从 TemplateSettings 值的不同源类型转换为绑定的目标类型。