Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I have a problem with DataContextChanged not being raised on a logical child of my custom Panel control. I've narrowed it down to this:

Starting from a wizard-generated WPF application I add:

    private void Window_Loaded( object sender, RoutedEventArgs e )
        var elt = new FrameworkElement();
        this.AddLogicalChild( elt );
        DataContext = 42;
        Debug.Assert( (int)elt.DataContext == 42 );

As I understand, this works because the DataContext is an inheritable dependency property.

Now, I add event handlers for DataContextChanged both on the Window (this) and its logical child:

    this.DataContextChanged += 
        delegate { Debug.WriteLine( "this:DataContextChanged" ); };
    elt.DataContextChanged += 
        delegate { Debug.WriteLine( "elt:DataContextChanged" ); };

If I run this, only the first event handler will execute. Why is this? If instead of AddLogicalChild( elt ) I do the following:

this.Content = elt;

both handlers will execute. But this is not an option in my case - I'm adding FrameworkContentElements to my control which aren't supposed to be visual children.

What's going on here? Should I do something more besides AddLogicalChild() to make it work?

(Fortunately, there is a rather simple workaround - just bind the DataContext of the element to the DataContext of the window)

BindingOperations.SetBinding( elt, FrameworkElement.DataContextProperty, 
            new Binding( "DataContext" ) { Source = this } );

Thank you.

You need to override the LogicalChildren property too:

protected override System.Collections.IEnumerator LogicalChildren
    get { yield return elt; }

Of course, you'll want to return any logical children defined by the base implementation, too.

I'd like to add some advice to Kent's answer if someone runs into a similar problems:

If you create a custom control with multiple content objects you should ensure:

  • Content objects are added to the LogicalTree via AddLogicalChild()
  • Create your own Enumerator class and return an instance of that in the overriden LogicalChildren property
  • If you don't add the content objects to the logical tree you might run into problems like Bindings with ElementNames can not be resolved (ElementName is resolved by FindName which in turn uses the LogicalTree to find the elements).

    What makes it even more dangerous is that my experience is that if you miss to add the objects to the logical tree the ElementName resolving works in some scenarios and it doesn't work in other scenarios.

    If you don't override LogicalChildren the DataContext is not updated like described above.

    Here a short example with a simple SplitContainer:

    SplitContainer:

        public class SplitContainer : Control
        static SplitContainer()
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitContainer), new FrameworkPropertyMetadata(typeof(SplitContainer)));
        /// <summary>
        /// Identifies the <see cref="Child1"/> property.
        /// </summary>
        public static readonly DependencyProperty Child1Property =
            DependencyProperty.Register(nameof(Child1), typeof(object), typeof(SplitContainer), new FrameworkPropertyMetadata(Child1PropertyChangedCallback));
        /// <summary>
        /// Left Container
        /// </summary>
        public object Child1
            get { return (object)GetValue(Child1Property); }
            set { SetValue(Child1Property, value); }
        /// <summary>
        /// Identifies the <see cref="Child2"/> property.
        /// </summary>
        public static readonly DependencyProperty Child2Property =
            DependencyProperty.Register(nameof(Child2), typeof(object), typeof(SplitContainer), new FrameworkPropertyMetadata(Child2PropertyChangedCallback));
        /// <summary>
        /// Right Container
        /// </summary>
        public object Child2
            get { return (object)GetValue(Child2Property); }
            set { SetValue(Child2Property, value); }
        private static void Child1PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
            var splitContainer = (SplitContainer)d;
            if (e.OldValue != null)
                splitContainer.RemoveLogicalChild(e.OldValue);
            if (e.NewValue != null)
                splitContainer.AddLogicalChild(((object)e.NewValue));
        private static void Child2PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
            var splitContainer = (SplitContainer)d;
            if (e.OldValue != null)
                splitContainer.RemoveLogicalChild(e.OldValue);
            if (e.NewValue != null)
                splitContainer.AddLogicalChild(((object)e.NewValue));
        protected override IEnumerator LogicalChildren
                return new SplitContainerLogicalChildrenEnumerator(this);
    

    SplitContainerLogicalChildrenEnumerator:

        internal class SplitContainerLogicalChildrenEnumerator : IEnumerator
        private readonly SplitContainer splitContainer;
        private int index = -1;
        public SplitContainerLogicalChildrenEnumerator(SplitContainer splitContainer)
            this.splitContainer = splitContainer;
        public object Current
                if (index == 0)
                    return splitContainer.Child1;
                else if (index == 1)
                    return splitContainer.Child2;
                throw new InvalidOperationException("No child for this index available");
        public bool MoveNext()
            index++;
            return index < 2;
        public void Reset()
            index = -1;
    

    Style (e.g. in Themes/generic.xaml):

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:scm="clr-namespace:System.ComponentModel;assembly=PresentationFramework"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
                    xmlns:local="clr-namespace:SplitContainerElementNameProblem">
    <Style TargetType="{x:Type local:SplitContainer}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:SplitContainer}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <ContentPresenter Grid.Column="0"
                                          Content="{TemplateBinding Child1}" />
                        <ContentPresenter Grid.Column="1"
                                          Content="{TemplateBinding Child2}" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    Sample which demonstrates that each Binding works fine:

    XAML:

    <Window x:Class="SplitContainerElementNameProblem.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SplitContainerElementNameProblem"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <TextBox x:Name="text1" Text="abc" />
        </Grid>
        <local:SplitContainer Grid.Row="1">
            <local:SplitContainer.Child1>
                <TextBox x:Name="text2"
                         Text="{Binding ElementName=text1, Path=Text}" />
            </local:SplitContainer.Child1>
            <local:SplitContainer.Child2>
                <StackPanel>
                    <TextBox x:Name="text3"
                             Text="{Binding ElementName=text2, Path=Text}" />
                    <TextBox x:Name="text4"
                             Text="{Binding MyName}" />
                </StackPanel>
            </local:SplitContainer.Child2>
        </local:SplitContainer>
    </Grid>
    

    XAML.cs

        public partial class MainWindow : Window
        public MainWindow()
            InitializeComponent();
            DataContext = this;
            MyName = "Bruno";
        public string MyName
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.