Compartilhar via


The Control Local Values Bug Solution and new WPF 4.0 related APIs

Intro
Previously I did a post on the “Control Local Values bug” and how a subtle bug can be introduced when setting dependency properties of controls to local values. In WPF 4.0 (dev10), a mechanism was added to the property engine to solve this problem. Here are the new APIs in dev10:

public class DependencyObject

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

   public void SetCurrentValue(DependencyProperty dp, object value);
}

 

public struct ValueSource

   public bool IsCurrent { get; }

}

 

Basically, when using DependencyObject.SetCurrentValue instead of DependencyObject.SetValue you set its effective value but its ValueSource remains the same. This is very similar to coercion.

Sample Test App

To make it more clear, I wrote a sample test app that shows the BaseValueSource and ValueSource properties when changing a DependencyProperty (DP) via SetCurrentValue. I created a custom control with a DP, TitleProperty, where I track its values. In addition, I setup several scenarios where the TitleProperty is initially set by a particular BaseValueSource. I’ll go through one scenario here.

I setup the custom control where the TitleProperty is set from a Style with a binding.

<Style x:Key="StyleBindingCustomControlStyle" TargetType="{x:Type local:CustomControl}">

   <Setter Property="Title"

           Value="{Binding RelativeSource={RelativeSource Self},

                          Path=WorkingTag,

                          StringFormat='Set via Style w/ Binding: {0}'}" />

   <Setter Property="Template">

       <Setter.Value>

         <ControlTemplate TargetType="{x:Type local:CustomControl}">

            <Button Content="{TemplateBinding Title}"

                   Background="{TemplateBinding Background}" />

         </ControlTemplate>

       </Setter.Value>

   </Setter>

</Style>

 

<GroupBox Header="Style Binding">

   <local:CustomControl x:Name="CustomControl_Style_Binding"

                   Style="{StaticResource StyleBindingCustomControlStyle}"/>

</GroupBox>

 

Initially it will look like this:

 CLV_1

Notice the BaseValueSource is ‘Style’, IsExpression is ‘True’, and IsCurrent is ‘False’ as expected. After pressing the “Set Current Value” button the values will update accordingly:

 CLV_2

Now the local value has updated to a new string that I set and IsCurrent has updated to ‘True’. Also notice that BaseValueSource and IsExpression have not changed which is also expected. If you press “Clear Local Value” the values will update back to their initial values. Now to bring back the original issue with the control local values bug, if you were to set or clear the local value any previous BaseValueSource would be overwritten and any expression lost. I have created other scenarios in the sample with different BaseValueSource behavior so you can understand how setting the current value behaves in those scenarios. Please be sure to check it out below.

Usage Recommendation

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.

Here is the sample app which works with VS 2010 beta 1.

ControlLocalValuesSample.zip

Comments

  • Anonymous
    May 21, 2009
    PingBack from http://blogs.msdn.com/vinsibal/archive/2009/03/24/the-control-local-values-bug.aspx

  • Anonymous
    June 05, 2009
    Please don't confuse lack of comments with lack of interest in what you are posting.   That is a pretty easy thing to do. This is one of my favorite msdn blogs, but...., I don't really have anything to add to what you said in this post or others aside from please keep it up. Great work keeping all of us developers informed on what is going on at Microsoft. Also, I am very happy to see Microsoft fixing issues like this.

  • Anonymous
    June 06, 2009
    Thanks a lot Paul!

  • Anonymous
    June 12, 2009
    I just discovered this blog. I've bookmarked it for future refernce and I agree with what Paul said. (I came here because I'm trying to discover how to enable deferred scrolling in the datagrid)

  • Anonymous
    June 12, 2009
    Rune, See this post, http://blogs.msdn.com/vinsibal/archive/2008/05/22/wpf-3-5-sp1-feature-non-live-scrolling.aspx.

  • Anonymous
    June 12, 2009
    This only solves if we use .NET 4.0 beta (or future versions), but how do we solve it in .NET 3.5 FW, this seems to be rather trivial that I do not wish to upgrade to VS2010 yet, then how do i do it? Do we simply reset the BindingExpression? var be = tb.GetBindingExpression(TextBox.TextProperty); // make my changes in internally // reset the BindingExpression tb.SetBinding(TextBox.TextProperty, be) I just thought of this idea, What do you think about it?

  • Anonymous
    June 12, 2009
    Fahad, The solution you propose make work for a simple problem but it does not scale for many of the common scenarios.  Take the DatePicker example that I talk about in the previous blog post, http://blogs.msdn.com/vinsibal/archive/2009/03/24/the-control-local-values-bug.aspx.  In OnIsDropDownChanged a member, _popUp.IsOpen is set to a local value.  How would you undo that?  From the outside you need to get access to _popUp which may or may not be possible and you need to know the kind of expression and ValueSource on IsOpen prior to it being set to a local value.  If you knew exactly where this would happen in code and had access to everything then it may work.  Since you don't have access to a lot of the Control specific implementation, it makes it impossible to work around this issue.  So unfortunately, there is not work around this issue in pre 4.0 bits.

  • Anonymous
    June 13, 2009
    Thanks for your views Vincent, I agree with your point!!!