WPF:为什么DataContextChanged没有在逻辑子级上引发?

WPF:为什么DataContextChanged没有在逻辑子级上引发?,wpf,datacontext,logical-tree,Wpf,Datacontext,Logical Tree,在自定义面板控件的逻辑子级上未引发DataContextChanged问题。我把范围缩小到: 从向导生成的WPF应用程序开始,我添加: private void Window_Loaded( object sender, RoutedEventArgs e ) { var elt = new FrameworkElement(); this.AddLogicalChild( elt ); DataContext = 42;

在自定义面板控件的逻辑子级上未引发DataContextChanged问题。我把范围缩小到:

从向导生成的WPF应用程序开始,我添加:

    private void Window_Loaded( object sender, RoutedEventArgs e )
    {
        var elt = new FrameworkElement();
        this.AddLogicalChild( elt );
        DataContext = 42;
        Debug.Assert( (int)elt.DataContext == 42 );
    }
据我所知,之所以这样做是因为数据上下文是一个

现在,我在窗口(此窗口)及其逻辑子窗口上为DataContextChanged添加事件处理程序:

    this.DataContextChanged += 
        delegate { Debug.WriteLine( "this:DataContextChanged" ); };
    elt.DataContextChanged += 
        delegate { Debug.WriteLine( "elt:DataContextChanged" ); };
如果我运行这个,那么只有第一个事件处理程序将执行这是为什么?如果我不使用AddLogicalChild(elt),而是执行以下操作:

this.Content = elt;
两个处理程序都将执行。但在我的例子中,这不是一个选项——我将FrameworkContentElements添加到我的控件中,这些元素不应该是可视的子元素

这是怎么回事?除了AddLogicalChild()之外,我还应该做些什么来让它工作吗

(幸运的是,有一个相当简单的解决方法-只需将元素的DataContext绑定到窗口的DataContext)


谢谢。

您还需要重写
LogicalChildren
属性:

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

当然,您还需要返回由基本实现定义的任何逻辑子级。

如果有人遇到类似问题,我想为Kent的答案添加一些建议:

如果使用多个内容对象创建自定义控件,则应确保:

  • 内容对象通过AddLogicalChild()添加到LogicalTree
  • 创建自己的枚举器类,并在重写的LogicalChildren属性中返回该类的实例
如果不将内容对象添加到逻辑树,则可能会遇到诸如无法解析与ElementName的绑定之类的问题(ElementName由FindName解析,FindName反过来使用LogicalTree查找元素)

更危险的是,根据我的经验,如果您没有将对象添加到逻辑树中,ElementName解析在某些情况下有效,而在其他情况下则不起作用

如果不重写LogicalChildren,则不会像上面所述那样更新DataContext

下面是一个简单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
    {
        get
        {
            return new SplitContainerLogicalChildrenEnumerator(this);
        }
    }
}
拆分容器:

    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
    {
        get
        {
            return new SplitContainerLogicalChildrenEnumerator(this);
        }
    }
}

是的,就是这样!谢谢你,肯特!
    internal class SplitContainerLogicalChildrenEnumerator : IEnumerator
{
    private readonly SplitContainer splitContainer;
    private int index = -1;

    public SplitContainerLogicalChildrenEnumerator(SplitContainer splitContainer)
    {
        this.splitContainer = splitContainer;
    }

    public object Current
    {
        get
        {
            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;
    }
}
<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>
                    <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>
<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>
    <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>
    public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = this;

        MyName = "Bruno";
    }

    public string MyName
    {
        get;
        set;
    }
}