Up until recently, I have used a custom extended version of the IDataErrorInfo
interface. My extension enables me to work with multiple errors simultaneously and so far, it's served me very well. However, with the introduction of the INotifyDataErrorInfo
interface, I thought I'd experiment with it to see if there was any improvement.
After following some online tutorials, I got it working with the various ValidationAttribute
s from the System.ComponentModel.DataAnnotations namespace
. Using these Attribute
s let you provide basic validation rules like this:
[MinLength(3, ErrorMessage = "Name must be longer than 3 characters.")]
public string Name
{
get { return name; }
set { name = value; NotifyPropertyChanged("Name"); Validate("Name", name); }
}
Initially, it seemed pretty good, as the error messages plug right into the Valaidation.Errors
collection available in the applied ErrorTemplate
s. However, most of the built in validation rules are really basic and I'm used to having to implement complicated validation rules that involve other property values.
So I set out to find a way to create a simple validation rule that involved multiple properties: A rule that one of two or more fields must be set. So I declared a class that extended the ValidationAttribute
and after searching online, found a way to access the other property values.
I knocked up a basic UI with a custom ErrorTemplate
applied to each TextBox
, that displayed the Validation.Errors
collection for the data bound property:
<ControlTemplate x:Key="ErrorTemplate">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="#4FFF0000" BorderThickness="1" Margin="0,10">
<AdornedElementPlaceholder />
</Border>
<Image Name="WarningImage" Source="pack://application:,,,/WpfApplication1;component/Images/Warning_16.png" Margin="5,0,0,0" Tag="{Binding}" />
<Popup PlacementTarget="{Binding ElementName=WarningImage}" Placement="Right" Margin="5,0,0,0" AllowsTransparency="True" IsOpen="True">
<Border BorderThickness="1" BorderBrush="#4FFF0000" CornerRadius="5" Background="White" Padding="5" Margin="10">
<Border.Effect>
<DropShadowEffect Color="Red" Opacity="0.5" BlurRadius="15" ShadowDepth="0" />
</Border.Effect>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Popup>
</StackPanel>
</ControlTemplate>
With my custom Attribute
set on the Name
property, I managed to add a ValidationResult
into the Validation.Errors
collection through the interface when neither property was set, but here's the problem: If I added a value into one of the other TextBox
es data bound to the other required properties, the error message in the first TextBox
would stay there.
If I went back to the first TextBox
and typed something, then the validation would work, so even if I deleted the value, it still knew that one of the required properties was set. So the validation code works, but the problem is that property changes to the other required properties do not trigger the validation in the Name
property.
Even when I applied the same custom Attribute
to the other required properties, the same thing happened... each validation error would only clear when typing in its related TextBox
. I also tried the built in CustomValidationAttribute
which enables us to call a method in the class to validate with, but the end result was the same.
The validation code works, but is just not triggered from the other required property changes. I even tried calling the Validate
method, passing in the names of the other properties, but that ended in a continuous loop. So the question is, how can I trigger a validation on one property when another property has been validated?
Here's what I did, in a class containing From
and To
properties. I wanted to validate that From
is less than or equal to To
.
The validation logic is applied using CustomValidationAttribute
, which is easier than creating your own validation attribute classes. You simply tell it the type of your class, and the name of the method to call that contains your validation logic (the method must have a specific signature though). Here is my relevant code:-
[CustomValidation(typeof(MyModel), "ValidateRange")]
public double From
{
get
{
return _from;
}
set
{
if (_from != value)
{
_from = value;
OnPropertyChanged("From");
// Validate the other side
ValidateProperty("To", _to);
}
}
}
[CustomValidation(typeof(MyModel), "ValidateRange")]
public double To
{
get
{
return _to;
}
set
{
if (_to != value)
{
_to = value;
OnPropertyChanged("To");
// Validate the other side
ValidateProperty("From", _from);
}
}
}
private static ValidationResult ValidateRange(ValidationContext validationContext)
{
var model = validationContext.ObjectInstance as MyModel;
if (model.From > model.To)
{
return new ValidationResult("Invalid range");
}
return null;
}
As you can see, the code in one property setter forces validation of the "other" property, exactly as you mentioned in your last paragraph. There's no reason why it should go into an endless loop, unless your validation code is trying to set one of the properties, which would trigger another call to Validate(), and so on, and so on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With