XAML 名称范围

XAML 名称范围存储对象定义的名称与其实例等效项之间的关系。 此概念类似于其他编程语言和技术中术语 名称范围 更广泛的含义。

如何定义 XAML 名称范围

XAML 名称范围中的名称使用户代码能够引用最初在 XAML 中声明的对象。 分析 XAML 的内部结果是运行时创建一组对象,这些对象保留这些对象在 XAML 声明中具有的部分或全部关系。 这些关系作为所创建对象的特定对象属性进行维护,或向编程模型 API 中的实用工具方法公开。

在 XAML 名称范围中,名称的最典型用法是作为直接引用对象实例,这种用法是通过标记编译过程作为项目构建操作启用的,并结合分部类模板中生成的 InitializeComponent 方法。

还可以在运行时使用实用工具方法 FindName 自行返回对在 XAML 标记中使用名称定义的对象的引用。

有关生成操作和 XAML 的更多信息

从技术上讲,XAML 本身会经历一个标记编译过程,同时 XAML 和它用于代码隐藏的分部类会一起进行编译。 标记中定义的 Namex: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 名称作用域为根名称作用域。

如果调用 FindName 以获取根 XAML 名称范围中的命名对象,该方法将找不到 XamlReader.Load 创建的离散 XAML 名称范围中的对象。 相反,如果从从离散 XAML 名称范围中获取的对象调用 FindName ,该方法将不会在根 XAML 名称范围中找到命名对象。

使用 FindName 调用时,此离散 XAML 名称范围问题仅影响在 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 名称范围不同,因此从应用模板的页面范围内查找命名元素需要不同的技术。 你首先获取应用了模板的对象,然后调用 GetTemplateChild,而不是对对象树中的某些对象调用 FindName。 如果你是控件作者,并且要生成一个约定,其中应用模板中的特定命名元素是控件本身定义的行为的目标,则可以从控件实现代码中使用 GetTemplateChild 方法。 GetTemplateChild 方法受到保护,因此只有控件作者有权访问它。 此外,还有一些约定,控件作者应遵循这些约定来命名部件和模板部件,并将其报告为应用于控件类的属性值。 此方法使重要部件的名称可供发现,以便控制可能想要应用其他模板的用户,这需要替换命名部件才能维护控件功能。