从DataTemplate设置自定义DependencyProperty

4
我正在使用一个自定义控件,其中包含多个用户定义的依赖属性。我遇到了与这个问题描述相同的问题。
我的控件在构造函数中设置自定义依赖属性的默认值。当我在中使用控件时,即使我尝试在XAML中设置它,也始终使用在构造函数中设置的值。
链接问题的答案解释说,C#代码中设置的值具有更高的优先级,更好的方法是在依赖属性的元数据中指定默认值。
在我的情况下,我无法指定默认值,因为依赖属性没有适用于所有情况的单个默认值。默认值取决于另一个属性,因此必须在创建控件时查找它们,而不是在注册属性时查找它们。
以下是一些代码,以帮助说明我的问题:
public partial class MyControl : UserControl
{
    public static readonly DependencyProperty MyProperty = 
            DependencyProperty.Register(
                "MyProperty",
                typeof(int),
                typeof(MyControl),
                new FrameworkPropertyMetadata(
                    int.MinValue,
                    FrameworkPropertyMetadataOptions.None,
                    new PropertyChangedCallback("OnMyPropertyChanged")));

    public MyControl() : base()
    {
        InitializeComponent();
        this.MyProperty = GetDefaultPropertyValue();
    }

    public int MyProperty
    {
        get { return (int)GetValue(MyProperty); }
        set { SetValue(MyProperty, value); }
    }

    private int GetDefaultPropertyValue()
    {
        // look up the appropriate default based on some other criteria
        return 42;

        // (in reality, the default value for "MyProperty"
        // depends on the value of a "Mode" custom DependencyProperty.
        // this is just hard coded for testing)
    }   
}

XAML的用法大致如下:
<!-- View displays 4 (desired) -->
<local:MyControl MyProperty="4" />

<!-- View displays default of 42 (desired) -->
<local:MyControl />

<!-- View displays default of 42 (wanted 4) -->
<DataTemplate x:Key="MyTemplate">
    <local:MyControl MyProperty="4"/>
</DataTemplate>

总结一下:
期望的行为是首先使用XAML中的值。如果在XAML中没有指定该值,则希望回退到控件构造函数中设置的默认值。
如果我直接在视图中包含控件,则会得到预期的行为。如果该控件在DataTemplate中使用,则始终会得到构造函数中设置的默认值(即使数据模板明确设置了其他值)。
是否有其他方法可以在模板中使用控件时指定默认值?我能想到的唯一选择是将控件分成几个类似但不同的控件,每个控件都使用注册到依赖属性的默认值(这样就不需要基于Mode属性设置默认值)。
1个回答

4
在添加一小项检查的同时,在OnApplyTemplate中设置默认值可以解决这个问题:
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    // Only set the default value if no value is set.
    if (MyProperty == (int)MyPropertyProperty.DefaultMetadata.DefaultValue)
    {
        this.MyProperty = GetDefaultPropertyValue();
    }
}

请注意,虽然这个方法可以运行,但并不理想,因为通过代码设置属性的值会清除该属性的任何数据绑定。例如,一旦您在代码中调用MyProperty = 42,以下绑定将不再起作用:
<local:MyControl MyProperty="{Binding SomeProperty}" />

可以使用SetCurrentValue(MyPropertyProperty, GetDefaultPropertyValue());来修改属性的值,而不是使用MyProperty = GetDefaultPropertyValue(),这样可以保持任何绑定的状态,但我不确定是否喜欢这种方法。

更好的解决方案

我会引入一个新的只读属性,作为计算属性。例如:

private static readonly DependencyPropertyKey MyCalculatedPropertyPropertyKey =
    DependencyProperty.RegisterReadOnly("MyCalculatedProperty", typeof(int), typeof(MyControl),
    new PropertyMetadata(int.MinValue));

public static readonly DependencyProperty MyCalculatedPropertyProperty = MyCalculatedPropertyPropertyKey.DependencyProperty;

public int MyCalculatedProperty
{
    get { return (int)GetValue(MyCalculatedPropertyProperty); }
    private set { SetValue(MyCalculatedPropertyPropertyKey, value); }
}

private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    ((MyControl)d).MyCalculatedProperty = (int)e.NewValue;
}

public MyControl()
    : base()
{
    InitializeComponent();
    MyCalculatedProperty = GetDefaultPropertyValue();
}

这个可以工作,谢谢!既然我只是在代码中设置属性,如果它没有在XAML中设置,那么可以安全地假设没有数据绑定会被破坏吗?(我们从不在代码中创建绑定) - zmb
在你的示例中,你展示了如何在XAML中设置属性:<local:MyControl MyProperty="4" /> 如果这是唯一应该设置属性的方式,而且永远不会使用绑定(请参见我的编辑),那么你就不必担心,尽管覆盖绑定并不被认为是一种好的做法。 - Adi Lester
我知道 MyProperty = 42; 会破坏任何绑定。如果我正在使用像您在编辑中添加的绑定,只要绑定未计算为 MyProperty.DefaultMetadata.DefaultValue,那么就不会调用该值并且绑定不会被破坏,对吗?您能详细说明一下只读属性的推荐解决方案吗?我不确定我是否理解了。 - zmb
1
我用更好的解决方案使事情正常运作,并接受了你的答案。但是我不知道这是如何工作的。添加一个只读依赖属性如何影响原始属性? - zmb
请参见此答案中关于“SetValue”和“SetCurrentValue”之间差异的更多细节。 - John Cummings

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接