Property Binding Not Updating When ValidationRule Fails
Asked Answered
H

3

9

I've got a few TextBoxes for input fields and a "Save" Button in my view. Two of the TextBoxes are required fields for saving, and I've set up a custom ValidationRule in the xaml for some visual feedback (red borders and tooltips) like so:

<TextBox ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}">
    <TextBox.Text>
        <Binding Path="ScriptFileMap" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <v:MinimumStringLengthRule MinimumLength="1" ErrorMessage="Map is required for saving." />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

The "Save" Button is linked to a DelegateCommand which calls the SaveScript() function. The function doesn't allow the user to save if the properties of the two required fields are empty:

public void SaveScript()
{
    if (this.ScriptFileName.Length > 0 && this.ScriptFileMap.Length > 0)
    {
        // save function logic
    }
}

However, the function still allows the file to be saved. On closer inspection, I see that the values of those two fields (ScriptFileName and ScriptFileMap) are not being updated when the ValidationRule fails, and it goes by the last known value.

Is this the expected behavior for ValidationRule or do I have something missing or a glitch somewhere? If the former, is there a way to override that behavior? I can't prevent the saving in the ViewModel if the empty string is never passed into the bound property.

Highspeed answered 22/10, 2014 at 17:23 Comment(0)
H
1

Since I never got the ValidationRule workling properly, I took a different approach and just used a number of bindings. Here's my textbox, with bindings for the text, border, and tooltip:

<TextBox Text="{Binding Path=ScriptFileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderBrush="{Binding Path=ScriptFileNameBorder, UpdateSourceTrigger=PropertyChanged}" ToolTip="{Binding Path=ScriptFileNameToolTip, UpdateSourceTrigger=PropertyChanged}" />

Here's my binding for the text field, with logic to update the border and tooltip myself (sans validation):

public string ScriptFileName
        {
            get
            {
                return this.scriptFileName;
            }

            set
            {
                this.scriptFileName = value;
                RaisePropertyChanged(() => ScriptFileName);

                if (this.ScriptFileName.Length > 0)
                {
                    this.ScriptFileNameBorder = borderBrushNormal;
                    this.scriptFileNameToolTip.Content = "Enter the name of the file.";
                }
                else
                {
                    this.ScriptFileNameBorder = Brushes.Red;
                    this.scriptFileNameToolTip.Content = "File name is required for saving.";
                }
            }
        }

Doing it this way allows me to have the user feedback I want (red borders and a tooltip message) when the box is left empty and still use the code in my SaveScript function to prevent the Save button from working.

It's a bit more typing, since I then need to have separate properties for each additional field I want to make required, but everything else I've tried either had no effect or broke something else in the program (including ValidationRules and DataTriggers).

Highspeed answered 23/10, 2014 at 17:9 Comment(0)
P
9

Yes, that is the expected behavior. By default, validation rules run on the raw proposed value, i.e., the value before it gets converted and written back to the binding source.

Try changing the ValidationStep on your rule to UpdatedValue. That should force the rule to run after the new value is converted and written back.

Prismatic answered 22/10, 2014 at 17:31 Comment(6)
I think a good example too to show why it doesn't update the bound property is because, what happens when property int is bound to a textbox and someone typed asdf?Antiknock
@TyCobb: That does make sense, but in this case, I'm using strings and just want to make sure the strings aren't empty. In fact, I'm not even sure how one would bind an int to a TextBox. Isn't the value string by default?Highspeed
There's a default value converter for some known types like Int32, but you would never hit an UpdatedValue rule that validates an Int32 if you provide an invalid string like asdf; an error would occur during conversion. You can set ValidatesOnExceptions on your Binding to report those kinds of errors.Prismatic
On closer inspection, using UpdatedValue caused the ValidationRule to not fire when the Textbox was empty. CommittedValue appears to do the same thing.Highspeed
When the TextBox starts empty and remains empty? Or when you enter some text, then delete it? If the former, try setting ValidatesOnTargetUpdated="True" on the rule. That will make it evaluate during the source-to-target value transfer, forcing the error to show up in the UI without waiting for the user to edit the text.Prismatic
@MikeStrobel Perfect! Took care of my issue and IMO, this should be the answer. Though in my case, I had to use 'ConvertedProposedValue'.Schreib
J
2

You should implement CanExecute method and RaiseCanExecuteChanged event which will keep your button disabled until all the required properties pass the validation logic.

Jamiejamieson answered 23/10, 2014 at 17:27 Comment(2)
This looks promising. I'll try this out next time I need to validate a field. Thanks for the idea.Highspeed
This will not work in case the input was valid, and then turned to be invalid, the ViewModel's property will stay valid, since the update was blocked, thus enabling the button.Severn
H
1

Since I never got the ValidationRule workling properly, I took a different approach and just used a number of bindings. Here's my textbox, with bindings for the text, border, and tooltip:

<TextBox Text="{Binding Path=ScriptFileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderBrush="{Binding Path=ScriptFileNameBorder, UpdateSourceTrigger=PropertyChanged}" ToolTip="{Binding Path=ScriptFileNameToolTip, UpdateSourceTrigger=PropertyChanged}" />

Here's my binding for the text field, with logic to update the border and tooltip myself (sans validation):

public string ScriptFileName
        {
            get
            {
                return this.scriptFileName;
            }

            set
            {
                this.scriptFileName = value;
                RaisePropertyChanged(() => ScriptFileName);

                if (this.ScriptFileName.Length > 0)
                {
                    this.ScriptFileNameBorder = borderBrushNormal;
                    this.scriptFileNameToolTip.Content = "Enter the name of the file.";
                }
                else
                {
                    this.ScriptFileNameBorder = Brushes.Red;
                    this.scriptFileNameToolTip.Content = "File name is required for saving.";
                }
            }
        }

Doing it this way allows me to have the user feedback I want (red borders and a tooltip message) when the box is left empty and still use the code in my SaveScript function to prevent the Save button from working.

It's a bit more typing, since I then need to have separate properties for each additional field I want to make required, but everything else I've tried either had no effect or broke something else in the program (including ValidationRules and DataTriggers).

Highspeed answered 23/10, 2014 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.