INotifyDataErrorInfo only trigger when needed - wpf

I have implemented the INotifyDataErrorInfo on my models. But I cant seem to use it when needed. For Example. It should not validate on errors on startup or as I am typing. Only when clicking on a button (save).
I have currently this in my XAML:
<TextBox Text="{Binding Car.Model, ValidatesOnNotifyDataErrors=False, UpdateSourceTrigger=Explicit}"/>
And in my ViewModel under the SaveCommand:
Car.Validate();
if (Car.HasErrors)
{
return;
}
//else save
My Model looks like this:
Public class Car:ValidateModelBase
{
private string _model;
[Required (ErrorMessage ="This field is required")]
public string Model
{
get { return _model; }
set { _model= value; RaisePropertyChanged(); }
}
}
And then my implementation of ValidateModelBase:
public class ValidateModelBase: INotifyDataErrorInfo, INotifyPropertyChanged
{
private ConcurrentDictionary<string, List<string>> _errors =
new ConcurrentDictionary<string, List<string>>();
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
ValidateAsync();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return null;
}
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
}
The thing is its working as it normally should, but the textbox is already red with rquired when Window is opened. I want it to ignore validation on startup and only validate the moment I click Save. When clicking save, it would validate and see if there is any errors. When there is errors, the validation is set (now it should be marked in red) and the window stays open. How can I achieve this.

If your model should only validate when saving, I would have your model implement IEditableObject and ensure that BeginEdit is called prior to changes being made, and call EndEdit prior to committing those changes (i.e., saving).
Have your base class track whether or not it's in 'edit mode', and if it is, suppress any validation. When EndEdit is called, allow validation again and fire ErrorsChanged. It's up to you to decide how to handle CancelEdit.
Some controls, like data grids, have built-in support for IEditableObject and will call BeginEdit when you begin editing a row, and EndEdit when committing a row. It's a pretty useful interface.

Related

WPF How to set Validation.HasError property on controls manually?

I have a wpf window which fires validation when a user interacts with the control (got into the control and change the value which results in updated property) and upon property changed, validation fire and displayed as it should.
But I want to show all validation errors on the screen manually when a user clicks on the save button without traversing the controls, otherwise how it suppose to look if the user loads the screen and click on the save button.
Even if I create a method like IsValid() and call it upon clicking on the save button, it validates the whole form and tell me if it is valid or not but the red border around text boxes won't be showing(because Validation.HasError property is not being updated), which is I need because in a form of several
controls I need to notify the user about the exact control that is causing the problem.
You can get the sample project with the problem from this link
https://1drv.ms/u/s!AuCr-YEWkmWUiopdQ-eZ17IC7IAJnA
When we validate a property without traversing it. It won't update Validate.HasError property of the control. The solution to this was plain old simple NotifyPropertyChanged(propertyName).
I was using NotifyPropertyChanged when my property value changes(in the set) but without traversing it, it never fires.
So either we should call NotifyPropertyChanged when property's validation failed or we should call NotifyPropertyChanged(null) which notify all the control's to refresh their properties.
Adding full implementation of my INotifyDataErrorInfo
public class NotifyDataErrorInfoBase<T> : INotifyDataErrorInfo
{
public NotifyDataErrorInfoBase(T model)
{
Model = model;
}
public T Model { get; set; }
protected void SetValue<TValue>(string propertyName, TValue value)
{
typeof(T).GetProperty(propertyName).SetValue(Model, value);
ValidateProperty<TValue>(propertyName);
}
public bool ValidateAllProperties()
{
List<KeyValuePair<string, Type>> lstOfProperties = typeof(T).GetProperties().
Select(u => new KeyValuePair<string, Type>(u.Name, u.PropertyType)).ToList();
foreach (var property in lstOfProperties)
{
Type currentType = property.Value;
if (property.Value == typeof(string))
{
ValidateProperty<string>(property.Key);
}
else if (property.Value == typeof(int))
{
ValidateProperty<int>(property.Key);
}
}
return !HasErrors;
}
private void ValidateProperty<TValue>([CallerMemberName]string propertyName = null)
{
ClearErrors(propertyName);
var validationContext = new ValidationContext(Model) { MemberName = propertyName };
List<ValidationResult> results = new List<ValidationResult>();
var userName = GetValue<TValue>(propertyName);
Validator.TryValidateProperty(userName, validationContext, results);
if (results.Any())
{
foreach (var item in results)
{
AddError(propertyName, item.ErrorMessage);
}
}
}
protected TValue GetValue<TValue>(string propertyName)
{
return (TValue)typeof(T).GetProperty(propertyName).GetValue(Model);
}
Dictionary<string, List<string>> _lstOfErrors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => _lstOfErrors.Any();
public IEnumerable GetErrors(string propertyName)
{
return _lstOfErrors.ContainsKey(propertyName) ? _lstOfErrors[propertyName] : null;
}
protected void AddError(string propertyName, string errorMessage)
{
if (!_lstOfErrors.ContainsKey(propertyName))
{
_lstOfErrors[propertyName] = new List<string>();
}
_lstOfErrors[propertyName].Add(errorMessage);
}
protected void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
protected void ClearErrors(string propertyName)
{
if (_lstOfErrors.ContainsKey(propertyName))
_lstOfErrors.Remove(propertyName);
}
}

UI not calling INotifyDataErrorInfo.GetErrors()

I have a model implementing both INotifyPropertyChanged and INotifyDataErrorInfo. The Property changed event fires when ever I have a property modified, but for some reason when I raise the Error event handler, the UI does ever invoke the GetErrors method. This results in the validation error not being rendered to the UI.
Can someone take a look at how I have the INotifyDataErrorInfo set up and tell me if I'm doing something wrong?
Base model implementation
public class BaseChangeNotify : INotifyPropertyChanged, INotifyDataErrorInfo
{
private bool isDirty;
private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
public BaseChangeNotify()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool IsDirty
{
get
{
return this.isDirty;
}
set
{
this.isDirty = value;
this.OnPropertyChanged();
}
}
public bool HasErrors
{
get
{
return this.errors.Count(e => e.GetType() == typeof(ErrorMessage)) > 0;
}
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
!this.errors.ContainsKey(propertyName))
{
return null;
}
return this.errors[propertyName];/*.Where(e => (e is ErrorMessage));*/
}
protected virtual void AddError(string propertyName, string error, bool isWarning = false)
{
if (!this.errors.ContainsKey(propertyName))
{
this.errors[propertyName] = new List<string>();
}
if (!this.errors[propertyName].Contains(error))
{
if (isWarning)
{
this.errors[propertyName].Add(error);
}
else
{
this.errors[propertyName].Insert(0, error);
}
this.OnErrorsChanged(propertyName);
}
}
protected virtual void RemoveError(string propertyName, string error)
{
if (this.errors.ContainsKey(propertyName) &&
this.errors[propertyName].Contains(error))
{
this.errors[propertyName].Remove(error);
if (this.errors[propertyName].Count == 0)
{
this.errors.Remove(propertyName);
}
this.OnErrorsChanged(propertyName);
}
}
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Perform the IsDirty check so we don't get stuck in a infinite loop.
if (propertyName != "IsDirty")
{
this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
}
if (this.PropertyChanged != null)
{
// Invoke the event handlers attached by other objects.
try
{
// When unit testing, this will always be null.
if (Application.Current != null)
{
try
{
Application.Current.Dispatcher.Invoke(() =>
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
catch (Exception)
{
throw;
}
}
else
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// Called when an error has changed for this instance.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
public virtual void OnErrorsChanged([CallerMemberName] string propertyName = "")
{
if (string.IsNullOrWhiteSpace(propertyName))
{
return;
}
if (this.ErrorsChanged != null)
{
this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
Model using the implementation
public class PayItem : BaseChangeNotify
{
private Section section;
public Section Section
{
get
{
return this.section;
}
set
{
this.section = value;
this.ValidateSection();
this.OnPropertyChanged();
}
}
private void ValidateSection([CallerMemberName] string propertyName = "")
{
const string sectionError = "You must select a Section.";
if (this.Section == null || this.Section.Name.Length > 1)
{
this.AddError(propertyName, sectionError);
}
else
{
this.RemoveError(propertyName, sectionError);
}
}
The View trying to use it
<ComboBox Name="SectionComboBox"
ItemsSource="{Binding Path=ProjectSections}"
SelectedItem="{Binding Path=SelectedPayItem.Section,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}">
The app is being wrote in WPF, and the WPF docs are pretty scarce. I've read through the Silverlight documentation on it along with a few other blog posts I found on the internet and have implemented in each of the different ways the blog authors suggest. Each time the result is the same, the GetErrors() method never gets hit by the Binding engine.
Can anyone see something that I'm doing wrong? When my model has its property set, I can step through the debugger and ultimately end up within the OnErrorsChanged event handler, and the event gets invoked. Nothing happens when it gets invoked though, so I'm stumped.
Thanks in advance for any help.
Johnathon
EDIT
Also I would like to note that I had been using IDataErrorInfo in the base class for the last couple of months without any issues. The binding worked, the errors were reported to the View and everything was happy. When I changed from IDataErrorInfo to INotifyDataErrorInfo, the validation appeared to stop communicating with the View.
The INotifyDataErrorInfo.HasErrors property must return true when raising the ErrorsChanged event. Otherwise the binding engine ignores the errors. Your HasErrors property will return false all the time. This happens because you are checking for items of type ErrorMessage but your dictionary contains items of type KeyValuePair<string, List<string>>. Besides that it is highly inefficent to count all the items. You should use .Any() instead.
By the way, the MSDN documentation of INotifyDataErrorInfo says the following:
Note that the binding engine never uses the HasErrors property,
although you can use it in custom error reporting.
This is plain wrong and it took me hours to find that out.

Why does BindingSource not tell me which property has changed?

I'm looking at using databinding - and the simplest thing to do seems to be to use a BindingSource to wrap my data objects.
However - while the CurrentItemChanged event tells me when a property has changed, it doesn't tell me which one - and that's a vital part of what I need.
Is there any way to find out which property is changing?
Your data objects need to implement the INotifyPropertyChanged interface:
public class MyObject : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string textData = string.Empty;
protected void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string TextData {
get { return textData; }
set {
if (value != textData) {
textData = value;
OnPropertyChanged("TextData");
}
}
}
}
Then if you use BindingList, you can use the BindingSource's ListChanged event to see which property changed:
BindingList<MyObject> items = new BindingList<MyObject>();
BindingSource bs = new BindingSource();
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
items.Add(new MyObject() { TextData = "default text" });
bs.DataSource = items;
bs.ListChanged += bs_ListChanged;
items[0].TextData = "Changed Text";
}
void bs_ListChanged(object sender, ListChangedEventArgs e) {
if (e.PropertyDescriptor != null) {
MessageBox.Show(e.PropertyDescriptor.Name);
}
}
Also see Implementing INotifyPropertyChanged - does a better way exist?

Validation firing too early

I have built a base class for my view model(s). Here is some of the code:
public class BaseViewModel<TModel> : DependencyObject, INotifyPropertyChanged, IDisposable, IBaseViewModel<TModel>, IDataErrorInfo
{
public TModel Model { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (this._disposed)
{
return;
}
if (disposing)
{
this.Model = default(TModel);
}
this._disposed = true;
}
}
Okay, so I thought, let's add some validation to the base class, which led me to the following article: Prism IDataErrorInfo validation with DataAnnotation on ViewModel Entities. So I added the following methods / properties (IDataErrorInfo) to my base class:
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get { return ValidateProperty(columnName); }
}
protected virtual string ValidateProperty(string columnName)
{
// get cached property accessors
var propertyGetters = GetPropertyGetterLookups(GetType());
if (propertyGetters.ContainsKey(columnName))
{
// read value of given property
var value = propertyGetters[columnName](this);
// run validation
var results = new List<ValidationResult>();
var vc = new ValidationContext(this, null, null) { MemberName = columnName };
Validator.TryValidateProperty(value, vc, results);
// transpose results
var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
return string.Join(Environment.NewLine, errors);
}
return string.Empty;
}
private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
{
var key = objType.FullName ?? "";
if (!PropertyLookupCache.ContainsKey(key))
{
var o = objType.GetProperties()
.Where(p => GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, CreatePropertyGetter);
PropertyLookupCache[key] = o;
return o;
}
return (Dictionary<string, Func<object, object>>)PropertyLookupCache[key];
}
private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
{
var instanceParameter = System.Linq.Expressions.Expression.Parameter(typeof(object), "instance");
var expression = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(
System.Linq.Expressions.Expression.ConvertChecked(
System.Linq.Expressions.Expression.MakeMemberAccess(
System.Linq.Expressions.Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
propertyInfo),
typeof(object)),
instanceParameter);
var compiledExpression = expression.Compile();
return compiledExpression;
}
private static ValidationAttribute[] GetValidations(PropertyInfo property)
{
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
Okay, this brings me to the issue. The thing is the validation works perfectly, but lets say I have a property (within my view model called: Person) with a StringLength attribute. The StringLength attribute fires as soon as the application is opened. The user didn't even have a chance to do anything. The validation fires as soon as the application is started.
public class PersonViewModel : BaseViewModel<BaseProxyWrapper<PosServiceClient>>
{
private string _password = string.Empty;
[StringLength(10, MinimumLength = 3, ErrorMessage = "Password must be between 3 and 10 characters long")]
public string Password
{
get { return this._password; }
set
{
if (this._password != value)
{
this._password = value;
this.OnPropertyChanged("Password");
}
}
}
}
I have noticed that this is caused by the IDataErrorInfo.this[string columnName] property, and in turn it calls the ValidateProperty method. But, I have no idea how to fix this?
There could be two issues...
Do you populate yopur Person instance by using the public properties?
e.g.
new Person { Password = null }
This will fire the property changed notification for Password and will validate it.
Some developers also set the properties in constructors...
public class Person {
public Person() {
this.Password = null;
}
}
Recommended practise is to use private fields...
public class Person {
public Person() {
_password = null;
}
public Person(string pwd) {
_password = pwd;
}
}
OR
You can create a flag in our view model base say IsLoaded. Make sure you set it to true only after your UI is loaded (probably in UI.Loaded event). In your IDataErrorInfo.this[string columnName] check if this property is true and only then validate the values. Otherwise return null.
[EDIT]
The following change did the job:
public class PersonViewModel : BaseViewModel<BaseProxyWrapper<PosServiceClient>>
{
private string _password;
[StringLength(10, MinimumLength = 3, ErrorMessage = "Password must be between 3 and 10 characters long")]
public string Password
{
get { return this._password; }
set
{
if (this._password != value)
{
this._password = value;
this.OnPropertyChanged("Password");
}
}
}
public PersonViewModel(BaseProxyWrapper<PosServiceClient> model)
: base(model)
{
this._username = null;
}
}
Something I've done in the past is change the update source trigger to explicit, create a behavior that will update the source when the TextBox loses focus, and then attach that behavior to the TextBox.

MVVM pattern filter listview and update on new item

I've been struggling with this problem for a couple of days, but somewhere I obviously on a wrong track. Situation is as follows: I have a window with 3 buttons (Add New Task, Show Inbox, Show Today) and a Listview. My TaskViewModel class is has a ObservableCollection of TaskModel, with pretty simple Filter functionality. My class looks as follows:
public class TaskViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<TaskModel> TaskCollection { get; private set; }
public TaskViewModel()
{
TaskDataAccess ac = new TaskDataAccess();
this.TaskCollection = ac.GetAllTasks();
}
public ICommand AddTaskCommand
{
get { return new DelegateCommand(this.AddTask); }
}
public ICommand FilterInboxCommand
{
get { return new DelegateCommand(this.FilterInbox); }
}
public void AddTask()
{
this.TaskCollection.Add(new TaskModel(9, "I", "New Item for testing"));
this.GetListCollectionView().Filter = this.IsInbox; ;
}
private void FilterInbox()
{
this.GetListCollectionView().Filter = this.IsInbox;
}
....
}
The filter functionality works fine, but when I call the new window "Add new task" it does not update the listview (here: this.TaskCollection.Add(new TaskModel(9, "I", "New Item for testing"));
I'd appreciate if someone could give me a hint...
Try to do this...
create a private field (say _taskCollection) to backup your property TaskCollection.
private readonly ObservableCollection<TaskModel> _taskCollection;
Then remove the private setter from TaskCollection property. Also remove the constructor code that loads the collection.
Instead write your getter this way...
public ObservableCollection<TaskModel> TaskCollection {
get {
if (this._taskCollection == null)
{
TaskDataAccess ac = new TaskDataAccess();
this._taskCollection = ac.GetAllTasks();
}
return this._taskCollection;
}
}
Let me know if this way works ....

Resources