What's the difference between Dependency Property SetValue() & SetCurrentValue()
The MSDN link you provided says it quite well:
This method is used by a component that programmatically sets the value of one of its own properties without disabling an application's declared use of the property. The SetCurrentValue method changes the effective value of the property, but existing triggers, data bindings, and styles will continue to work.
Suppose you're writing the TextBox
control and you've exposed a Text
property that people often use as follows:
<TextBox Text="{Binding SomeProperty}"/>
In your control's code, if you call SetValue
you will overwrite the binding with whatever you provide. If you call SetCurrentValue
, however, will ensure that the property takes on the given value, but won't destroy any bindings.
To the best of my knowledge, Greg's advice is incorrect. You should always use GetValue
/SetValue
from your CLR wrapper property. SetCurrentValue
is more useful in scenarios where you need a property to take on a given value but don't want to overwrite any bindings, triggers, or styles that have been configured against your property.
demo harness (complete):
class test : DependencyObject
{
static DependencyProperty XyzProperty =
DependencyProperty.Register("Xyz", typeof(int), typeof(test), new PropertyMetadata(42));
public test()
{
/* ... see code shown below ... */
}
void inf()
{
var info = DependencyPropertyHelper.GetValueSource(this, XyzProperty);
var msg = $@"{"//"
} {(int)GetValue(XyzProperty),2
} {(ReadLocalValue(XyzProperty) is int x ? "(Object)" + x : "UnsetValue"),12
} {info.BaseValueSource,9
} {(info.IsCurrent ? "" : "Not") + "Current",12
} {(info.IsCoerced ? "" : "Not") + "Coerced",12
}";
Trace.WriteLine(msg);
}
};
discussion examples:
// v̲a̲l̲u̲e̲ s̲t̲o̲r̲e̲d̲-o̲b̲j̲ B̲V̲S̲ C̲u̲r̲r̲e̲n̲t̲? C̲o̲e̲r̲c̲e̲d̲?
/*1*/ // 42 UnsetValue Default NotCurrent NotCoerced
/*2*/ SetValue(XyzProperty, 5); // 5 (Object)5 Local NotCurrent NotCoerced
/*3*/ SetValue(XyzProperty, 42); // 42 (Object)42 Local NotCurrent NotCoerced
/*4*/ ClearValue(XyzProperty); // 42 UnsetValue Default NotCurrent NotCoerced
/*5*/ SetCurrentValue(XyzProperty, 5); // 5 (Object)5 Default Current Coerced
/*6*/ SetCurrentValue(XyzProperty, 42); // 42 UnsetValue Default NotCurrent NotCoerced
/*7*/ SetValue(XyzProperty, 5); // 5 (Object)5 Local NotCurrent NotCoerced
SetCurrentValue(XyzProperty, 42); // 42 (Object)42 Local Current Coerced
discussion:
-
Initial state of an absent
DependencyProperty
which has aDefaultValue
of '42' has theBaseValueSource.Default
flag asserted.ReadLocalValue()
returns the global singleton instanceDependencyProperty.UnsetValue
. -
SetValue()
internally stores aBaseValueSource.Local
value as expected. -
Using
SetValue
to store a value which happens to equal to theDefaultValue
does not restore theBaseValueSource.Default
state (compare to #6, below). -
Instead, if you want to remove any/all stored value or binding and restore the DP to pristine, call
ClearValue()
. (see note below) -
With
SetCurrentValue()
, the property value is produced via coercion, and without asserting theBaseValueSource.Local
mode. Notice that the previousBaseValueSource
Default still prevails despite the property now reporting a value which is not, in fact, equal to itsDefaultValue
.
Important:
This means that checking that theBaseValueSource
returned byGetValueSource()
state isBaseValueSource.Default
is not a reliable indicator of whether the prevailing property value equals the default value from the DP metatdata.
-
On the other hand--and unlike #3 above--
SetCurrentValue
does check for equality against the DP metadata'sDefaultValue
, in order to prune values it thus deems redundant as "unnecessary" as well. This eager cleanup may be designed to alleviate DP storage bloat, but it also complicates DP state transparency with a special-case "unmasking" behavior which can lead to obscure bugs if not thoroughly understood. For example, #6 clears the DP back to a pristine state indistinguishable fromClearValue()
... -
...but only if the previously stored
BaseValueSource
wasCurrent
and notLocal
; Compare #5/#6 to pair #7, where internal state flags differ considerably, despite identical reported property values.
regarding ClearValue()
:
It is obvious that the PropertyChangedCallback
is not invoked for any SetValue()
operation that doesn't ultimately result in a change from the previous value of the property. It's fundamental because SetValue
carries an implicit assumption that ongoing changes relate to the value of an active property at work. What's less intuitive is that the same logic applies to ClearValue()
as well.
For example, in #4, ClearValue
causes local value 42
to be deleted from internal DP storage, plus other internal state changes, all as expected. The problem is that whether (or not) OnPropertyChanged
is called during the current ClearValue
call depends on whether (or not) the previous value happened to equal the metadata default value.
Since the semantics of a "clear" operation seem to imply the summary discard of previous state--which is often therefore assumed to be contextually arbitrary--one might not expect this inconsistency where the behavior of ClearValue()
depends on some/any previous state. Especially for a significant behavior which also implicates (and co-mingles) the new state, such as whether to fire "change" notification or not.
Further to the accepted answer:
I found that this post explains SetCurrentValue() quite well. Note how the Dependency Property Value Precedence system will take a local value over a bound value. Which explains the commenters unexpected behaviour.