Force validation on bound controls in WPF

WpfValidationBusiness Objects

Wpf Problem Overview


I have a WPF dialog with a couple of textboxes on it. Textboxes are bound to my business object and have WPF validation rules attached.

The problem is that user can perfectly click 'OK' button and close the dialog, without actually entering the data into textboxes. Validation rules never fire, since user didn't even attempt entering the information into textboxes.

Is it possible to force validation checks and determine if some validation rules are broken?

I would be able to do it when user tries to close the dialog and prohibit him from doing it if any validation rules are broken.

Thank you.

Wpf Solutions


Solution 1 - Wpf

In 3.5SP1 / 3.0SP2, they also added a new property to the ValidationRule base, namely, ValidatesOnTargetUpdated="True". This will call the validation as soon as the source object is bound, rather than only when the target control is updated. That may not be exactly what you want, but it's not bad to see initially all the stuff you need to fix.

Works something like this:

<TextBox.Text>
    <Binding Path="Amount" StringFormat="C">
        <Binding.ValidationRules>
            <validation:RequiredValidationRule 
                ErrorMessage="The pledge amount is required." 
                ValidatesOnTargetUpdated="True"  />
            <validation:IsNumericValidationRule 
                ErrorMessage="The pledge amount must be numeric." 
                ValidationStep="ConvertedProposedValue" 
                ValidatesOnTargetUpdated="True"  />
        </Binding.ValidationRules>
    </Binding>
</TextBox.Text>

Solution 2 - Wpf

We have this issue in our application as well. The validation only fires when bindings update, so you have to update them by hand. We do this in the Window's Loaded event:

public void Window_Loaded(object sender, RoutedEventArgs e)
{
    // we manually fire the bindings so we get the validation initially
    txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    txtCode.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}

This will make the error template (red outline) appear, and set the Validation.HasError property, which we have triggering the OK button to disable:

<Button x:Name="btnOK" Content="OK" IsDefault="True" Click="btnOK_Click">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="IsEnabled" Value="false" />
            <Style.Triggers>
                <!-- Require the controls to be valid in order to press OK -->
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ElementName=txtName, Path=(Validation.HasError)}" Value="false" />
                        <Condition Binding="{Binding ElementName=txtCode, Path=(Validation.HasError)}" Value="false" />
                    </MultiDataTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="true" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

Solution 3 - Wpf

Here is an alternative way that doesn't require calling "UpdateSource()" or "UpdateTarget()":

var binding = thingToValidate.GetBinding(propertyToValidate);
foreach (var rule in binding.ValidationRules)
{
    var value = thingToValidate.GetValue(propertyToValidate);
    var result = rule.Validate(value, CultureInfo.CurrentCulture);
    if (result.IsValid) 
         continue;
    var expr = BindingOperations.GetBindingExpression(thingToValidate, propertyToValidate);
    if (expr == null)  
        continue;
    var validationError = new ValidationError(rule, expr);
    validationError.ErrorContent = result.ErrorContent;
    Validation.MarkInvalid(expr, validationError);
}

Solution 4 - Wpf

Use the method above proposed by Robert Macnee. For example:

//force initial validation
foreach (FrameworkElement item in grid1.Children)
{
    if (item is TextBox)
    {
        TextBox txt = item as TextBox;
        txt.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    }
}        

But, BE SURE that the bound controls are Visibles before this code run!

Solution 5 - Wpf

Just in case anyone happens to find this old question and is looking for an answer that addresses Monstieur's comment about UI guidelines, I did the following:

Xaml

<TextBox.Text>
    <Binding Path="TextValue" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
            <local:RequiredFieldValidationRule>
                    <local:RequiredFieldValidationRule.IsRequiredField>
                    <local:BoolValue Value="{Binding Data.Required, Source={StaticResource proxy}}" />
                </local:RequiredFieldValidationRule.IsRequiredField>
                <local:RequiredFieldValidationRule.ValidationFailed>
                    <local:BoolValue Value="{Binding Data.HasValidationError, Mode=TwoWay, Source={StaticResource proxy}}" />
                </local:RequiredFieldValidationRule.ValidationFailed>
            </local:RequiredFieldValidationRule>
        </Binding.ValidationRules>
    </Binding>
</TextBox.Text>

RequiredFieldValidationRule:

public class RequiredFieldValidationRule : ValidationRule
{
    private BoolValue _isRequiredField;
    public BoolValue IsRequiredField
    {
        get { return _isRequiredField; }
        set { _isRequiredField = value; }
    }
    private BoolValue _validationFailed;
    public BoolValue ValidationFailed
    {
        get { return _validationFailed; }
        set { _validationFailed = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        ValidationFailed.Value = IsRequiredField.Value && (value == null || value.ToString().Length == 0);
        return new ValidationResult(!ValidationFailed.Value, ValidationFailed.Value ? "This field is mandatory" : null);
    }
}

In the class that the Xaml binds to

private bool _hasValidationError;
public bool HasValidationError
{
    get { return _hasValidationError; }
    set { _hasValidationError = value; NotifyPropertyChanged(nameof(HasValidationError)); }
}


public void InitialisationMethod() // Or could be done in a constructor
{
    _hasValidationError = Required; // Required is a property indicating whether the field is mandatory or not
}

I then hide my Save button using a bound property, if any of my objects has HasValidationError = true.

Hope this is helpful to someone.

Solution 6 - Wpf

using the INotifyPropertychanged on your data object

public class MyObject : INotifyPropertyChanged
{
    string _MyPropertyToBind = string.Empty;
    public string MyPropertyToBind
    {
        get
        {
            return _MyPropertyToBind;
        }
        set
        {
            _MyPropertyToBind = value;
            NotifyPropertyChanged("MyPropertyToBind");
        }
    }

    public void NotifyPropertyChanged(string property)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

}

you can add the following code to your control

<TextBox Text="{Binding MyPropertyToBind, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >

The textbox susbscribe to the propertychanged event of the datacontext object ( MyObjet in our example) and assumes it is fired when the source data has been updated

it automatically forces the refresh to the control

No need to call yourself the UpdateTarget method

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionValentin VView Question on Stackoverflow
Solution 1 - WpfKen SmithView Answer on Stackoverflow
Solution 2 - WpfRobert MacneeView Answer on Stackoverflow
Solution 3 - WpfryanView Answer on Stackoverflow
Solution 4 - WpfAlex PollanView Answer on Stackoverflow
Solution 5 - WpflukepView Answer on Stackoverflow
Solution 6 - WpfJenzoView Answer on Stackoverflow