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 一样。
Code 中的 XAML 名称范围
可以在代码中创建和使用 XAML 名称范围。 即使对于纯代码用法,XAML 名称范围创建中涉及的 API 和概念也是如此,因为 WPF 的 XAML 处理器在处理 XAML 本身时使用这些 API 和概念。 概念和 API 主要是为了能够在通常部分或完全在 XAML 中定义的对象树中按名称查找对象。
对于以编程方式而不是从加载的 XAML 创建的应用程序,定义 XAML 名称范围的对象必须实现INameScope或FrameworkElementFrameworkContentElement派生类,以支持在其实例上创建 XAML 名称范围。
此外,对于 XAML 处理器未加载和处理的任何元素,默认情况下不会创建或初始化对象的 XAML 名称范围。 您必须为任何计划后续注册名称的对象显式创建一个新的 XAML 名称作用域。 若要创建 XAML 名称范围,请调用静态 SetNameScope 方法。 指定将拥有它作为 dependencyObject
参数的对象,并将新的 NameScope 构造函数调用指定为 value
参数。
如果作为dependencyObject
提供给SetNameScope的对象不是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 名称或名称范围;它改用键,因为它是字典实现。 实现INameScope的唯一原因是让ResourceDictionary可以对用户代码引发异常,从而帮助澄清真正的 XAML 名称范围与ResourceDictionary处理键的方式之间的区别,并确保父元素不会将 XAML 名称范围应用于ResourceDictionary。
FrameworkTemplate 和 Style 通过显式接口定义实现 INameScope。 显式实现允许这些 XAML 名称范围在通过 INameScope 接口进行访问时采用常规行为,即 WPF 内部进程如何传达 XAML 名称范围。 但是,显式接口定义不是传统 API 图面的FrameworkTemplateStyle一部分,因为你很少需要直接调用FrameworkTemplateINameScopeStyle方法,而是使用其他 API,例如GetTemplateChild。
以下类通过使用 System.Windows.NameScope 帮助程序类,并通过 NameScope.NameScope 附加属性连接到其 XAML 名称范围实现,来定义其自己的 XAML 名称范围: