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

10

30

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.

Amp answered 23/10, 2008 at 19:13 Comment(0)
A
19

Here is the complete sample what you need.

http://codeblitz.wordpress.com/2009/05/08/wpf-validation-made-easy-with-idataerrorinfo/

https://skydrive.live.com/?cid=2c6600f1c1d5e3be&id=2C6600F1C1D5E3BE%21203

enter image description here

Abib answered 23/1, 2012 at 17:24 Comment(4)
Unfortunately the referenced example does not describe the enabling/disabling of the button in detail.Filial
Would be great to have the key parts here in the answer. If the referred sites go away, this answer gets useless.Filial
The Question is about how to disable button when using ValidationRules. But the referenced examples is about using IDataErrorInfo - another validation way.Unmanned
Look this video youtube.com/watch?v=OOHDie8BdGI, it shows a example how to use IDataErrorInfo to disable/enable a button.Toowoomba
C
18

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).

Contrecoup answered 30/4, 2009 at 17:44 Comment(3)
How to do it if there is no any view and only a Window?Abib
Who uses code behind any more. How do you do this in a view model?Shahjahanpur
To be notified of error I had to add NotifyOnValidationError="True" on elements which needed validation : <Binding Path ="LastName" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True"> (default value is false)Amberjack
B
7

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.

Binetta answered 24/10, 2008 at 5:22 Comment(1)
You didn't answer "how do I disable the Save button". Please provide the code. The sample you referred "Binding to (Validation.Errors)[0] without Creating Debug Spew." Doesn't have any code to disable Save button.Abib
R
3
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;
    }
}
Rhineland answered 22/9, 2011 at 9:21 Comment(0)
A
2

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);
        } 
Ataxia answered 16/11, 2011 at 19:50 Comment(0)
U
2

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());
Uneventful answered 25/12, 2012 at 11:36 Comment(0)
G
2

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--;
}
Goody answered 23/7, 2019 at 8:44 Comment(0)
J
1

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;
            }
        }
}
Jennettejenni answered 21/10, 2014 at 11:14 Comment(1)
Can you please add all the closing braces in your code?Quintin
F
1

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)));
Fassold answered 22/1, 2016 at 18:42 Comment(0)
L
0

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>
Leek answered 16/1, 2020 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.