wpfxamlbindingvisual-treelogical-tree

Why does binding fail when binding a child element to another element when the parent succeeds?


Say I have two classes that can reference a third UI object (in this example a button).

In addition, the parent class can contain an element of the child class.

If they both are bound to the same control, the same way, the child will fail but the parent succeed.

Is this a bug in WPF?


The parent :

class MyFrameworkElement : FrameworkElement
{
    // A depenedency property that will contain a child element sub-element
    private static readonly DependencyProperty ChildElementProperty =
                    DependencyProperty.Register("ChildElement",
                    typeof(MyChildElement),
                    typeof(MyFrameworkElement),
                    new PropertyMetadata());

    [Category("ChildProperties")]
    public MyChildElement ChildElement
    {
        set { SetValue(ChildElementProperty, value); }
        get { return (MyChildElement)GetValue(ChildElementProperty); }
    }


    // Now, a reference to some other control, in this case we will bind a button to it!
    public UIElement ButtonReferenceInParent
    {
        get { return (UIElement)GetValue(ButtonReferenceInParentProperty); }
        set { SetValue(ButtonReferenceInParentProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ButtonReferenceInParent.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ButtonReferenceInParentProperty =
        DependencyProperty.Register("ButtonReferenceInParent", typeof(UIElement), typeof(MyFrameworkElement), new UIPropertyMetadata(null));

And then the child :

public class MyChildElement : FrameworkElement
{
    public UIElement ButtonReferenceInChild
    {
        get { return (UIElement)GetValue(ButtonReferenceInChildProperty); }
        set { SetValue(ButtonReferenceInChildProperty, value); }
    }

    public static readonly DependencyProperty ButtonReferenceInChildProperty =
        DependencyProperty.Register("ButtonReferenceInChild", typeof(UIElement), typeof(MyChildElement), new UIPropertyMetadata(null));
}

OK -

Now say I Add them to my XAML like this :

<Grid>
    <my:MyFrameworkElement x:Name="ParentName" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ButtonReferenceInParent="{Binding ElementName=buttonisme}">
        <my:MyFrameworkElement.ChildElement>
            <my:MyChildElement x:Name="ChildName" ButtonReferenceInChild="{Binding ElementName=buttonisme}"/>
        </my:MyFrameworkElement.ChildElement>
    </my:MyFrameworkElement>
    
    <Button x:Name="buttonisme" Click="buttonisme_Click" />
</Grid>

Why does the binding work on the parent but then fail on the child, when I am using the EXACT same notation?


Here is my test code...

     Console.WriteLine("Parent button reference is {0}", ParentName.ButtonReferenceInParent);

        if (ChildName.ButtonReferenceInChild == null)
        {
            Console.WriteLine("Child button reference is null!");
        } 
        else
        {
            Console.WriteLine("Child button is {0}", ChildName.ButtonReferenceInChild);
        }

And here is the test result...

Parent button reference is System.Windows.Controls.Button

Child button reference is null!


Solution

  • The short answer to a long question is that Microsoft doesn't expect you to derive from FrameworkElement without doing a little plumbing.

    Just doing derivation, breaks the logical tree which is used when doing binding by element name.

    You probably also have to plum up the visual tree, and overload the arrange/measure parts of framework element. (We don't do that here as we aren't visual in the example.)

    In this specific case we need to add any children of your object to the logical tree or break the ability to bind child elements.

    A good example of someone who solved this is here

    Information on overriding the logical tree is here

    Anyways, the code needed to fix this SIMPLE example only relied on the logical tree (as the child object isn't really visual.)

    Adding this function and changing the dependency property makes the binding work.

            private static readonly DependencyProperty ChildElementProperty =
                        DependencyProperty.Register("ChildElement",
                        typeof(MyChildElement),
                        typeof(MyFrameworkElement),
                        new PropertyMetadata(OnChildElementChanged));
    
        private static void OnChildElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MyFrameworkElement control = d as MyFrameworkElement;
    
            if (e.OldValue != null)
            {
                control.RemoveLogicalChild(e.OldValue);
            }
    
            control.AddLogicalChild(e.NewValue);
        }