Общее и надежное решение должно учитывать полный набор правил для Приоритет значения DependencyProperty .Я не думаю, что можно с уверенностью сказать, что если значение не установлено локально, тогда его значением будет значение по умолчанию - вместо этого значение может быть предоставлено стилем, триггером и т. Д.
Я бы посоветовал взглянуть на класс DependencyPropertyHelper
.Этот класс содержит метод с именем GetValueSource
, который при получении DependencyObject
и DependencyProperty
возвращает результат BaseValueSource , который сообщает вам, откуда берется значение для свойства (стиль, триггер стиля,наследуется, локально и т. д.).
При этом я думаю, что было бы не сложно написать прикрепленное поведение, которое вы могли бы использовать в XAML для возврата «нелокального» значения для указанного DependencyProperty
.Это поведение будет проверять, является ли источник значения локальным, и если он является клоном элемента, добавить его в логическое дерево и очистить локальное значение.Затем он будет читать значение из указанного DependencyProperty
на клоне и устанавливать присоединенное значение «1015 * только для чтения» к этому значению.Таким образом, вы позволяете механизму свойств WPF делать всю работу за вас.
Надеюсь, это поможет!
Редактировать:
Вот доказательство того, чтоконцепция для прикрепленного поведения.Поведение имеет 2 свойства:
- PropertyToInspectValue - значение
DependencyProperty
, чье значение вы хотите проверить. - 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
результатом, который затем может быть считан из.
Первая попытка этого будет:
- Очиститьлокальное значение для DP, сохраните его где-нибудь (например, используя
DependencyObject.ClearValue
) - Запросите DP для его нового значения, которое, как мы будем предполагать, будет таким, каким было бы значение, если бы оно не было установлено локально.Запишите это значение.
- Восстановите исходное значение с шага 1.
Этот подход не работает - в том случае, если на шаге 2 ваше свойство не будет выбирать значения из стилей по умолчанию (например)поскольку стиль уже применен, и простое удаление локального значения из случайного DependencyPropery
не вызывает повторного применения.
Другой подход был бы на шаге 2 выше - удалить элемент из логического дерева., а затем добавьте его обратно (поскольку при добавлении элементов в дерево WPF проходит через все возможные источники значений, чтобы определить значение каждого DP).Это также не выполняется по той же причине - вам не гарантируется повторное применение стилей.
Приведенное выше поведение пытается использовать третий подход - клонировать элемент, но убедиться, что рассматриваемое свойство не установлено в клоне, а затем добавить его в логическое дерево. На этом этапе WPF будет обрабатывать элемент так же, как это было бы с исходным элементом, и применять значение к DP (наследуемый, триггеры, стиль и т. Д.). Затем мы можем прочитать его значение и сохранить его. Как продемонстрировано, этот подход работает для общих случаев использования, но не является идеальным, поскольку, если нелокальное значение было получено из Trigger
, использующего свойство только для чтения, такое как IsMouseOver
, оно не получило бы его (и глубокое копия исходного состояния объекта не исправит это).