简体   繁体   中英

How to catch an exception thrown during a PropertyChanged event

I am using MVC\\MVVM and WPF. I have a form that is bound to a model and a controller that is catching the PropertyChanged events and evaluating business rules and validations. Well, I want to pop-up a message when a validation is false telling the user what is wrong and how to fix it. But I'm not sure the "correct" way from the controller. I'd like to throw an exception that can be caught by the view but I can't figure out how. I've tried Dispatcher.Invoke() but that just got me an unhandled exception at the application level.

How do I trap exceptions that are generated from the PropertyChanged event handler in the Controller?

EDIT: Specifically I have a combobox that has a list of discounts in it. I can't allow an inappropriate selection, but I have to notify the user why the selection is inappropriate. This is not as obvious as a textbox that has integers in it. I need to tell the user the date the customer filled out a survey. They can't use that discount twice. I don't want to exclude the survey discount from the list since that would just look like an error to the user. I need to show them the discount and tell them the customer has used that discount and cannot use it again.

EDIT 2: I have looked over the ValidationRule class and since I have to use a database lookup I don't see how I'm going to keep everything in the Model and still have the business rules in the Controller. I've looked at IDataErrorInfo but that requires me to wrap my Model in my Controller and bind to the Controller instead, but only for one field. I think the best course of action in this case is to have the Controller call a method on the View and pop-up a message.

You are walking down the wrong path.

A good way to handle validation in MVVM is by implementing IDataErrorInfo and setting ValidatesOnDataErrors to true on your bindings. You will also almost certainly want to enable ValidatesOnExceptions for completeness, and NotifyOnValidationError so that the binding engine triggers the Validation.Error attached event on the control that you have bound the property to.

For more details, see the Validation section on the MSDN documentation for data binding in WPF.

Some tips:

  • .NET 4.5 introduces INotifyDataErrorInfo and the corresponding ValidatesOnNotifyDataErrors binding property that provide beefed-up validation functionality in comparison to IDataErrorInfo ; you might want to look into that.
  • You do not need to actually do anything meaningful inside IDataErrorInfo.Error because it is used by the Windows Forms infrastructure and is ignored in WPF. You can even have the getter throw NotImplementedException .
  • There is good reading material illustrating this approach, complete with examples and code, here and here .

Update: clarification and example code

This validation model does not involve implementing a ValidationRule yourself at all; your models (ie the binding sources) simply need to implement one of the two interfaces. How to implement the interface is entirely up to you; in a past project I implemented basic async validation with

public interface IDelegatedValidation : IDataErrorInfo
{
    /// <summary>
    /// Occurs when validation is to be performed.
    /// </summary>
    event EventHandler<DelegatedValidationEventArgs> ValidationRequested;
}

public class DelegatedValidationEventArgs : EventArgs
{
    public DelegatedValidationEventArgs(string propertyName)
    {
        this.PropertyName = propertyName;
    }

    public string PropertyName { get; private set; }

    public bool Handled { get; set; }

    public string ValidationError { get; set; }
}

The model implements IDelegatedValidation by exposing an event and with

string IDataErrorInfo.this[string columnName]
{
    get { return this.GetValidationError(columnName); }
}

private string GetValidationError(string propertyName)
{
    var args = new DelegatedValidationEventArgs(propertyName);
    this.OnValidationRequested(args);
    return args.ValidationError;
}

protected virtual void OnValidationRequested(DelegatedValidationEventArgs args)
{
    var handler = this.ValidationRequested;
    if (handler == null) {
        return;
    }

    foreach (EventHandler<DelegatedValidationEventArgs> target in handler.GetInvocationList()) {
        target.Invoke(this, args);
        if (args.Handled) {
            break;
        }
    }
}

So the workflow goes like this:

  1. When the model is about to be wrapped by a viewmodel, some appropriate entity that can perform the validation subscribes to its ValidationRequested event.
  2. The view binds to the model; at some point validation is triggered.
  3. The model calls GetValidationError .
  4. Event handlers get invoked one by one; the first handler that produces a validation error sets args.ValidationError and args.Handled to true so that the chain is stopped.
  5. The validation error is returned to the view.

There is an added twist in that if the viewmodel needs to know whether its model is valid or not (eg to enable/disable a "save" command) it needs to also tap into this process.

There's really nothing that you cannot do with IDataErrorInfo / INotifyDataErrorInfo because how you implement them is entirely up to you. Definitely have a look at the Silverlight version of the latter's documentation for more examples; there is also a lot of helpful material like this on the internet.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM