ScottG
ScottG

Reputation: 11121

Using WPF Validation rules and the disabling of a 'Save' button

I have a page where a few textboxes cannot be empty before clicking a Save button.

<TextBox...

                <TextBox.Text>
                    <Binding Path ="LastName" UpdateSourceTrigger="PropertyChanged">

                        <Binding.ValidationRules>
                            <local:StringRequiredValidationRule />
                        </Binding.ValidationRules>                              
                    </Binding>
                </TextBox.Text>

My rule works, I have a red border around my textbox until I enter a value. I now want to add this validation rule to my other text boxes.

How do I disable the Save button until the page has no validation errors? I'm not sure what to check.

Upvotes: 30

Views: 56819

Answers (10)

David Shader
David Shader

Reputation: 145

This website has the code you're looking for: https://www.wpfsharp.com/2012/02/03/how-to-disable-a-button-on-textbox-validationerrors-in-wpf/

For posterity the button code should look like this if you are using a ValidationRule override on the input fields:

<Button Content="<NameThisButton>" Click="<MethodToCallOnClick>" >
                <Button.Style>
                    <Style TargetType="{x:Type Button}">
                        <Setter Property="IsEnabled" Value="false" />
                        <Style.Triggers>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>                                    
                                    <Condition Binding="{Binding ElementName=<TextBoxName>, Path=(Validation.HasError)}" Value="false" />
                                    <Condition Binding="{Binding ElementName=<TextBoxName>, Path=(Validation.HasError)}" Value="false" />
                                </MultiDataTrigger.Conditions>
                                <Setter Property="IsEnabled" Value="true" />
                            </MultiDataTrigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>

Upvotes: 0

Gregor A. Lamche
Gregor A. Lamche

Reputation: 472

Because it's still missing, here is an adaption of Developer's answer in case the link ever goes away:

XAML:

<TextBox.Text Validation.Error="handleValidationError">
    <Binding Path ="LastName" 
             UpdateSourceTrigger="PropertyChanged"
             NotifyOnValidationError="True">
        <Binding.ValidationRules>
            <local:StringRequiredValidationRule />
        </Binding.ValidationRules>                              
    </Binding>
</TextBox.Text>
<Button IsEnabled="{Binding HasNoValidationErrors}"/>

CodeBehind/C#:

private int _numberOfValidationErrors;
public bool HasNoValidationErrors => _numberOfValidationErrors = 0;

private void handleValidationError(object sender, ValidationErrorEventArgs e)
{
    if (e.Action == ValidationErrorEventAction.Added)
        _numberOfValidationErrors++;
    else
        _numberOfValidationErrors--;
}

Upvotes: 2

Kabua
Kabua

Reputation: 1019

I've tried several of the solutions stated above; however, none of them worked for me.

My Simple Problem

I have a simple input window that request a URI from the user, if the TextBox value isn't a valid Uri then the Okay button should be disabled.

My Simple Solution

Here is what worked for me:

CommandBindings.Add(new CommandBinding(AppCommands.Okay,
            (sender, args) => DialogResult = true,
            (sender, args) => args.CanExecute = !(bool) _uriTextBoxControl.GetValue(Validation.HasErrorProperty)));

Upvotes: 1

Alexander  Sirotkin
Alexander Sirotkin

Reputation: 11

just inhert your ViewModel from System.ComponentModel.IDataErrorInfo for Validate and from INotifyPropertyChanged to notify button

make property:

    public bool IsValid
    {
        get
        {
            if (this.FloorPlanName.IsEmpty())
                return false;
            return true;
        }
    }

in xaml, connect it to button

<Button Margin="4,0,0,0" Style="{StaticResource McVMStdButton_Ok}" Click="btnDialogOk_Click" IsEnabled="{Binding IsValid}"/>

in the IDataErrorInfo overrides, notify btutton

public string this[string columnName]{
        get
        {
            switch (columnName)
            {
                case "FloorPlanName":
                    if (this.FloorPlanName.IsEmpty())
                    {
                        OnPropertyChanged("IsValid");
                        return "Floor plan name cant be empty";
                    }
                    break;
            }
        }
}

Upvotes: 1

Bilal
Bilal

Reputation: 91

this is it you need to check the HasError control property from the code behaind

and do this code in the save button click

 BindingExpression bexp = this.TextBox1.GetBindingExpression(TextBox.TextProperty);
bexp.UpdateSource(); // this to refresh the binding and see if any error exist 
bool hasError = bexp.HasError;  // this is boolean property indique if there is error 

MessageBox.Show(hasError.ToString());

Upvotes: 2

andrey.tsykunov
andrey.tsykunov

Reputation: 2916

Here is a helper method which tracks validation errors on the dependency objects (and all its children) and calls delegate to notify about the change. It also tracks removal of the children with validation errors.

 public static void AddErrorHandler(DependencyObject element, Action<bool> setHasValidationErrors)
        {
            var errors = new List<Tuple<object, ValidationError>>();

            RoutedEventHandler sourceUnloaded = null;

            sourceUnloaded = (sender, args) =>
                {
                    if (sender is FrameworkElement)
                        ((FrameworkElement) sender).Unloaded -= sourceUnloaded;
                    else
                        ((FrameworkContentElement) sender).Unloaded -= sourceUnloaded;

                    foreach (var error in errors.Where(err => err.Item1 == sender).ToArray())
                        errors.Remove(error);

                    setHasValidationErrors(errors.Any());
                };

            EventHandler<ValidationErrorEventArgs> errorHandler = (_, args) =>
                {
                    if (args.Action == ValidationErrorEventAction.Added)
                    {
                        errors.Add(new Tuple<object, ValidationError>(args.OriginalSource, args.Error));

                        if (args.OriginalSource is FrameworkElement)
                            ((FrameworkElement)args.OriginalSource).Unloaded += sourceUnloaded;
                        else if (args.OriginalSource is FrameworkContentElement)
                            ((FrameworkContentElement)args.OriginalSource).Unloaded += sourceUnloaded;
                    }
                    else
                    {
                        var error = errors
                            .FirstOrDefault(err => err.Item1 == args.OriginalSource && err.Item2 == args.Error);

                        if (error != null) 
                            errors.Remove(error);
                    }

                    setHasValidationErrors(errors.Any());
                };


            System.Windows.Controls.Validation.AddErrorHandler(element, errorHandler);
        } 

Upvotes: 2

Nihal
Nihal

Reputation: 31

int count = 0;

private void LayoutRoot_BindingValidationError(object sender, ValidationErrorEventArgs e)
{
    if (e.Action == ValidationErrorEventAction.Added)
    {
        button1.IsEnabled = false;
        count++;
    }
    if (e.Action == ValidationErrorEventAction.Removed)
    {                
        count--;
        if (count == 0) button1.IsEnabled = true;
    }
}

Upvotes: 3

Christoph
Christoph

Reputation: 4401

On the codebehind for the view you could wireup the Validation.ErrorEvent like so;

this.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(OnErrorEvent)); 

And then

private int errorCount;
private void OnErrorEvent(object sender, RoutedEventArgs e)
{
    var validationEventArgs = e as ValidationErrorEventArgs;
    if (validationEventArgs  == null)
        throw new Exception("Unexpected event args");
    switch(validationEventArgs.Action)
    {
        case ValidationErrorEventAction.Added:
            {
                errorCount++; break;
            }
        case ValidationErrorEventAction.Removed:
            {
                errorCount--; break;
            }
        default:
            {
                throw new Exception("Unknown action");
            }
    }
    Save.IsEnabled = errorCount == 0;
}

This makes the assumption that you will get notified of the removal (which won't happen if you remove the offending element while it is invalid).

Upvotes: 18

Todd White
Todd White

Reputation: 7880

You want to use Validation.HasError attached property.

Along the same lines Josh Smith has an interesting read on Binding to (Validation.Errors)[0] without Creating Debug Spew.

Upvotes: 7

Related Questions