Можно ли получить то значение, которое будет иметь DependencyProperty на каждом из уровней приоритета, а не только на конечном значении? - PullRequest
2 голосов
/ 11 декабря 2011

Мы пытаемся получить то, что было бы значением DependencyProperty, если бы оно не было установлено локально в XAML.Например, возьмите этот 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 ]

2 голосов
/ 11 декабря 2011

Общее и надежное решение должно учитывать полный набор правил для Приоритет значения DependencyProperty .Я не думаю, что можно с уверенностью сказать, что если значение не установлено локально, тогда его значением будет значение по умолчанию - вместо этого значение может быть предоставлено стилем, триггером и т. Д.

Я бы посоветовал взглянуть на класс DependencyPropertyHelper.Этот класс содержит метод с именем GetValueSource, который при получении DependencyObject и DependencyProperty возвращает результат BaseValueSource , который сообщает вам, откуда берется значение для свойства (стиль, триггер стиля,наследуется, локально и т. д.).

При этом я думаю, что было бы не сложно написать прикрепленное поведение, которое вы могли бы использовать в XAML для возврата «нелокального» значения для указанного DependencyProperty.Это поведение будет проверять, является ли источник значения локальным, и если он является клоном элемента, добавить его в логическое дерево и очистить локальное значение.Затем он будет читать значение из указанного DependencyProperty на клоне и устанавливать присоединенное значение «1015 * только для чтения» к этому значению.Таким образом, вы позволяете механизму свойств WPF делать всю работу за вас.

Надеюсь, это поможет!

Редактировать:

Вот доказательство того, чтоконцепция для прикрепленного поведения.Поведение имеет 2 свойства:

  1. PropertyToInspectValue - значение DependencyProperty, чье значение вы хотите проверить.
  2. InspectedValue - вычисленное нелокальное значение для указанного DependencyProperty.

В XAML вы связываете эти 2 свойства (задаете одно, читаете другое):

<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 должен быть дочерним по отношению к Panel, и в настоящее время копируется только свойство Style, но на самом деле все свойства должны быть клонированы.поскольку триггеры в стиле могут использовать любое свойство для изменения значения DependencyProperty.Пища для размышлений, я думаю ...

Прикрепленное поведение:

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));
        }
    }
}

Редактировать 2

Чтобы уточнить этот подход.В WPF нет цепочки ценностей, которые мы можем проверить (или, по крайней мере, о которых я знаю).Приведенный выше код пытается выяснить, какое значение было бы, если бы оно не было установлено локально (из любого источника значения: унаследованного, стиля, триггера и т. Д.).Когда он обнаруживает это значение, он затем заполняет свойство InspectedValue результатом, который затем может быть считан из.

Первая попытка этого будет:

  1. Очиститьлокальное значение для DP, сохраните его где-нибудь (например, используя DependencyObject.ClearValue)
  2. Запросите DP для его нового значения, которое, как мы будем предполагать, будет таким, каким было бы значение, если бы оно не было установлено локально.Запишите это значение.
  3. Восстановите исходное значение с шага 1.

Этот подход не работает - в том случае, если на шаге 2 ваше свойство не будет выбирать значения из стилей по умолчанию (например)поскольку стиль уже применен, и простое удаление локального значения из случайного DependencyPropery не вызывает повторного применения.

Другой подход был бы на шаге 2 выше - удалить элемент из логического дерева., а затем добавьте его обратно (поскольку при добавлении элементов в дерево WPF проходит через все возможные источники значений, чтобы определить значение каждого DP).Это также не выполняется по той же причине - вам не гарантируется повторное применение стилей.

Приведенное выше поведение пытается использовать третий подход - клонировать элемент, но убедиться, что рассматриваемое свойство не установлено в клоне, а затем добавить его в логическое дерево. На этом этапе WPF будет обрабатывать элемент так же, как это было бы с исходным элементом, и применять значение к DP (наследуемый, триггеры, стиль и т. Д.). Затем мы можем прочитать его значение и сохранить его. Как продемонстрировано, этот подход работает для общих случаев использования, но не является идеальным, поскольку, если нелокальное значение было получено из Trigger, использующего свойство только для чтения, такое как IsMouseOver, оно не получило бы его (и глубокое копия исходного состояния объекта не исправит это).

0 голосов
/ 11 декабря 2011

Вы должны использовать перегруженный метод GetPropertyMetaData, который лучше всего соответствует вашим потребностям (это должен быть тот, который использует типы), а затем вы можете получить значение по умолчанию с помощью ... свойства DefaultValue.

...