SoFunction
Updated on 2025-03-08

Detailed explanation of NameScope search rules in WPF

Preface

When we use binding in WPF, we can use the writing method like ElementName=Foo, and we can actually find the object corresponding to this name at runtime because the concept of name range is provided in WPF.

Implementing the INameScope interface can define a name range. Whether you use the Name property or the x:Name property, you can specify the name of an element within a name range. When binding, look up within this name range, so you can find the object you need.

NameScope in XAML

First, let’s talk about WPF’s name management mechanism NameScope, that is, name scope. Name scope mainly provides two functions: record the association relationship between XAML names and interface element instances; prevent name conflicts. It can be said that the second function is the side effect caused by the implementation of the first function. When referring to a name in XAML, WPF will automatically use the corresponding NameScope to search for the name.

So, how does WPF name range work in program compositions such as XAML? If an element has a name set in XAML using the x:Name or Name attribute, then WPF will perform some additional execution logic for the attribute settings, such as automatically generating members with the same name in the corresponding cs file and registering them into the corresponding name range. If the same name is used multiple times in the range, WPF throws an exception. When referring to an element in XAML, WPF will look for the interface element corresponding to the name from the NameScope for operation.

Of course, users do not need to explicitly process the name range. By default, WPF will use certain mechanisms to ensure that each interface element in the file can have the appropriate name range. Both the Page class and the Window class, which are often used as root elements in XAML, provide support for name scope. If the root element in XAML is not these two types, the XAML processor implicitly adds a Page element to the file as a new root element during processing. Through this method, WPF can ensure that the use of x:Name and Name in XAML files can correctly register the names into the corresponding name range.

This article will introduce the search rules for NameScope in WPF. (Extra, the resource/resource dictionary is searched the same way as NameScope, so this analysis process also uses the resource search.)

INameScope

The INameScope interface of WPF is only used to manage names within one scope. It contains the following three methods:

public interface INameScope
{
 object FindName(string name);
 void RegisterName(string name, object scopedElement);
 void UnregisterName(string name);
}

Its main implementation is NameScope, which contains more functions; and the above interface is its original 2222-quality function.

However, the implementation of NameScope brings an important dependency property - NameScope. Here is the code for this property (simplified):

public static readonly DependencyProperty NameScopeProperty
 = ("NameScope", typeof(INameScope), typeof(NameScope));

public static void SetNameScope(DependencyObject dependencyObject, INameScope value)
{
 if (dependencyObject == null) throw new ArgumentNullException(nameof(dependencyObject));
 (NameScopeProperty, value);
}

public static INameScope GetNameScope(DependencyObject dependencyObject)
{
 if (dependencyObject == null) throw new ArgumentNullException(nameof(dependencyObject));
 return ((INameScope)(NameScopeProperty));
}

TemplateNameScope is also implemented. This NameScope will be set to the above dependency properties by FrameworkTemplate / FrameworkElementFactory / BamlRecordReader. So we can find the element corresponding to a specific name within the template scope.

In addition, the NameScope settings are automatically generated by the XAML parser when the WPF project is compiled.

NameScope name registration rules

If you do not explicitly call the RegisterName method in the code, then the creation of NameScope and the registration of the name are done by the XAML parser.

When registering a name, the XAML parser (BamlRecordReader) does not crawl the visual tree or something, but simply calls the code to register the name when parsing XAML. Registration is done by a Stack, NameScopeStack.

Imagine the following example (from comments in the .NET Framework code):

<Window x:Name="myWindow">
 ...
 <Style x:Name="myStyle">
 ...
 <SolidColorBrush x:Name="myBrush">
 </SolidColorBrush>
 </Style>
</Window>

Whenever the XAML parser parses a layer, it will put the NameScopeStack on the stack, so the Window first creates NameScope on the stack. Then, when parsing to Style, another NameScope is added to the stack. NameScope will not be created when parsing other elements (including the top-level element UserControl in XAML, etc.).

At this time, myWindow will be registered in the NameScope on the first floor of Window, myStyle will also be registered in the NameScope on the first floor of Window, and myBrush will be registered in the NameScope on the first floor of Style.

Window's NameScope

  • myWindow
  • myStyle

Style's NameScope

  • myBrush

NameScope's name search rules

When posting the NameScope dependency property at the beginning of this article, you should note that this is just a normal property and does not use anything that can be used to inherit such advanced metadata using a visual tree. In fact, there shouldn't be such advanced metadata, because NameScope has a lower abstraction level than a visual tree or a logical tree.

However, in fact, NameScope's search depends on the logical tree - this is the function of FrameworkElement:

internal static INameScope FindScope(DependencyObject d, out DependencyObject scopeOwner)
{
 while (d != null)
 {
  INameScope nameScope = (d);
  if (nameScope != null)
  {
   scopeOwner = d;
   return nameScope;
  }

  DependencyObject parent = (d);

  d = (parent != null) ? parent : ();
 }

 scopeOwner = null;
 return null;
}

It is very obvious that FindScope expects to use a logical tree to find name ranges.

However, it is worth noting that when an element has no logical parent, it will try to use . to find another object. So what method is this and what object are you trying to find?

Mentor is a noun, meaning "mentor, guidance". So we need to read the implementation of the following method to understand its intention:

Tip: The FE in the comments below stands for FrameworkElement, and FCE stands for FrameworkContentElement.

/// <summary>
///  This method finds the mentor by looking up the InheritanceContext
///  links starting from the given node until it finds an FE/FCE. This
///  mentor will be used to do a FindResource call while evaluating this
///  expression.
/// </summary>
/// <remarks>
///  This method is invoked by the ResourceReferenceExpression
///  and BindingExpression
/// </remarks>
internal static DependencyObject FindMentor(DependencyObject d)
{
 // Find the nearest FE/FCE InheritanceContext
 while (d != null)
 {
  FrameworkElement fe;
  FrameworkContentElement fce;
  (d, out fe, out fce, false);

  if (fe != null)
  {
   return fe;
  }
  else if (fce != null)
  {
   return fce;
  }
  else
  {
   d = ;
  }
 }

 return null;
}

Specifically, it is to constantly search for InheritanceContext. If FrameworkElement or FrameworkContentElement is found, then the FE or FCE is returned; if it is not found in the end, then the null is returned.

This is a virtual property. The base class DependencyObject only returns null, and the child class returns the parent when it overrides it. Freezable, FrameworkElement, FrameworkContentElement, etc. rewritten this property.

For FrameworkElement, when rewriting, it simply returns an internally managed field:

internal override DependencyObject InheritanceContext
{
 get { return (this); }
}

This field will be assigned a value when called. For the establishment of a visual tree or logical tree, this method will not be called, so this property will not have an impact on the visual tree or logical tree. However, Freezable, InputBinding, Visual3D, GridViewColumn, ViewBase, CollectionViewSource, ResourceDictionary, TriggerAction, TriggerBase, etc. will call this method when attribute assignment. So we can find the name in the settings of these properties.

In particular, only those types that have rewritten InheritanceContext can find NameScope when searching for names; only the above properties that call the method can find NameScope when assigning values.

So, the ContextMenu mentioned in another article cannot find the corresponding NameScope. WPF's ElementName cannot be bound successfully in ContextMenu? Try using x:Reference! . The NameScope found by ContextMenu in this article is null.

Summarize

The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.