如果在构造函数中初始化属性,则似乎不会设置XAML绑定

时间:2009-11-22 15:26:50

标签: c# wpf data-binding xaml custom-controls

我在控件模板中遇到数据绑定问题,而属性在构造函数中初始化。

以下是展示案例(您也可以下载sample solution):

CustomControl1.cs

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

    public CustomControl1()
    {
        Content = "Initial"; // comment this line out and everything 
                             // will start working just great
    }
}

CustomControl1风格:

<Style TargetType="{x:Type local:CustomControl1}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:CustomControl1}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ContentPresenter />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

CustomControl2.cs:

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

CustomControl样式:

<Style TargetType="{x:Type local:CustomControl2}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:CustomControl2}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <local:CustomControl1 
                        Content="{Binding Content, 
                            RelativeSource={RelativeSource 
                                    AncestorType=local:CustomControl2}}" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Window1.xaml:

<Window x:Class="WpfApplication5.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300"
        xmlns:local="clr-namespace:WpfApplication5">
    <Grid>
        <local:CustomControl2 Content="Some content" />
    </Grid>
</Window>

所以,问题是:当您启动应用程序时,CustomControl1的内容似乎是“Initial”,它由构造函数设置,而不是“Some content”字符串,它应该是通过绑定来设置。

当我们从构造函数中删除初始化时,绑定开始工作。

首先,让我预测答案:“您应该在其元数据中设置依赖项属性的初始值:在注册时或通过元数据覆盖功能”。是的,你是对的,但是这个初始化方法的问题是属性是集合类型,所以如果我提供new MyCustomCollection()作为属性的默认值,那么CustomControl1的每个实例都是将分享该集合的相同实例,这显然不是主意。

我已经对问题进行了一些调试,结果如下:

  • 创建绑定实例,当我们将其放入类似元素的语法并为其指定x:Name时,可以通过Template.FindName("PART_Binding", this)内的OnApplyTemplate访问它。
  • 仅在属性上设置绑定:在同一OnApplyTemplate内,代码this.GetBindingExpression(ContentProperty)返回null
  • 绑定本身没有任何问题:在OnApplyTemplate内我们可以查找它然后我们可以简单地在属性上设置它:this.SetBinding(ContentProperty, myBinding) - 一切都会正常工作。

任何人都可以解释这种情况发生的原因和原因吗?

是否有人为依赖项属性设置非共享初始值的解决方案,因此绑定不会中断?

提前致谢!

UPD: 最奇怪的是,对于这两种情况,具有最高跟踪级别的调试输出是相同的:要么在没有发生绑定时,要么在没有发生时。

这就是:

System.Windows.Data Warning: 52 : Created BindingExpression (hash=18961937) for Binding (hash=44419000)
System.Windows.Data Warning: 54 :   Path: 'Content'
System.Windows.Data Warning: 56 : BindingExpression (hash=18961937): Default mode resolved to OneWay
System.Windows.Data Warning: 57 : BindingExpression (hash=18961937): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 58 : BindingExpression (hash=18961937): Attach to WpfApplication5.CustomControl1.Content (hash=47980820)
System.Windows.Data Warning: 62 : BindingExpression (hash=18961937): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 61 : BindingExpression (hash=18961937): Resolve source deferred
System.Windows.Data Warning: 63 : BindingExpression (hash=18961937): Resolving source 
System.Windows.Data Warning: 66 : BindingExpression (hash=18961937): Found data context element: <null> (OK)
System.Windows.Data Warning: 69 :     Lookup ancestor of type CustomControl2:  queried Border (hash=11653293)
System.Windows.Data Warning: 69 :     Lookup ancestor of type CustomControl2:  queried CustomControl2 (hash=54636159)
System.Windows.Data Warning: 68 :   RelativeSource.FindAncestor found CustomControl2 (hash=54636159)
System.Windows.Data Warning: 74 : BindingExpression (hash=18961937): Activate with root item CustomControl2 (hash=54636159)
System.Windows.Data Warning: 104 : BindingExpression (hash=18961937):   At level 0 - for CustomControl2.Content found accessor DependencyProperty(Content)
System.Windows.Data Warning: 100 : BindingExpression (hash=18961937): Replace item at level 0 with CustomControl2 (hash=54636159), using accessor DependencyProperty(Content)
System.Windows.Data Warning: 97 : BindingExpression (hash=18961937): GetValue at level 0 from CustomControl2 (hash=54636159) using DependencyProperty(Content): 'Some content'
System.Windows.Data Warning: 76 : BindingExpression (hash=18961937): TransferValue - got raw value 'Some content'
System.Windows.Data Warning: 85 : BindingExpression (hash=18961937): TransferValue - using final value 'Some content'

UPD2:添加了sample solution

的链接

4 个答案:

答案 0 :(得分:5)

答案 1 :(得分:3)

我已经在MSDN论坛上转发了这个问题,有人建议在Microsft Connect上创建一个问题......长话短说:我不清楚理解的关键机制是 DP的价值优先。它被完美地描述为here(本地值的优先级高于模板化的父级值)。

其次,如果由任何模板(甚至不是元素自己的模板)设置,则该值被视为模板化父级,并非如此明显。

HTH。

答案 2 :(得分:1)

也许您应该使用 TwoWay 绑定模式?你应该用“一些内容”来控制什么?由于绑定是OneWay,因此无法将其存储在控件的模型中。 在你的案例中,绑定会看到模型属性中有一个值,并将其覆盖为“一些内容”。如果不初始化属性,则绑定不执行任何操作,因为它忽略空值并且您看到“Some content”。 我希望我的解释清楚。

修改

很抱歉对您的问题没什么误解。我已经下载了您的演示应用并重现了该问题。 阅读thisthis MSDN文章显示您的意图是对的。但是你可以找到这样的话:

  

在计算设置依赖项属性值的SetValue调用期间,可能会调用以下虚方法或回调:ValidateValueCallback,PropertyChangedCallback,CoerceValueCallback,OnPropertyChanged。

因此,在构造函数中设置DependencyProperty的值可能与调用未构造的对象的虚方法一样危险。

好的,在构造函数中设置DependencyProperty是不好的。我的下一个想法是在一些回调中设置值(我使用OnInitialized,因为它应该在Control的构造函数之后调用)。我发现了另一个非常奇怪的行为。如果我没有在构造函数中设置任何值(这样)

    public CustomControl1()
    {
        //Content = "Initial1";
    }
    protected override void OnInitialized(EventArgs e)
    {
        Content = "Initial2";
        var check = Content; // after this  check == "Initial_2"
    }

即使我没有在Window1.xaml中为Content指定任何值,我也不会在窗口中看到“Initial2”。请注意,值设置正确(正如您所看到的那样)。 但如果我取消注释 Content =“Initial1”; 字符串,我会看到“Initial2”。此外,如果我初始化OnInitialized绑定中的内容工作正常,但它不解决内容的实际值是“Initial2”。看起来它的来源不是 内容属性。

我稍后会继续解决这个问题。 我希望这些信息可以提供帮助。

答案 3 :(得分:0)

不要在ctor中初始化值,请使用CoerceValue()

在ctor

public SomeUserControl()
{
    InitializeComponent();
    CoerceValue(SomeProperty);    
}

SomeProperty Defenition

public static readonly DependencyProperty SomeProperty =
    DependencyProperty.Register(
        "Some", typeof(ObservableCollection<IModel>),
        typeof(SomeUserControl),
        new PropertyMetadata()
        {
            DefaultValue = null,
            PropertyChangedCallback = OnSomeChanged,
            CoerceValueCallback = OnCoerceSome
        }
    );

private static object OnCoerceSome(DependencyObject d, object baseValue)
{
    var v = (ObservableCollection<IModel>)baseValue;
    return v ?? new ObservableCollection<IModel>();
}