4
votes

I have a WPF application with a UI that contains a checkbox and a textbox. The checkbox and textbox are bound to properties on my business object. I've been using validation rules to validate user input and for the most part, they're pretty straight forward (checking that the value is not null/empty, checking that a value is within a certain range, etc). FWIW, here's my existing XAML:

<StackPanel>
    <CheckBox x:Name="requirePinNumberCheckBox" IsChecked="{Binding Path=RequirePinNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Require PIN number</CheckBox>
    <TextBox x:Name="pinNumberTextBox" Style="{StaticResource textBoxInError}" PreviewTextInput="pinNumberTextBox_PreviewTextInput">
        <TextBox.Text>
            <Binding Path="PinNumber" Mode="TwoWay" UpdateSourceTrigger="LostFocus">
                <Binding.ValidationRules>
                    <local:PinNumberValidationRule ValidationStep="RawProposedValue"/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
</StackPanel>

My validation rule for the textbox is simply:

public class PinNumberValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        // make sure that the PIN number isn't blank
        if (string.IsNullOrEmpty(value.ToString()))
        {
            return new ValidationResult(false, "PIN number cannot be blank.");
        }
        else
        {
            return new ValidationResult(true, null);
        }
    }
}

Unlike most of my other validation scenarios, the ValidationRule for the textbox should only apply if the checkbox is checked (or rather when the boolean property that the checkbox is bound to is set to TRUE). Can anyone tell me how to implement something like this? Thanks!

2

2 Answers

9
votes

You should move your validation logic away from UI (ValidationRule) and consider implementing IDataErrorInfo in your ViewModel.

A good start is this article.

Then you could do something like this:

<StackPanel>
    <CheckBox x:Name="requirePinNumberCheckBox" IsChecked="{Binding Path=RequirePinNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Require PIN number</CheckBox>
    <TextBox x:Name="pinNumberTextBox" 
             PreviewTextInput="pinNumberTextBox_PreviewTextInput"
             Text="{Binding PinNumber, ValidatesOnDataErrors=True}"
             ToolTip="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
</StackPanel>
   public class ViewModel : IDataErrorInfo, INotifyPropertyChanged
    {
            private bool _requirePinNumber;
            public bool RequirePinNumber
            {
                get
                {
                    return this._requirePinNumber;
                }
                set
                {
                    this._requirePinNumber = value;
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("RequirePinNumber"));
                        this.PropertyChanged(this, new PropertyChangedEventArgs("PinNumber"));
                    }
                }
            }

            private string _pinNumber;
            public string PinNumber 
            { 
                get
                {
                    return this._pinNumber;
                }
                set
                {
                    this._pinNumber = value;
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("PinNumber"));
                    }
                }
            }

            public string Error
            {
                get 
                { 
                    throw new NotImplementedException(); 
                }
            }

            public string this[string columnName]
            {
                get 
                {
                    if (columnName == "PinNumber") 
                    {
                        if (this.RequirePinNumber && string.IsNullOrEmpty(this.PinNumber))
                        {
                            return "PIN number cannot be blank.";
                        }
                    }
                    return string.Empty;
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;
        }
1
votes

Forget about those old ValidationRules and start validating your data instead of your controls. Take a look at either the IDataErrorInfo Interface or INotifyDataErrorInfo Interface pages on MSDN for full details, but using these interfaces, you could move your validation code to your data classes.

public override string this[string propertyName]
{
    get
    {
        string error = string.Empty;
        if (propertyName == "PinNumber" && RequirePinNumber && 
            string.IsNullOrEmpty(PinNumber)) 
                error = "You must enter the PinNumber field.";
        ...
        return error;
    }
}