享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

How does ElementName Binding work? - Part 1 Logical Tree & NameScope

Posted on 2010-05-28 17:31  idior  阅读(3753)  评论(6编辑  收藏  举报

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.

 

 

Notes

TemplateNameScope is a little different from NameScope. Elements have names  which are defined in a template will not be registered into the nameMap. I don’t want to digger into that too much, you just need to know TemplateNameScope.FindName is not just relay on nameMap dictionary and it’s a little different from NameScope.FindName, but we can still use it to find the elements defined in the template by using its name.

 

 

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.

 

  1. 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.
  2. Call the NameScope.FindName method on the found NameScope.
  3. 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。