2
votes

I try to make my own ContentControl that derives from Control to fully understand dark wpf tree concepts. For now, i just implemented the logical part (Content) of the ContentControl.

My code behind :

[ContentProperty("Content")]
public class MyContentControl : Control
{
    public MyContentControl()
    {

    }

    public Object Content
    {
        get { return (Object)GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }

    public static readonly DependencyProperty ContentProperty =
        DependencyProperty.Register("Content", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata());

}

XAML :

<StackPanel x:Name="stackPanel">
    <TextBlock Visibility="Collapsed" x:Name="textBlock" Text="Hello World"/>
    <ContentControl>
        <TextBlock Background="LightBlue" Text="{Binding Text, ElementName=textBlock}"/>
    </ContentControl>
    <local:MyContentControl>
        <TextBlock Text="{Binding Text, ElementName=textBlock}"/>
    </local:MyContentControl>
</StackPanel>

I got the following binding error :

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=textBlock'. BindingExpression:Path=Text; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

It is like the inner TextBlock can't go up in the logical tree and find the original textblock on which it should bind. I wasn't able to set myContentControl as the parent of the Content object.

Any idee?

Thanks for your time.

Jonas

2

2 Answers

4
votes

Relevant question: Binding ElementName. Does it use Visual Tree or Logical Tree

The binding you want is not possible because the same instance of MyContentControl could theoretically be used somewhere else in the application where the element "textBlock" is not in the scope.

If you want to do this type of binding you could use a Resource instead:

xmlns:clr="clr-namespace:System;assembly=mscorlib"

<StackPanel>
    <StackPanel.Resources>
        <clr:String x:Key="MyText">Hanky Panky</clr:String>
    </StackPanel.Resources>
    <TextBlock Text="{StaticResource MyText}" />

    <ContentControl>
        <TextBlock Text="{Binding Source={StaticResource MyText}}" />
    </ContentControl>
</StackPanel>
2
votes

I Just had to apply FrameworkElement.AddLogicalChild and FrameworkElement.RemoveLogicalChild when the ContentChanged and the binding is correctly apply (Verified with WPF Inspector).

So all this is about LogicalTree (and maybe the xaml namescope is inherited from logical parent). The TextBlock inside MyContentControl get the MyContentControl as Parent when MyContentControl.AddLogicalChild(TextBlock) is called.

My Code :

[ContentProperty("Content")]
public class MyContentControl : Control
{
    public MyContentControl()
    {
        Content = new UIElementCollection(this, this);
    }


    public Object Content
    {
        get { return (Object)GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }

    public static readonly DependencyProperty ContentProperty =
        DependencyProperty.Register("Content", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata(new PropertyChangedCallback(OnContentChanged)));

    public static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MyContentControl c = (MyContentControl)d;

        if (e.OldValue != null)
        {
            c.RemoveLogicalChild(e.OldValue);
        }

        c.AddLogicalChild(e.NewValue);
    }

    protected override System.Collections.IEnumerator LogicalChildren
    {
        get
        {
            List<Object> l = new List<object>();
            if (Content != null)
                l.Add(Content);

            return l.GetEnumerator();
        }
    }
}