如果有人遇到类似问题,我想为 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;
}
}