0
votes

Trying to figure out how to capture view-only validation errors such as entering non-numeric characters in a text box bound to an integer property. I would like the Catel DataWindow to behave consistently.

Description:

I have a Catel MVVM window (implemented using DataWindow and a view model with a model property.)

The model property is an integer:

    public Foo { get { GetValue .......... } }

The view model property is also an integer, and bound to the model:

    [ViewModelToModel(...)]
    public Foo { get { GetValue .......... } }

In the view, there is a text box that is bound to Foo. When the user enters a non-integer value in the text box, there is naturally an error in the binding process, and because the text box has ValidatesOnExceptions set to true, the following appears in the Catel info message bar:

Attempting to enter a non-integer in a text box bound to an integer property

Two problems that I must fix:

  • I need a custom error message ("Value 117.228 could not be converted" is not going to fly here.)
  • The WarningAndErrorValidator does pick up the error, but the DataWindow OK button is still enabled, and I am able to "save" the view model. I need OK to be disabled when there are any errors, even if they don't make it to the view model.

A web search has provided a couple possible solutions:

  1. Bind to a view model property that's a string, and handle mapping/conversion between the view model and the model
  2. Build support in the MVVM framework to trap UI validation errors and communicate them to the view model

Solution #1 is definitely the "workaround" solution because it means I need something like this in the view model (excuse the pseudo-code...):

    [ViewToViewModel(...)]
    public int Foo { ...... }

    // Also a Catel property
    public string Foo_Raw { ...... }

    // Property changed handlers for both the above properties, keeping them in sync with one another when possible...

    protected override void ValidateBusinessRules(List<.......> validationResults)
    {
        if (this.Foo_Raw != this.Foo.ToString())
        {
            validationResults.AddError("Foo must be an integer.");
        }
    }

I am not pleased at the prospect of creating this kind of rickety structure.

I'd much rather go with something like #2, but I didn't see anything in Catel's documentation that suggests that approach is supported. Did I miss an easy solution?

UPDATE: I just learned about the numeric text box behavior which might be another way to solve my specific problem, but I am really looking for a more general solution for capturing binding/UI errors in the view model validation.

2

2 Answers

1
votes

The issue is that the exceptions you are trying to receive are not yet bound (since binding them goes wrong). There is no way for the vm to be aware of this issue. Since this is a view-related issue, you can only handle this error in the view.

One solution might be to forward the messages caught by the WarningAndErrorValidator onto the view model. You can define your own WarningAndErrorValidator on the view and subscribe to the Validated event. Then you can pass this onto your vm. This will require you to write a custom base class for your views if you want this shared among all controls in your app.

1
votes

Geert van Horrik's answer was not quite correct (unless I missed something, Geert). The WarningAndErrorValidator only traps view model errors, not errors from the visual tree itself, or binding errors. It turns out that is something the InfoBarMessageControl does without help from the WarningAndErrorValidator.

What I did was, in my subclass of DataWindow, I duplicated the logic from InfoBarMessageControl that traps and analyzes visual tree validation errors, and I maintained a similar error message data structure.

I then overrode DataWindow::ValidateData like so:

    protected override bool ValidateData()
    {
        // In addition to base class logic, make sure there are no errors of any kind including view-only errors (like binding exceptions or ValidationRule errors). 
        return base.ValidateData() && this.ViewErrorCount == 0;
    }

ViewErrorCount is a simple int that I update when I trap errors as described above.