I use WPF data binding with entities that implement IDataErrorInfo interface. In general my code looks like this:
Business entity:
public class Person : IDataErrorInfo
{
public string Name { get; set;}
string IDataErrorInfo.this[string columnName]
{
if (columnName=="Name" && string.IsNullOrEmpty(Name))
return "Name is not entered";
return string.Empty;
}
}
Xaml file:
<TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnDataErrors=true}" />
When user clicks on "Create new person" following code is executed:
DataContext = new Person();
The problem is that when person is just created its name is empty and WPF immediately draws red frame and shows error message. I want it to show error only when name was already edited and focus is lost. Does anybody know the way to do this?
You can change your person class to fire validation error only if Name property was ever changed:
public class Person : IDataErrorInfo {
private bool nameChanged = false;
private string name;
public string Name {
get { return name; }
set {
name = value;
nameChanged = true;
}
}
//... skipped some code
string IDataErrorInfo.this[string columnName] {
get {
if(nameChanged && columnName == "Name" && string.IsNullOrEmpty(Name))
return "Name is not entered";
return string.Empty;
}
}
}
There is another solution which i found but i don't like it a lot. You have to clear validation on page load.
What i mean is you have to do this :
Validation.ClearInvalid(...)
for instance if you have a textbox you dont want to be validated should call
Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty))
or something like that.
You should do this for every control you want to be cleared of validation.
I didn't like the solution but that was the best i found. I hoped wpf had something "out of the box" that worked but didn't find it.
I think @Stanislav Kniazev approach is the correct one. Your comment about not adding logic to the business object is also valid. To have a clean separation of concern, how about keeping the Person in the business layer (or the data model layer), and introduce a new class PersonVm with view logic. For the VM layer I like the containment pattern more than inheritence, and at this layer I also implement INotifyPropertyChanged, which is also a property of the VM and not the data model.
public class PersonVm : IDataErrorInfo, INotifyPropertyChanged
{
private Person _person;
public PersonVm( ) {
// default constructor
_person = new Person( );
_dirty = false;
}
public PersonVm( Person p ) {
// User this constructor when you get a Person from database or network
_person = p;
_dirty = false;
}
void fire( string prop ) {
PropertyChanged( this, new PropertyChangedEventArgs( prop ) );
}
public string name {
get { return _person.name; }
set { _person.name = value; fire( "name" ); dirty = true; }
}
...
string IDataErrorInfo.this[string columnName] {
get {
if( dirty ) return _person[columnName];
}
}
}
The idea is to put the logic of each layer at the appropriate class. At the data model layer, you do validation that only concern the pure data. The the View Model layer, you add logic that concerns the View Model (as well as notificaton and other View Model logic).
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