Home » Visual StudioRSS

TextBox shows "old" value after being Coerced when bound to a Dependency Property

Hello,

I am writing a WPF Custom Control.  I want to be able to limit the input for the DependancyProperty "Text".  I have the OnCoerce event handlers connected and this property is bound to the "Text" property on a TextBox in my content template as shown below:

Code Snippet

<TextBox Name="PART_txtEdit"

Background="{TemplateBinding Background}"

BorderBrush="{TemplateBinding BorderBrush}"

BorderThickness="{TemplateBinding BorderThickness}"

Text="{Binding RelativeSource={RelativeSource FindAncestor,

AncestorType={x:Type local:MyCustomControl}},

Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

The binding works as expected.  I am able to set the Text property of the TextBox from my dependency property.  I get notification of property changed when I press a key in the textbox.  My static coerce calls a virtual one (shown below):

Code Snippet

publicstaticobject TextCoerce(DependencyObject d, object baseValue) {

MyCustomControl c = d asMyCustomControl;

if (edit != null) {

return c.TextCoerce(baseValue asstring);

} else

return baseValue;

}

publicvirtualobject TextCoerce(string value) {

return (Logic.IsValid(value, true)) ? value : DependencyProperty.UnsetValue;

}

As you can see I use "DependencyProperty.UnsetValue" to indicate that I want to reject the change.  When the databinding is complete my text has the correct value but my textbox has the original input.  For example say this text box only allowed upper-case chars.  If I enter "ABC" everything is fine.  If I enter "ABc" the coerce forces the value back to "AB" but the textbox still shows "ABc".  I can test this by doing this in the main window:

Code Snippet

<local:MyCustomControl x:Name="myControl" Text="AB" Height="25" Width="150"/>

<TextBox Name="TextBox1" Height="25" Width="85" Text="{Binding Path=Text, ElementName=myControl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

The correct behavior exists with TextBox1 where if you enter "ABc" in the "myControl" box only "AB" shows in "TextBox1".  Respectivly, if you enter "ABc" in TextBox1 you will see "ABc" in that textbox but only "AB" in myControl. 

I don't really want to inherit directly from TextBox as the textbox is only a portion of a bigger control.

Any help would be appreciated!

Thanks!

Nathan Zaugg

 

8 Answers Found

 

Answer 1

Even with a binding, the text  element is going to present what is typed into it.  You would have to force a target update on the binding  when the coersion fails if you want the value to revert to the current DP value.

    txtEdit.GetBindingExpression(TextBox.TextProperty).UpdateTarget();

Note that coersion only changes the effective value of the property.  The base value would still be "ABc".

Nathan Zaugg wrote:

I don't really want to inherit  directly from textbox  as the textbox is only a portion  of a bigger  control.

I would definitely derive from TextBox.  If you are composing several inter-related elements, a UserControl might be better suited for your task.

 

Answer 2

I'd like to be able to keep the specific visual implementation of the control  "lookless".  Is there any way to cause the target to be updated in XAML, using the Command Pattern, or by adding a DataTrigger/EventTrigger?

 

Answer 3

Nathan Zaugg wrote:

Is there any way to cause the target to be updated in XAML, using the Command Pattern, or by adding a DataTrigger/EventTrigger?

Not without baking that behavior  into a custom  TextBox derivative. 

The issue at hand is more about behavior (code behind) than it is about look and feel (templating/styling).  That is why I would tend toward creating a custom control.  Then I would try to prevent the invalid text  from ever being entered (by filtering input  via keystroke filters and overriding OnPreviewTextInput).  That way, you never have to concern yourself with bindings on your Text property.  You can truly be lookless.

If you want to use the binding  approach and force a target update for the binding, then I would choose the UserControl option.  Again, a binding target update would fall under behavior, so doing this in the code  behind of a user control  makes sense.

 

Answer 4

There is still an issue here, though, even if you take the issue of defining a custom  UserControl out of the picture.

I have a DependencyProperty A that is bound  to DependencyProperty B.  B has a coercion function, as described above.  In my case this has to do with a custom object  that implements IScrollInfo, and the coercion ensures that B isn't outside the bounds of the scrollable area.

Anything that directly  uses B gets the coerced value. However, A gets the base value rather than the coerced value.  This is a serious problem for me.  Of course, B shouldn't know anything about who has bound to it, so I don't have any acceptable way to force A to update itself again once it gets the new bound (base) value from B. 

Is this simply a bug in the DependencyProperty code?  Presumably the intent should be that if A is bound to B then A will equal B after the binding  has been resolved, even if the value of B has to be coerced.

Thanks,

David Cater

 

Answer 5

David,

David Cater wrote:

"Anything that directly  uses B gets the coerced value. However, A gets the base value rather than the coerced value.  This is a serious problem for me.  Of course, B shouldn't know anything about who has bound  to it, so I don't have any acceptable way to force A to update itself again once it gets the new bound (base) value from B."

I agree 100%, I think the chain of events for Dependency property  should never leave the the base value in the control  -- I am betting, however, that the reason it doesn't do that is because you could have infinite recursion on your hands.  If your coerce function were to be non-deterministic (i.e. it mutates the value even if it had done so before) then it would leave things in a real mess. 

I think this is something to be looked at for the next version of WPF.  The only two solutions break the look-less aspect of these controls. 

One thing I intend to try is setting Metadata Flags (http://msdn2.microsoft.com/en-us/library/ms753358.aspx scroll to the section Setting Appropriate Metadata Flags) and try setting the AffectsRender flag.  This or one of these other flags might work.

If you come across another solution, let me know!

Thanks!

Nathan Zaugg

 

Answer 6

For my case I simply moved the "coercion" code  into the change  handler for property  B.  It basically looks like this (pseudocode):

private static  void BChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) {

double value = DoCoercion((double) args.NewValue);

if (value != (double)args.NewValue)

B = value;

else

DoSideEffectsOfValueBeingSet();

}

So the property changed  handler will get called twice, meaning Property A (the bound  property) will also get updated twice, once with the out-of-bounds value and once with the coerced value.  That's not a problem for me, but it could be a problem in other situations.  Well, it is a performance issue for me, because when Property A changes some time-consuming things run, and I shouldn't be running that twice.  But Property A does end up with the right value now by doing this and removing the coercion handler from the metadata altogether.

D.

 

Answer 7

Dr. wpf  + Dave et al,
I am faced with a similar problem wherein my WPF app has several toggle buttons bound  to a bool in live CLR object  model.  The source object in the CLR object model implements INotifyPropertyChanged.  Problem is that when a toggle button is clicked it's visual state gets out of sync and does not match the actual bool value in the object model nor the state of all of the other toggle buttons that are bound to that same property.  Do you see any drawbacks with the following proposed target-side general work-around to this problem? 

It appears that the click event  handler for the ToggleButton occurs after all of the dependency  property changes settle down.   I don't know if other standard controls come with a similar handler such that this soultion could be applied.

The idea for the Depedency property  enumumerator comes from another thread: Enumerate Bindings.
public static class DependencyPropertyHelper  
{  
    public static void UpdateTargets(DependencyObject element)  
    {  
        if (element == null)  
        {  
            throw new ArgumentNullException("element");  
        }  
        LocalValueEnumerator lve = element.GetLocalValueEnumerator();  
        while (lve.MoveNext())  
        {  
            LocalValueEntry entry = lve.Current;  
            if (BindingOperations.IsDataBound(element, entry.Property))  
            {  
                BindingExpression be = (entry.Value as BindingExpression);  
                if(null!=be)  
                {  
                    be.UpdateTarget();  
                }  
                MultiBindingExpression mbe = (entry.Value as MultiBindingExpression);  
                if(null!=mbe)  
                {  
                    mbe.UpdateTarget();  
                }  
            }  
        }  
    }  
}  
// Target Window  
public partial class Window1 : Window  
{  
//...  
   private void ToggleButton_Click(object sender, RoutedEventArgs e)  
   {  
      DependencyPropertyHelper.UpdateTargets(sender as DependencyObject);  
   }  
//...  
}  

Regards,
-Ron
 

Answer 8

Even with a binding, the text  element is going to present what is typed into it.  You would have to force a target update on the binding  when the coersion fails if you want the value to revert to the current DP value.

 

    txtEdit.GetBindingExpression(TextBox.TextProperty).UpdateTarget();

 

Note that coersion only changes the effective value of the property.  The base value would still be "ABc".


Unfortunately, this is broken in WPF4 (see this post). The solution is to set  SourceTrigger to Explicit and use UpdateSource() instead of UpdateTarget().
 
 
 

<< Previous      Next >>


Microsoft   |   Windows   |   Visual Studio   |   Follow us on Twitter