1

我遇到了一个问题,如果我的一个用户控件在初始化时该控件不可见,则绑定没有正确设置。我用以下简化的控件复制了这个问题:

public class Test3 : Control
    {
        static Test3()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Test3), new FrameworkPropertyMetadata(typeof(Test3)));
        }

        public string Test
        {
            get { return (string)GetValue(TestProperty); }
            set { SetValue(TestProperty, value); }
        }

        public static readonly DependencyProperty TestProperty =
            DependencyProperty.Register("Test", typeof(string), 
            typeof(Test3), new UIPropertyMetadata("test3 default text"));        
    }  

public class Test2 : Control
    {
        static Test2()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Test2), new FrameworkPropertyMetadata(typeof(Test2)));
        }

        public FrameworkElement Test3Control
        {
            get { return (FrameworkElement)GetValue(Test3ControlProperty); }
            set { SetValue(Test3ControlProperty, value); }
        }

        public static readonly DependencyProperty Test3ControlProperty =
            DependencyProperty.Register("Test3Control", typeof(FrameworkElement), 
            typeof(Test2), new UIPropertyMetadata(null));

        public string Test
        {
            get { return (string)GetValue(TestProperty); }
            set { SetValue(TestProperty, value); }
        }

        public static readonly DependencyProperty TestProperty =
            DependencyProperty.Register("Test", typeof(string), typeof(Test2), 
            new UIPropertyMetadata("test2 default text"));
    }

public partial class Test1 : UserControl
    {
        public Test1()
        {
            InitializeComponent();
        }

        public string Test
        {
            get { return (string)GetValue(TestProperty); }
            set { SetValue(TestProperty, value); }
        }

        public static readonly DependencyProperty TestProperty =
            DependencyProperty.Register("Test", typeof(string), 
            typeof(Test1), new UIPropertyMetadata("test1 default text"));        
    }

用于用户控件的 XAML:

<UserControl x:Class="Test.Test1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:dummy="clr-namespace:WpfTestApplication.Test"
             Name="ucThis">

    <UserControl.Resources>
        <Style TargetType="{x:Type test:Test2}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type test:Test2}">
                        <GroupBox Header="Test2">
                            <StackPanel>
                                <TextBox IsEnabled="False" Text="{TemplateBinding Test}"/>
                                <GroupBox Header="Test3">
                                    <ContentPresenter Content="{TemplateBinding Test3Control}"/>
                                </GroupBox>
                            </StackPanel>
                        </GroupBox>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>

    <Grid>
        <test:Test2 Test="{Binding ElementName=ucThis,Path=Test}">
            <test:Test2.Test3Control>
                <test:Test3 Test="{Binding ElementName=ucThis,Path=Test}">
                    <test:Test3.Template>
                        <ControlTemplate TargetType="{x:Type test:Test3}">
                            <TextBox IsEnabled="False" Text="{TemplateBinding Test}"/>
                        </ControlTemplate>
                    </test:Test3.Template>
                </test:Test3>
            </test:Test2.Test3Control>
        </test:Test2>
    </Grid>
</UserControl>

...和主窗口的 XAML(无论如何,它的胆量):

<DockPanel>            
    <StackPanel>
        <TextBox Name="tbInput"/>    

        <Expander Header="Initially Visible" IsExpanded="True">
            <test:Test1 Test="{Binding ElementName=tbInput, Path=Text}" />
        </Expander>

        <Expander Header="Initially Collapsed" IsExpanded="False">
            <test:Test1 Test="{Binding ElementName=tbInput, Path=Text}" />
        </Expander>
    </StackPanel>    
</DockPanel>

我希望在文本框(“tbInput”)中输入的任何文本都将显示在 Test2 和 Test3 框中 - 实际上它适用于 Test2 的两个实例,但仅适用于最初可见的 Test3。最初折叠的 Test3 始终显示默认文本,即使在输入文本时它是可见的。

我尝试使用 Snoop 对此进行调查,但是当我使用 Snoop 评估树的相关部分时,问题会自行纠正,因此它没有太大帮助。

是什么导致了这种行为?我该如何纠正?我在哪里可以阅读更多关于它的信息?

更新:

通过观察输出窗口,我发现了这个错误信息: System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ucThis'. BindingExpression:Path=Test; DataItem=null; target element is 'Dummy3' (Name=''); target property is 'Test' (type 'String')

通过处理这些控件上的加载和初始化事件,我可以看到这个错误发生在 Dummy1 和 Dummy2 在启动时加载之后。Dummy 3 在可见之前不会加载。

4

2 回答 2

1

我认为问题在于,由于模板尚未应用于折叠Test3实例,因此尚未将其插入可视化树中。因此,在创建绑定时,它不在外部实例的名称范围内Test1,因此无法解析为ucThis指定的名称ElementName

您可以通过添加Test3ControlTest2. 尝试修改依赖属性定义如下:

public static readonly DependencyProperty Test3ControlProperty =
    DependencyProperty.Register("Test3Control", typeof(FrameworkElement),
    typeof(Test2), 
    new UIPropertyMetadata(null, OnTest3ControlPropertyChanged));

private static void OnTest3ControlPropertyChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var source = (Test2)d;

    var oldValue = e.OldValue as FrameworkElement;
    var newValue = e.NewValue as FrameworkElement;

    if (oldValue != null)
        source.RemoveLogicalChild(oldValue);

    if (newValue != null)
        source.AddLogicalChild(newValue);
}

在大多数情况下,当您UIElement向控件添加基于 - 的新属性时,您会希望确保将其添加到逻辑树中。这不会自动发生。就此而言,它也不会自动添加到可视化树中。在这种情况下,它只被加载到可视化树中,因为它被显式地插入到模板中。

查看 WPF 核心控件的内部结构,例如Decorator(从中Border派生)及其Child属性,以了解在定义新控件类型时可能需要什么样的管道。

还要注意有多少“子控件”属性不是依赖属性。基于 - 的依赖属性很容易遇到问题Visual,尤其是当您尝试通过设置器更改它们或为它们设置动画时。我发现最好通过简单地将子控件公开为常规 CLR 属性来阻止开发人员滥用属性并为自己制造麻烦。

于 2013-11-12T22:09:55.230 回答
0

我不确定您的具体问题,但我会将 Test2 的隐式样式定义移动到必须位于“主题”文件夹中的 Generic.xaml 模块中。框架将自动扫描该文件以查找隐式样式。

于 2013-11-09T07:22:38.257 回答