views:

141

answers:

2

I have a TextBox whose Value is binded to a ViewModel property:

        <TextBox Name="txtRunAfter" Grid.Column="4" Text="{Binding Mode=TwoWay, Path=RunAfter}" Style="{StaticResource TestStepTextBox}"/>

The set and get were working fine until I tried to add some validation when the Value is set:

    private int _runAfter = 0;
    public string RunAfter
    {
        get
        {
            return _runAfter.ToString();
        }

        set
        {
            int val = int.Parse(value);

            if (_runAfter != val)
            {
                if (val < _order)
                    _runAfter = val;
                else
                {
                    _runAfter = 0;
                    OnPropertyChanged("RunAfter");
                }
            }
        }
    }

Although the OnPropertyChanged is reached (I have dubugged that), the View is not changed. How can I make this work?

Thanks, José Tavares

+2  A: 

A few things I noticed here.

Unless you have a compelling reason to expose the RunAfter property as a string, there's no reason why it can't be an int. That would save you the cast in the setter (as well as a lurking possible InvalidCastException if the user enters something non-integer in the field).

Secondly, the OnPropertyChanged() call should occur outside of the inner if statement, like the following:

if(_runAfter != val)
{
    if(val < _order)
        _runAfter = val;
    else
        _runAfter = 0;
    OnPropertyChanged("RunAfter");
}

Since the _runAfter local is being updated in both paths of the conditional, the OnPropertyChanged() has to be called regardless of the branch taken. I hope that helps point you in the right direction!

Eric
+1 for noting that you need to move the OnPropertyChanged outside of the else.
Robaticus
Well, the set is called by the View (via binding) so I thought it would only be necessary to call OnPropertyChanged if the value would be changed from the one setted in the View.
jpsstavares
Anyway, that didn't solve my problem...
jpsstavares
Regardless of how the setter is called, any change must raise the PropertyChanged event to let listeners know that they need to refresh their value.Have you looked at using ValidationRules for doing validation rather than embedding it in your setter code?http://msdn.microsoft.com/en-us/library/system.windows.controls.validationrule.aspx
Eric
+2  A: 

The problem is that you are updating the source for the Binding while the Binding is updating your property. WPF won't actually check your property value when it raises the PropertyChanged event in response to a Binding update. You can solve this by using the Dispatcher to delay the propagation of the event in that branch:

set
{
    int val = int.Parse(value);

    if (_runAfter != val)
    {
        if (val < _order)
        {
            _runAfter = val;
            OnPropertyChanged("RunAfter");
        }
        else
        {
            _runAfter = 0;
            Dispatcher.CurrentDispatcher.BeginInvoke(
                new Action<String>(OnPropertyChanged), 
                DispatcherPriority.DataBind, "RunAfter");
        }
    }
}

Update:

The other thing I noticed is that the Binding on your TextBox is using the default UpdateSourceTrigger, which happens when the TextBox loses focus. You won't see the text change back to 0 until after the TextBox loses focus with this mode. If you change it to PropertyChanged, you will see this happen immediately. Otherwise, your property won't get set until your TextBox loses focus:

<TextBox Name="txtRunAfter" Grid.Column="4" Text="{Binding RunAfter, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TestStepTextBox}"/>
Abe Heidebrecht
I guess your assessment of the problem is correct, but the dispatcher call doesn't work. My UserControl is used inside a WinForm application using an ElementHost. May this be affecting the Dispatcher call?
jpsstavares
Okay, I tested this out and it worked fine (when you tab away from the TextBox as the default mode of the binding won't update the property until the TextBox loses focus). I updated the answer to explain about the UpdateSourceTrigger on the bindings, in case that is the behavior that you are seeing. I don't deal a lot with WPF hosted in WinForms, but I don't see why that would affect the Binding or Dispatcher in your context.
Abe Heidebrecht