Question

I have a ViewModel that implements the IDataErrorInfo interface. It just has a property: MyNumber.

In my view have a TextBox in which the user can type any number, but only numbers from 0 to 9 are valid.

So far, the textbox is marked in red when the validation fails, but I have a problem: this doesn't avoid the bound property in the ViewModel to be set.

In few words, the MyNumber property is set no matter the input is valid or not. I don't want the MyNumber gets invalid data.

How could achieve this in a pure MVVM approach?

Thanks a lot!

Was it helpful?

Solution 2

After some time researching, I've come to a very satisfying solution:

Since I'm using the Cinch MVVM Framework by Sacha Barber, the properties in my ViewModel that require validation use a very handy class that is called DataWrapper. It wraps the actual property and provides some useful features. In fact, the MyProperty I mentioned before is a DataWrapper. I added to its "Rules" collection the desired rule "x <= 5".

A very good thing about the DataWrapper class is that is contains a property called "IsValid" that indicates whether the actual value breaks the rules you have set to it.

This encapsulates in the wrapper all the properties I need to check for validation!

The next step is set up a Binding in the XAML to the DataWrapper, but I decided the Binding will only apply when the DataWrapper contains a VALID value.

WARNING. complex explanation ahead.

You can ask me why I need this behavior. It's a bit complex. The view is like a wizard in which the user can choose sizes, colors and other graphic aspects of the model. The model is represented in the View using a Control. To simplify the understanding (it's a bit more complex than this) you can assume that the model is a class called Portrait and it will be represented in the View with a Border control. The TextBox in which the user enters the thickness of the Portrait has a Binding to a property in the ViewModel (the aforementioned DataWrapper), that I will call "BorderThicknessWrapper". In fact, the Binding is set to BorderThicknessWraper.DataValue, so typing into the TextBox modifies the actual value of the data held in the DataWrapper.

With that, we have the value that the user enters in the TextBox in the ViewModel.

Now we want to make the control that represents the model to reflect the changes. We said we will have a Border as the representation of our Portrait, so every change the the BorderThicknessWrapper will modify the BorderThickness of the Border. But wait, we DO WANT it to represent valid values so in the XAML I have this! Magical trick ;)

<Border>
  <Border.Style>
    <Style TargetType="{x:Type Border}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding BorderThickessWrapper.IsValid}" Value="True">
                <Setter Property="BorderThickness" Value="{Binding GutterWidth.DataValue}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Border.Style>

As you can see, the Border has a Style with a DataTrigger that states "when the wrapper has a valid value, set the binding". When the property is not valid, the default value is applied.

What we need so far!

I hope you understand the solution. It's pure MVVM based :D and pretty SOLID!

OTHER TIPS

I would suggest that you're going about this the wrong way.

If you want to restrict input to numbers only, don't use a standard text box. Instead use a control that restrict what input / format is valid, such as IntegerUpDown / DecimalUpDown / DoubleUpDown from the WPF Control Toolkit - available via Nuget. You can then bind the value of this control to a field of the appropriate numeric type in the view model.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top