Currently I am using the IDataErrorInfo
interface to implement validation in a WPF application. The indexer which is part of that interface allows to validate a single property, like so:
public string this[string columnName]
{
get
{
switch (columnName)
{
case "LastName":
if (string.IsNullOrEmpty(this.LastName))
return "LastName must not be empty.";
break;
// case, case, case, etc., etc.
}
return null;
}
}
If a validation error occurs I display an asterisk with a tooltip next to the TextBox.
What can I do if I have validation rules which are not strictly related to a single property? For instance, if a domain entity representing an order has a shipping date and an invoice date and I want to validate the rule that the invoice date must be later than or equal to the shipping date?
Of course I could force this rule into the indexer as well by checking the relationship twice, once for columnName "ShippingDate" and once for columnName "InvoiceDate" and then marking the error with an asterisk at both input fields in the UI, like so:
public string this[string columnName]
{
get
{
switch (columnName)
{
case "ShippingDate":
case "InvoiceDate":
if (this.ShippingDate > this.InvoiceDate)
return "Invoice date must not be before shipping date.";
break;
}
return null;
}
}
But I would prefer to have an "object level" or "cross property" validation independently from the indexer (the indexer should only report an invalid "single property state") and display those object level or relationship errors separately on the UI.
I was hoping that the Error
property of the IDataErrorInfo
interface might have this purpose of object level validation. WPF calls the indexer for property validation when I specify ValidatesOnDataErrors=True
in the Binding expression of a TextBox, for instance. But I couldn't find a way to tell WPF to call the Error
property whenever I change some data in my input fields. Maybe my guess about the purpose of this property is wrong?
How can I implement cross-property validation in WPF?
Thank you for suggestions in advance!
Regarding my question if I can setup a binding so that WPF tests automatically the Error
property of the IDataErrorInfo
interface I found the following negative answer here:
Question from someone:
Basically, I'd like to know of a Binding property that will trigger the testing of IDataErrorInfo.Error, in the way that ValidatesOnDataErrors causes the testing of IDataErrorInfo.Item.
Answer from Microsoft Online Community Support:
Setting the ValidatesOnDataErrors property of the Binding class only tests of the IDataErrorInfo.Item and not of the IDataErrorInfo.Error.
The Binding class doesn't provide a property to check the IDataErrorInfo.Error as the ValidatesOnDataErrors property to check the IDataErrorInfo.Item so far.
To get what yo want, we have to set up a data binding to the IDataError.Error...
So, the Error
property doesn't have any more value than defining my own hand-made property (like CrossPropertyErrors
) in the domain entities. WPF doesn't support testing of the Error
property in an easy built-in way.
Edit: The quotes above are from March 2008, so very likely related to .NET 3.5. But I couldn't find any sign that this did change in .NET 4.0.
Edit: Finally I had to create my own hand-written binding to the Error
property and fill it with appropriate cross-property error messages. Every change of any other property in the class raises now a PropertyChanged
Event of both the changed property itself and of the Error
property to refresh the Error message on the UI.
Edit 2
It looks roughly like this:
Model (or ViewModel) classes:
public class SomeModel : NotificationObject, IDataErrorInfo
{
private string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if (_someProperty != value)
{
_someProperty = value;
RaisePropertyChanged("SomeProperty", "Error");
// That's the key: For every changed property a change
// notification also for the Error property is raised
}
}
}
// The above repeats for every property of the model
#region IDataErrorInfo Member
public string Error
{
get
{
var sb = new StringBuilder();
// for example...
if (InvoiceDate < ShippingDate)
sb.AppendLine("InvoiceDate must not be before ShippingDate.");
// more cross-property validations... We have only one Error
// string, therefore we append the messages with
// sb.AppendLine("Another message...") ... etc.
// could all be moved into a separate validation class
// to keep the model class cleaner
return sb.ToString();
}
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "ShippingDate":
// property-level validations
case "InvoiceDate":
// property-level validations
// etc.
}
return null;
}
}
#endregion
}
NotificationObject
implements RaisePropertyChanged
:
public abstract class NotificationObject : INotifyPropertyChanged
{
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected void RaisePropertyChanged(params string[] propertyNames)
{
if (propertyNames == null)
throw new ArgumentNullException("propertyNames");
foreach (var name in propertyNames)
RaisePropertyChanged(name);
}
// ...
}
Then in a view the Error
property is bound to - for instance - a TextBlock
which displays the cross-property validation errors:
<TextBlock Text="{Binding SomeModel.Error}" TextWrapping="Wrap" ... />
So: Every changed property on the model will notify the WPF binding engine about a (potential) change of the Error
property causing therefore an update of the cross-property validation text.