WPF XAML 名称范围
XAML 名称范围是一个概念,用于标识在 XAML 中定义的对象。 XAML 名称范围中的名称可以用于在对象树中,将 XAML 定义的对象名称与其对应的实例对象建立关系。 通常,在加载 XAML 应用程序的各个 XAML 页面根时,会在 WPF 的托管代码中创建 XAML 名称作用域。 XAML 名称范围作为编程对象由 INameScope 接口定义,并由实际类 NameScope实现。
加载的 XAML 应用程序中的命名范围
在更广泛的编程或计算机科学上下文中,编程概念通常包括可用于访问对象的唯一标识符或名称的原则。 对于使用标识符或名称的系统,名称范围定义了当请求某个名称的对象时,进程或技术将在其中搜索的边界,以及用于确保标识名称唯一性的边界。 这些一般原则适用于 XAML 名称范围。 在 WPF 中,加载页面时,XAML 名称范围在 XAML 页面的根元素上创建。 从 XAML 页面的根节点开始指定的每个名称都会添加到相应的 XAML 名称范围。
在 WPF XAML 中,常见根元素(如 Page和 Window)的元素始终控制 XAML 名称范围。 如果 FrameworkElement 或 FrameworkContentElement 等元素是标记中页面的根元素,则 XAML 处理器会隐式添加 Page 根,以便 Page 可以提供有效的 XAML 名称范围。
注意
即使在 XAML 标记中的任何元素上未定义 Name
或 x:Name
属性,WPF 构建操作也会为 XAML 输出创建 XAML 名称范围。
如果尝试在任何 XAML 名称范围中使用同一名称两次,则会引发异常。 对于具有后台代码且属于已编译应用程序的一部分的 WPF XAML,在初始标记编译期间,WPF 生成操作会在生成页面的生成类时引发异常。 对于未通过任何生成操作进行标记编译的 XAML,在加载时可能会引发与 XAML 名称范围问题相关的异常。 XAML 设计器还可能在设计时预测 XAML 名称范围问题。
将对象添加到运行时对象树
分析 XAML 的那一刻表示创建和定义 WPF XAML 名称范围的时间。 如果在 XAML 文件解析生成该对象树之后的某个时间点向对象树添加一个对象,则新对象上的 Name
或 x:Name
值不会自动更新 XAML 名称范围中的信息。 若要在加载 XAML 后将对象的名称添加到 WPF XAML 名称范围中,必须在定义 XAML 名称范围(通常是 XAML 页面根)的对象上调用相应的 RegisterName 实现。 如果未注册名称,则无法通过 FindName等方法引用添加的对象,并且不能将该名称用于动画目标。
应用程序开发人员最常见的方案是,你将使用 RegisterName 将名称注册到页面当前根目录的 XAML 名称范围中。 RegisterName 是面向动画中对象的情节提要重要场景的一部分。 有关详细信息,请参阅 故事板概览。
如果在定义 XAML 名称范围的对象以外的对象上调用 RegisterName,该名称仍会注册到调用对象所在 XAML 名称范围中,就像在定义对象的 XAML 名称范围上调用 RegisterName 一样。
代码中的 XAML 名称范围
可以在代码中创建和使用 XAML 名称范围。 即使对于纯代码用法,XAML 名称范围创建中涉及的 API 和概念也是如此,因为 WPF 的 XAML 处理器在处理 XAML 本身时使用这些 API 和概念。 概念和 API 主要是为了能够在通常部分或完全在 XAML 中定义的对象树中按名称查找对象。
对于以编程方式而不是从加载的 XAML 创建的应用程序,定义 XAML 名称范围的对象必须实现 INameScope,或者是 FrameworkElement 或 FrameworkContentElement 派生类,以支持在其实例上创建 XAML 名称范围。
此外,对于 XAML 处理器未加载和处理的任何元素,默认情况下不会创建或初始化对象的 XAML 名称范围。 您必须为任何对象显式创建一个新的 XAML 名称范围,以便随后进行名称注册。 若要创建 XAML 名称范围,请调用静态 SetNameScope 方法。 请指定将拥有它的对象作为 dependencyObject
参数,并将新的 NameScope 构造函数调用设定为 value
参数。
如果提供为 SetNameScope 的 dependencyObject
对象不是 INameScope 实现、FrameworkElement 或 FrameworkContentElement,那么对任何子元素调用 RegisterName 将不起作用。 如果无法显式创建新的 XAML 名称范围,则调用 RegisterName 将引发异常。
有关在代码中使用 XAML 名称范围 API 的示例,请参阅 定义名称范围。
样式和模板中的 XAML 名称范围
WPF 中的样式和模板提供了一种简单的方式重用和重新应用内容的功能。 但是,样式和模板也可能包含在模板级别中定义的具有 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="MyButtonTemplate" TargetType="{x:Type Button}">
<Border BorderBrush="Red" Name="TheBorder" BorderThickness="2">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Page.Resources>
<StackPanel>
<Button Template="{StaticResource MyButtonTemplate}">My first button</Button>
<Button Template="{StaticResource MyButtonTemplate}">My second button</Button>
</StackPanel>
</Page>
在这里,同一模板应用于两个不同的按钮。 如果模板没有离散的 XAML 名称范围,则模板中使用的 TheBorder
名称将导致 XAML 名称范围中的名称冲突。 模板的每个实例化都有自己的 XAML 名称范围,因此在此示例中,每个实例化模板的 XAML 名称范围将仅包含一个名称。
样式还定义了自己的 XAML 名称范围,主要是为了可以为动画脚本的各个部分分配特定的名称。 即使模板被重新定义为控件自定义的一部分,这些名称也允许控制特定行为以该名称的元素为目标。
由于单独的 XAML 名称范围,在模板中查找命名元素比在页面中查找非模板化命名元素更具挑战性。 首先需要确定应用的模板,方法是获取应用模板的控件所在的 Template 属性值。 然后,调用 FindName的模板版本,并将应用模板的控件作为第二个参数传递。
如果你是控件作者,并且要生成一个约定,其中应用模板中的特定命名元素是控件本身定义的行为的目标,则可以使用控件实现代码中的 GetTemplateChild 方法。 GetTemplateChild 方法受到保护,因此只有控件作者有权访问它。
如果您是在一个模板内部工作,并且需要访问应用模板的 XAML 名称范围,请获取 TemplatedParent的值,然后在该处调用 FindName。 在模板中工作的示例是,如果要编写事件处理程序实现,其中事件将从应用模板中的元素引发。
XAML 名称范围和与名称相关的 API
FrameworkElement 具有 FindName、RegisterName 和 UnregisterName 方法。 如果您调用这些方法的对象具有 XAML 名称范围,则这些方法将调用相关 XAML 名称范围中的方法。 否则,将检查父元素是否拥有 XAML 名称范围,并且此过程会以递归方式继续,直到找到 XAML 名称范围(由于 XAML 处理器行为,保证其根位置有 XAML 名称范围)。 FrameworkContentElement 具有类似的行为,但例外是,任何 FrameworkContentElement 都不会拥有 XAML 名称范围。 方法存在于 FrameworkContentElement 上,以便最终可以将调用转发到 FrameworkElement 父元素。
SetNameScope 用于将新的 XAML 名称范围映射到现有对象。 可以多次调用 SetNameScope 以重置或清除 XAML 名称范围,但这不是常见的用法。 此外,GetNameScope 通常不会在代码中使用。
XAML 名称空间实现
以下类直接实现 INameScope:
ResourceDictionary 不使用 XAML 名称或名称范围,而是使用键,因为它是一个字典数据结构的实现。 ResourceDictionary 实现 INameScope 的唯一原因是,它可以对用户代码引发异常,以帮助阐明真正的 XAML 名称范围与 ResourceDictionary 如何处理键之间的区别,并确保 XAML 名称范围不适用于父元素的 ResourceDictionary。
FrameworkTemplate 和 Style 通过显式接口定义实现 INameScope。 显式实现允许这些 XAML 名称范围在通过 INameScope 接口访问它们时采用常规行为,这就是 WPF 内部进程传达 XAML 名称范围的方式。 但是,显式接口定义不属于 FrameworkTemplate 和 Style的传统 API 接口之一,因为你通常不会直接调用 FrameworkTemplate 和 Style 上的 INameScope 方法,而是会使用其他 API,比如 GetTemplateChild。
以下类通过使用System.Windows.NameScope 辅助类,并通过附加属性连接到其 XAML 命名范围 NameScope.NameScope 实现,来定义其自己的 XAML 命名范围:
另请参阅
- XAML 命名空间和命名空间映射 为 WPF XAML
- x:Name 指令