Most developers have used {Binding ElementName= myControl , Path = myProperty} in their WPF projects, however you may find it didn’t work for you in certain cases, especially when you were building a complex control in which lots of controls nested.
To understand the ElementName binding, we must understand NameScope first, since ElementName binding will use NameScope.FindName to find control internally.
I don’t want to talk about the concept of NameScope too much in this article; MSDN already gave a good description for it. It comes into play when you give a name to the control defined in the template, see following codes:
<Window>
<Window.Resources>
<ControlTemplate x:Key="template" TargetType="{x:Type Button}">
<Rectangle Name="foo"/>
</ControlTemplate
</Window.Resources>
<Button Template="{StaticResource
template}"/>
<Button Template="{StaticResource
template}"/>
</Window>
Without NameScope, there will be two buttons with the same name in the window, which will defiantly cause an error for us. How NameScope rescue?
Every template has its own NameScope , an instance of TemplateNameScope type. I think this is known to most developer; however you may not heard of that every UserControl has it own NameScope.
Now let’s see how NameScope.FindName works. Actually the idea is very simple; each element defined in the xaml with Name Property will be registered in a nameMap dictionary in its nearest NameScope. NameScope.FindName will simply get the element from the nameMap. You may wonder what’s the nearest NameScope? Here nearest means the nearest element which has a NameScope in the logic tree.
One thing you should keep in mind is that not every element has a NameScope, the top level window has a NameScope, each UserControl has a NameScope, and each element which defined at the top level in a ControlTemplate has a NameScope.
Now let’s go back to the element name binding. After studying the source codes of WPF, I find ElementName Binding use ElementObjectRef class for finding the element via its name. By looking into the source code, we can easily find how it works.
- Start from the element which applied the ElementName Binding, keep searching on the logic tree via its logic parent, until an element which has NameScope is found, let’s call it NameScopeElement. If no element owns a NameScope, search will stop.
- Call the NameScope.FindName method on the found NameScope.
- If the element is found, return it, otherwise try to get the template parent of NameScopeElement; if the template parent is null, it will stop search. or it goes back to step 1, search on the logic tree for element owns a NameScope.
Now let’s see an example in which ElementName Binding doesn’t work properly.
<Window x:Class="TestElementBindingInStyle.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestElementBindingInStyle"
Title="Window1" Height="300" Width="300" x:Name="root" >
<local:MyUC Grid.Row="0" x:Name="myuc" IsEnabled="false" >
<local:MyUC.MyContent >
<Button Content="{Binding ElementName=myuc, Path=IsEnabled}" />
</local:MyUC.MyContent>
</local:MyUC>
</Window>
<UserControl x:Class="TestElementBindingInStyle.MyUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:TestElementBindingInStyle"
x:Name="root">
<ContentPresenter Content="{Binding ElementName= root,Path= MyContent}" Grid.Row="0"/>
</UserControl>
public partial class MyUC : UserControl
{
public MyUC() {
InitializeComponent();
}
/// <summary>
/// MyContent Dependency Property
/// </summary>
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(object), typeof(MyUC),
new FrameworkPropertyMetadata(null ,
new PropertyChangedCallback(OnMyContentChanged)));
/// <summary>
/// Gets or sets the MyContent property. This dependency property
/// indicates ....
/// </summary>
public object MyContent
{
get { return (object)GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
/// <summary>
/// Handles changes to the MyContent property.
/// </summary>
private static void OnMyContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyUC)d).OnMyContentChanged(e);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the MyContent property.
/// </summary>
protected virtual void OnMyContentChanged(DependencyPropertyChangedEventArgs e)
{
this.AddLogicalChild(e.NewValue);
}
}
I have highlighted the binding which doesn’t work. Now let’s go though the finding path to see why it doesn’t work here.
The search starts from myBtn, it will check whether it has a NameScope, apparently it doesn’t. Then we move to its logic parent which is myuc, since it’s a UserControl, it does have a NameScope; however we cannot find myuc in this Scope, because myuc is registered in the Window’s NameScope. Now it will try to get the TemplateParent of myuc, but the value is null, so the search stops. It cannot find Element whose name is myuc.
Now my question is what‘s the correct approach for making this binding work? Use RelativeSource Binding.
<local:MyUc x:Name="myuc" IsEnabled="false">
<local:MyUc.MyContent >
<Button Content="{Binding
Path=IsEnabled,
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:MyUc}}}" />
</local:MyUc.MyContent>
</local:MyUc>
顺便做个广告,道富信息科技和网新恒天都招聘.NET高级开发人员和架构师。需要对.NET和面向对象设计有比较深入的了解和较好的英文读写能力,如果有WPF 或Silverlight开发经验更佳,工作地点杭州。简历请发至 nxu [at] statestreet [dot] com。