59

The reason why I am asking this is because I was recommended by @Greg D (from this question) to use SetCurrentValue() instead, but a look at the docs and didn't see whats the difference. Or whats does "without changing its value source" mean?

SetValue()

Sets the local value of a dependency property, specified by its dependency property identifier.

SetCurrentValue()

Sets the value of a dependency property without changing its value source.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Jiew Meng
  • 84,767
  • 185
  • 495
  • 805

3 Answers3

68

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.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 2
    In Vincent Sibal's blog (http://blogs.msdn.com/b/vinsibal/archive/2009/05/21/the-control-local-values-bug-solution-and-new-wpf-4-0-related-apis.aspx) you can read the following at the end. "For a control developer, the general recommendation is to always use DependencyObject.SetCurrentValue over DependencyObject.SetValue in Control code. You’ll notice that our stock controls in the 4.0 framework have all been updated to use this API instead of setting the properties with local values." You're both probably right but can you explain the difference here and the recommendation from the blog? – Fredrik Hedblad Nov 20 '10 at 11:19
  • 3
    @Meleak: to be honest, I think that post is a little unclear. I think he meant to say that `SetCurrentValue` should be used anywhere in your control where you want to *internally* modify a dependency property's value. Indeed, if you crack open his example project, you'll see he's still using `GetValue` and `SetValue` in the CLR property wrapper. Indeed, if you crack open reflector against .NET 4.0 you'll see that's still the case too. – Kent Boogaart Nov 20 '10 at 13:59
  • 3
    Hmm, so far, I've been using `SetValue` as its generated by Visual Studio, it works fine. I don't really get what you mean by destroy the binding. After the 1st value change, my Binding don't get destroyed? cos I can still make changes and the Binding still works? – Jiew Meng Nov 20 '10 at 14:03
  • From the link @Meleak provided, I seem to be thinking, maybe `SetCurrentValue()` just sets the **Current** value and I can reset the value to the original? But whats the use of that? – Jiew Meng Nov 20 '10 at 14:05
  • @Kent: Thanks! Now I understand the difference, good answer! +1 – Fredrik Hedblad Nov 20 '10 at 14:32
  • 4
    The absence of `SetCurrentValue()` prior to .Net 4 is one thing that makes implementing a proper, self-contained, fully-featured `NumericUpDownControl` unreasonably difficult. Clicking the classic Up/Down arrows on such a control will typically modify the value of the control programmatically, but with `SetValue()`, that blows away any bindings that the user may have implemented against that field. `SetCurrentValue()` lets you modify the value of the property programmatically from the Up and Down buttons without destroying the binding against the backing data. – Greg D Nov 20 '10 at 19:42
  • 1
    I must be the noobiest of all here... maybe can you can explain what happens when "application's use of property is disabled". How does `SetValue` cause "existing triggers, data bindings, and styles" to fail? Erm... I don't know if its too much, maybe a simple example demonstrating when I cannot just use `SetValue`? ... It seems my own question from [here](http://stackoverflow.com/questions/4231478/binding-setting-property-but-ui-not-updating-can-i-debug-within-referenced-proje) only succeeds when using `SetCurrentValue` but not `SetValue`... – Jiew Meng Nov 21 '10 at 10:28
  • 5
    I tried seeing if any binding is destroyed when I use `SetValue` in this [**Screenr Video**](http://screenr.com/3Pc). and everything worked fine? Maybe I didn't do the thing that destroys the binding? But how can I do that? – Jiew Meng Nov 21 '10 at 11:11
  • 6
    I believe bindings were mainly destroyed only in cases where the Binding Mode is not TwoWay. For TwoWay bindings everything seems to work find in regard to using SetValue from my observations. – jpierson Apr 03 '13 at 21:29
  • 1
    Why is it that "you will overwrite the binding" but "use GetValue/SetValue from your CLR wrapper property"? – Mikhail Orlov Jan 11 '14 at 09:50
4

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:

  1. Initial state of an absent DependencyProperty which has a DefaultValue of '42' has the BaseValueSource.Default flag asserted. ReadLocalValue() returns the global singleton instance DependencyProperty.UnsetValue.

  2. SetValue() internally stores a BaseValueSource.Local value as expected.

  3. Using SetValue to store a value which happens to equal to the DefaultValue does not restore the BaseValueSource.Default state (compare to #6, below).

  4. Instead, if you want to remove any/all stored value or binding and restore the DP to pristine, call ClearValue(). (see note below)

  5. With SetCurrentValue(), the property value is produced via coercion, and without asserting the BaseValueSource.Local mode. Notice that the previous BaseValueSource Default still prevails despite the property now reporting a value which is not, in fact, equal to its DefaultValue.

Important:
This means that checking that the BaseValueSource returned by GetValueSource() state is BaseValueSource.Default is not a reliable indicator of whether the prevailing property value equals the default value from the DP metatdata.

  1. On the other hand--and unlike #3 above--SetCurrentValue does check for equality against the DP metadata's DefaultValue, 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 from ClearValue()...

  2. ...but only if the previously stored BaseValueSource was Current and not Local; 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.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
3

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.

johnDisplayClass
  • 269
  • 3
  • 11