我在控件模板中遇到数据绑定问题,而属性在构造函数中初始化。
以下是展示案例(您也可以下载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
的链接答案 0 :(得分:5)
请参阅此答案:Binding Setting Property but UI not updating. Can I debug within referenced project/control?
在构造函数中使用SetCurrentValue()。
答案 1 :(得分:3)
我已经在MSDN论坛上转发了这个问题,有人建议在Microsft Connect上创建一个问题......长话短说:我不清楚理解的关键机制是 DP的价值优先。它被完美地描述为here(本地值的优先级高于模板化的父级值)。
其次,如果由任何模板(甚至不是元素自己的模板)设置,则该值被视为模板化父级,并非如此明显。
HTH。
答案 2 :(得分:1)
也许您应该使用 TwoWay 绑定模式?你应该用“一些内容”来控制什么?由于绑定是OneWay,因此无法将其存储在控件的模型中。 在你的案例中,绑定会看到模型属性中有一个值,并将其覆盖为“一些内容”。如果不初始化属性,则绑定不执行任何操作,因为它忽略空值并且您看到“Some content”。 我希望我的解释清楚。
修改强>
很抱歉对您的问题没什么误解。我已经下载了您的演示应用并重现了该问题。 阅读this和this 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>();
}