是否有可能获得DependencyProperty在每个优先级别上的值,而不仅仅是最终值?

时间:2011-12-11 06:01:31

标签: wpf dependency-properties default-value

如果未在XAML中本地设置,我们试图获得DependencyProperty的值。例如,拿这个XAML ......

<StackPanel Orientation="Vertical"
    TextElement.FontSize="24">

    <TextBlock Text="What size am I?"
        FontSize="{Binding FontSize, RelativeSource={RelativeSource Self}, Converter={StaticResource PercentageConverter}, ConverterParameter=0.75}" />

</StackPanel>

这当然不起作用。我所做的却是......

<TextBlock Text="What size am I?"
    FontSize="{Binding (TextElement.FontSize), RelativeSource={RelativeSource AncestorType=FrameworkElement}, Converter={StaticResource PercentageConverter}, ConverterParameter=0.5}" />

...它检查继承的FontSize属性的父值,但这只能起作用,因为FontSize 支持继承。我正在寻找任何DependencyProperty的解决方案,而不仅仅是可以继承的解决方案。

更新

我将问题的标题更改为对我们有用的内容更为通用。具体来说,我们希望能够说'对于DependencyObject的特定实例,DependencyProperty对每个优先级的价值是多少?当我们查询DP时,我们当然得到应用所有优先级后的值。我们很乐意说'它会更高一两级?'等等。

2 个答案:

答案 0 :(得分:2)

通用且强大的解决方案应考虑DependencyProperty value precedence的完整规则集。我不认为如果没有在本地设置一个值,那么它的值将是默认值 - 然后可以通过样式,触发器等提供该值,这是不安全的。

我建议你看看班级DependencyPropertyHelper。此类包含一个名为GetValueSource的方法,当给定DependencyObjectDependencyProperty时,将返回BaseValueSource结果,该结果会告诉您属性的值来自何处(样式) ,样式触发器,继承,本地等)。

话虽如此,我认为编写一个可以在XAML中用于返回指定DependencyProperty的“非本地”值的附加行为并不会太难。此行为将检查值源是否为本地,如果它是克隆元素,则将其添加到逻辑树,并清除本地值。然后,它将从克隆上的指定DependencyProperty读取值,并将只读的附加DependencyProperty设置为此值。这样,您就可以让WPF属性引擎为您完成工作。

希望这有帮助!

修改

以下是附加行为的概念证明。该行为有2个属性:

  1. PropertyToInspectValue - 对您有价值的DependencyProperty 希望检查。
  2. InspectedValue - 指定的DependencyProperty的计算非本地值。
  3. 在XAML中,你绑定到这两个属性(设置一个,读另一个):

    <StackPanel>
        <StackPanel>
            <StackPanel.Resources>
                <Style TargetType="{x:Type TextBlock}">
                    <Setter Property="FontSize" Value="30" />
                </Style>
                <Style x:Key="NamedStyle" TargetType="{x:Type TextBlock}">
                    <Setter Property="FontSize" Value="35" />
                </Style>
            </StackPanel.Resources>
            <!-- Without local value - uses default style (30)-->
            <TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
                       Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
                       FontSize="15" />
            <!-- Without local value - uses named style (35)-->
            <TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
                       Style="{StaticResource NamedStyle}"
                       Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
                       FontSize="15" />
        </StackPanel>
        <StackPanel TextElement.FontSize="25">
            <!-- Without local value - uses inherited value (25) -->
            <TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
                       Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
                       FontSize="15" />
        </StackPanel>
        <!-- Without local value - uses default font size (11) -->
        <TextBlock l:DependencyPropertyValueHelper.PropertyToInspectValue="TextElement.FontSize"
                   Text="{Binding Path=(l:DependencyPropertyValueHelper.InspectedValue), RelativeSource={RelativeSource Self}}"
                   FontSize="15" />
    </StackPanel>
    

    上面的示例将正确显示值:30,35,25和11.以下代码中的一些限制是我们正在检查的DependencyObject必须是{{1}的子项当前只复制Panel属性,但实际上应该克隆所有属性,因为样式中的触发器可以使用任何属性来更改值Style。值得深思的是我猜...

    附加行为:

    DependencyProperty

    修改2

    详细说明这种方法。 WPF没有公开任何价值链,我们可以检查(或者至少我知道)。上面的代码试图发现如果没有在本地设置它的值(从任何值源:继承,样式,触发器等)。当它发现这个值时,它会用结果填充public static class DependencyPropertyValueHelper { private static bool _isUpdating; #region InspectedValue Read-only Attached Dependency Property public static object GetInspectedValue(DependencyObject d) { return d.GetValue(InspectedValueProperty); } private static readonly DependencyPropertyKey InspectedValuePropertyKey = DependencyProperty.RegisterAttachedReadOnly("InspectedValue", typeof (object), typeof (DependencyPropertyValueHelper), new FrameworkPropertyMetadata(null)); public static readonly DependencyProperty InspectedValueProperty = InspectedValuePropertyKey.DependencyProperty; #endregion #region PropertyToInspect Attached Dependency Property public static void SetPropertyToInspectValue(DependencyObject d, DependencyProperty dependencyProperty) { d.SetValue(PropertyToInspectValueProperty, dependencyProperty); } public static DependencyProperty GetPropertyToInspectValue(DependencyObject d) { return (DependencyProperty)d.GetValue(PropertyToInspectValueProperty); } public static readonly DependencyProperty PropertyToInspectValueProperty = DependencyProperty.RegisterAttached("PropertyToInspectValue", typeof (DependencyProperty), typeof (DependencyPropertyValueHelper), new FrameworkPropertyMetadata(OnPropertyToInspectValuePropertyChanged)); #endregion #region Private ValueChanged Attached Dependency Property public static readonly DependencyProperty ValueChangedProperty = DependencyProperty.RegisterAttached("ValueChanged", typeof(object), typeof(DependencyPropertyValueHelper), new FrameworkPropertyMetadata(OnValuePropertyChanged)); #endregion private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!_isUpdating) DetermineNonLocalValue(d, GetPropertyToInspectValue(d)); } private static void OnPropertyToInspectValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var dependencyProperty = (DependencyProperty) e.NewValue; DetermineNonLocalValue(d, dependencyProperty); var binding = new Binding { RelativeSource = new RelativeSource(RelativeSourceMode.Self), Path = new PropertyPath(dependencyProperty.Name), }; BindingOperations.SetBinding(d, ValueChangedProperty, binding); } private static void DetermineNonLocalValue(DependencyObject d, DependencyProperty dependencyProperty) { var element = d as FrameworkElement; if (element == null) return; var valueSource = DependencyPropertyHelper.GetValueSource(element, dependencyProperty); if (valueSource.BaseValueSource == BaseValueSource.Local) { _isUpdating = true; var clonedDependencyObject = Activator.CreateInstance(element.GetType()) as FrameworkElement; var parent = VisualTreeHelper.GetParent(element) as Panel; // Currently only works if parent is a panel if (parent != null) { // Copy any property which could impact the DP's value ... // Probably check if this is a control and copy the ControlTemplate too ... if (element.Style != null) clonedDependencyObject.Style = element.Style; parent.Children.Add(clonedDependencyObject); // Let WPF provide us with the non-local value element.SetValue(InspectedValuePropertyKey, clonedDependencyObject.GetValue(dependencyProperty)); parent.Children.Remove(clonedDependencyObject); } _isUpdating = false; } else { element.SetValue(InspectedValuePropertyKey, d.GetValue(dependencyProperty)); } } } 属性,然后可以从中读取。

    首次尝试是:

    1. 清除DP的本地值,将其存储在某处(例如使用InspectedValue
    2. 查询DP的新值,我们将假设该值是未在本地设置的值。记录此值。
    3. 从步骤1恢复原始值。
    4. 这种方法失败了 - 因为在第2步中你的属性不会从默认样式中获取值(例如),因为样式已经应用了,只是从随机DependencyObject.ClearValue中删除本地值不会触发重新申请。

      另一种方法是在上面的步骤2中 - 从逻辑树中删除元素,然后将其添加回来(因为当元素添加到树中时,WPF会遍历所有可能的值源以确定每个DP的值) 。由于同样的原因,这也失败了 - 你不能保证重新应用样式。

      上面的附加行为尝试第三种方法 - 克隆元素但确保未在克隆上设置有问题的属性,然后将其添加到逻辑树中。此时,WPF将处理元素,就像它对原始元素一样,并将值应用于DP(继承,触发器,样式等)。然后我们可以读取它的值,并将其存储起来。如上所示,这种方法适用于常见的用例,但并不完美,因为如果非本地值来自DependencyPropery使用只有Trigger这样的只读属性,它就不会捡起它(并且原始对象状态的深层副本不会解决此问题)。

答案 1 :(得分:0)

您应该使用最适合您需要的GetPropertyMetaData重载方法(应该是使用类型的方法),然后您可以使用... DefaultValue属性检索默认值。