How do I reset a DependencyProperty back to it's default value in XAML
I'm implementing a user-adjus开发者_StackOverflow中文版table Effect
using sliders and I have a reset button beside the slider. The idea is to allow the user to reset back to the default value of the Effect
's property as specified in metadata.
I think it might be trivial to do that in XAML.
Dependency properties don't really have default values. If a dependency property doesn't have a local value, it will obtain its value either through value inheritance or through coercion, depending on how the property has been implemented.
You can't really get rid of the property's local value in XAML - that would require you to call ClearValue
on the property, and there's no way to find the object and call a method on it declaratively. But as long as the property's getting its value through value inheritance (instead of value coercion) - you can accomplish fundamentally the same thing by binding the property to the property it's inheriting from on the appropriate ancestor
For instance, here's a style you'd use to create a ListBox
that sets the foreground color of all items that aren't selected:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Foreground" Value="Red"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter
Property="Foreground"
Value="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox},
Path=Foreground}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
This basically explicitly implements value inheritance through binding. It does mean that the ListBoxItem.Foreground
property now has a local value, and that whenever you change the Foreground
property on the ListBox
it's binding that updates the ListBoxItem.Foreground
and not the dependency-property system. This might actually matter if you have hundreds of thousands of items in your ListBox
. But in most real-world cases, you'll never notice the difference.
In order to avoid logic cycles, WPF strongly resists attempts to set a DependencyProperty
value to its current value. This is a problem during initialization, where you'd like to have dependent logic triggered to set everything up for the first time based on the DefaultValue
recorded in the metadata, for each of the various dependent properties. WPF won't do it because, as a special case, all those properties already obtain their default values without ever having executed any such logic.
As far as I can tell, no combination of InvalidateProperty
, CoerceValue
, or ClearValue
will convince WPF to do the work. A trivial fix would be to somehow conjure a hopefully harmless non-default value to change it to, and then after that, reset the value via ClearValue
. This seems hacky, though, since it may not be practical to determine a "harmless" value. Perhaps worse, this approach will unnecessarily ripple the dependency graph twice, instead of just once.
As a perhaps more elegant solution, you can call the following extension method in the constructor of each relevant object to manually invoke the PropertyChanged
logic for each DependencyProperty
that needs to have its changes propagated. This helper will trigger follow-on changes according the default value stored in the respective metadata.
public static void ResetValue(this DependencyObject _this, DependencyProperty dp)
{
var md = dp.GetMetadata(_this.GetType());
if (_this.GetValue(dp).Equals(md.DefaultValue))
{
var args = new DependencyPropertyChangedEventArgs(dp, DependencyProperty.UnsetValue, md.DefaultValue);
md.PropertyChangedCallback(_this, args);
}
else
_this.ClearValue(dp);
}
As shown, the function includes a check to see if ClearValue
would in fact be effective, so beyond initialization scenarios, you can also use this function as a replacement for ClearValue
in general. For clarity of demonstration, this method does not verify that the host object in fact exposes any property-change logic, but this would be a simple change. In C# 7, for example:
// ...
md.PropertyChangedCallback?.Invoke(_this, args);
Example usage in your class:
class MyClass : DependencyObject
{
public static readonly DependencyProperty MyDp = /* ... */
public MyClass()
{
this.ResetValue(MyDp);
/* ... */
}
};
精彩评论