【问题标题】:WPF: Why is DataContextChanged not raised on a logical child?WPF:为什么 DataContextChanged 没有在逻辑孩子上提出?
【发布时间】:2009-04-06 23:40:39
【问题描述】:

DataContextChanged 没有在我的自定义 Panel 控件的逻辑子项上引发问题。我把它缩小到这个:

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

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

据我了解,这是可行的,因为 DataContextinheritable dependency property

现在,我在 Window (this) 及其逻辑子级上为 DataContextChanged 添加事件处理程序:

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

如果我运行它,只有第一个事件处理程序会执行。 为什么会这样?如果不是 AddLogicalChild(elt) 我执行以下操作:

this.Content = elt;

两个处理程序都会执行。但在我的情况下,这不是一个选项 - 我将 FrameworkContentElements 添加到我的控件中,这些控件不应该是可视子项。

这里发生了什么?除了 AddLogicalChild() 之外,我还应该做更多的事情来使它工作吗?

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

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

谢谢。

【问题讨论】:

    标签: wpf datacontext logical-tree


    【解决方案1】:

    您还需要覆盖 LogicalChildren 属性:

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

    当然,您也希望返回由基本实现定义的任何逻辑子代。

    【讨论】:

    • 是的,就是这样!谢谢,肯特!
    【解决方案2】:

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

    如果您创建一个包含多个内容对象的自定义控件,您应该确保:

    • 通过 AddLogicalChild() 将内容对象添加到 LogicalTree
    • 创建您自己的 Enumerator 类并在覆盖的 LogicalChildren 属性中返回该类的实例

    如果您不将内容对象添加到逻辑树中,您可能会遇到无法解析与 ElementNames 的绑定等问题(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);
            }
        }
    }
    

    SplitContainerLogicalChildrenEnumerator:

        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;
        }
    }
    

    样式(例如在 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>
                        <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>
    

    证明每个绑定都可以正常工作的示例:

    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>
        <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
        {
            get;
            set;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-05-16
      • 1970-01-01
      • 2011-05-31
      • 1970-01-01
      • 2020-07-12
      • 1970-01-01
      • 1970-01-01
      • 2017-12-26
      • 2022-01-13
      相关资源
      最近更新 更多