5

I have a textbox that is bound to an Integer property. when the user enters something in the textbox that can't be converted to an integer (eg. name) an exception will be thrown and the original property value won't change. i want to catch the exception so that i can disable a command that is connected to that property ? how can i do that in general and if possible from the view model where the property is defined ?

4 Answers 4

5

I faced the same issue recently and I used behaviors to solve it (but you don't need them if you don't want, it was just for reusing some code I needed among different views). The main idea is to define some methods in the ViewModel that allow the view to notify errors in the input that the ViewModel cannot detect.

So, first define those methods in your ViewModel. For simplicity, I will only keep track of the number of errors, but you can store more info about them (like the actual error):

private int _errorCount = 0;
void AddUIValidationError()
{
   _errorCount++;
}

void RemoveUIValidationError()
{
   _errorCount--;
}

Then, in your View, you register for System.Windows.Controls.Validation.ErrorEvent, which is a routed event that lets you know when a component (previously configured to notify data errors) detects errors (like an exception validation error):

public partial class MyView : UserControl // or whatever it is
{
    public MyView(MyViewModel viewModel)
    {
        // Possibly ensure that viewModel is not null
        InitializeComponent();
        _myViewModel = viewModel;

        this.AddHandler(System.Windows.Controls.Validation.ErrorEvent, new RoutedEventHandler(OnValidationRaised));
    }

    private MyViewModel _myViewModel;

    private void OnValidationRaised(object sender, RoutedEventArgs e)
    {
        var args = (System.Windows.Controls.ValidationErrorEventArgs)e;

        if (_myViewModel != null)
        {

            // Check if the error was caused by an exception
            if (args.Error.RuleInError is ExceptionValidationRule)
            {
                // Add or remove the error from the ViewModel
                if (args.Action == ValidationErrorEventAction.Added)
                    _myViewModel.AddUIValidationError();
                else if (args.Action == ValidationErrorEventAction.Removed)
                    _myViewModel.RemoveUIValidationError();
            }
        }
    }
}

In the CanExecute method of your Command, you would check if the _errorCount field of your ViewModel is more than 0, and in that case, the command should be disabled.

Please note that you should must add ValidatesOnExceptions=True, NotifyOnValidationError=True to your bindings so this can work. Ex:

<TextBox Text="{Binding Path=MyProperty, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />

EDIT:

Another approach, apart from what Riley mentioned (which is also good, but requires that you map each integer property from your model to a new string property in your ViewModel) is using ValidationRules. You can add ValidationRules that are checked before parsing and calling the property setter. So you could, for example, inherit from ValidationRule and implement the Validate method to ensure that the string can be parsed to an integer. Example:

public class IntegerValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        int number;
        if(Int32.TryParse((string)value, out number) == false)
            return new ValidationResult(false, "It is not a valid number");
        return new ValidationResult(true, null);
    }
}

And then, in your view define the namespace where IntegerValidationRule is defined:

<UserControl 
...
    xmlns:rules="clr-namespace:MyApplication.ValidationRules"
...>

And use the rule in your bindings:

<TextBox>
    <TextBox.Text>
       <Binding Path="MyProperty">
           <Binding.ValidationRules>

              <rules:IntegerValidationRule/>
           </Binding.ValidationRules>
       </Binding>
    </TextBox.Text>
</TextBox>

But anyway, you'll need to create classes for each non-string type you want to validate, and I think the Binding syntax now looks a bit long.

Greetings

Sign up to request clarification or add additional context in comments.

3 Comments

thanks, i like the fact that you have worked a way out the problem but i won't accept this answer for now cause i want to see if there is a simpler solution.
No problem, I'm interested too. Anyway, it's not that complicated :) . Glad you liked it
i think the conversion solution is most correct one so i accepted the answer for that. nevertheless i will into this further in the future to see if i can find a more robust solution that doesn't involve adding another class just to implement a simple method.
1

The "best" solution I've found in the web is explained at http://www.wpfsharp.com/2012/02/03/how-to-disable-a-button-on-textbox-validationerrors-in-wpf/

In short, the Validation errors are dealt with in the View, not in the ViewModel. You do not need your own ValidationRule. Just add a style to your button in XAML:

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

Hope this helps someone who stumbles upon this question (I am aware that it won't likely help the OP so many years later).

Comments

0

Consider calling the command from the view model instead of from the view.

private int _myProperty;
public int MyProperty
{
    get
    {
        return _myProperty;
    }
    set
    {
        _myProperty = value;
        // See if the value can be parsed to an int.
        int potentialInt;
        if(int.TryParse(_myProperty, out potentialInt))
        {
            // If it can, execute your command with any needed parameters.
            yourCommand.Execute(_possibleParameter)
        }
    }
}

This will allow you to handle the user typing things that cannot be parsed to an integer, and you will only fire the command when what the user typed is an integer.

(I didn't test this code, but I think it might be helpful.)

12 Comments

I think you meant public string MyProperty, and _myProperty = potentialInt; inside the if instead of the command thing. Don't think the OP wants to execute the method in each set. Instead you should disable the command somehow. Anyway, it's a nice idea :)
The text of my answer was misleading. I've edited it to reflect better what I intended. I'm suggesting calling the command from the view model only when desired instead of using binding. It's easy to forget that binding to a command is the same thing as calling command.Execute()
@Riley, thanks. i haven't looked at your answer carefully before. but i want to tell you that i have read in an article (which i don't remember) is that you should never throw an exception in a property setter. that might cause lots of trouble especially if that property is data bound.
I accepted your answer because using strings is the fastest and most easily implemented although not the most beautiful.
You are correct that throwing exceptions in a property setter is a bad idea. It's best to never generate exceptions unless the condition that triggers them is truly exceptional. Make sure you use int.TryParse() instead of int.Parse(). The int.TryParse() method will not throw exceptions if you pass in something that is not an integer, so you don't have to throw any exceptions if your user types something that cannot be parsed to an integer. See here
|
0

We can even directly try avoiding the user from entering any char to the text box

<TextBox Name="txtBox" PreviewTextInput="NumberValidationTextBox"/>

In xaml.cs

private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
{
        e.Handled = Regex.IsMatch(e.Text, "[^0-9]+");
}

We can have check in the Model for this property which checks the value before setting it to avoid any null or empty values

private int _number;
public int Number
{
     get => _number;
     set
     {
        if(!string.IsNullOrEmpty(value.ToString())
            _number = value;
     }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.