我的自定义Panel控件的逻辑子项上没有出现 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 );
}
据我了解,这是有效的,因为 DataContext 是inheritable 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 } );
谢谢。
答案 0 :(得分:5)
您还需要覆盖LogicalChildren
属性:
protected override System.Collections.IEnumerator LogicalChildren
{
get { yield return elt; }
}
当然,您也希望返回由基本实现定义的任何逻辑子项。
答案 1 :(得分:1)
如果有人遇到类似的问题,我想在 Kent 的回答中添加一些建议:
如果您使用多个内容对象创建自定义控件,则应确保:
如果您不将内容对象添加到逻辑树中,您可能会遇到诸如 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;
}
}