How to clear errors generated by INofityDataErrorInfo - wpf

Say, I have a field Email and I want to show RegEx errors while user is typing in. But it is an optional field, so if user submits the form, this field should be cleared of any errors, and null value should be sent.
I have implemented INofityDataErrorInfo interface in my ViewModel.

If you have implemented the INotifyDataErrorInfo interface correctly you should just be able to raise the ErrorsChanged event and make sure that the GetErrors method returns null for the Email property:
...
private readonly Dictionary<string, ICollection<string>> _validationErrors = new Dictionary<string, ICollection<string>>();
public string Email { get; set; }
public void Submit()
{
if (_validationErrors.ContainsKey(nameof(Email)))
_validationErrors.Remove(nameof(Email));
RaiseErrorsChanged(nameof(Email));
...
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)
|| !_validationErrors.ContainsKey(propertyName))
return null;
return _validationErrors[propertyName];
}
public bool HasErrors
{
get { return _validationErrors.Count > 0; }
}
The GetErrors method will be called by the runtime when you raise the ErrorsChanged event.

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);
}
}

INotifyDataErrorInfo only trigger when needed

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.

How to implement INotifyDataErrorInfo for Datarow

i have a datarow class that implements Dynamicobject and INotifyPropertyChanged and INotifyDataErrorInfo
Also have a property called 'GridData'(datarows) in this class that is bound to xaml for displaying in the grid
May i know how to implement public IEnumerable GetErrors(string propertyName)
correctly since 'GridData' property can have many property bags
Thanks
//This is your row... more or less.
public class GridData : DynamicObject, INotifyDataErrorInfo
{
private Dictionary<string, object> _propertyValues = new Dictionary<string, object>();
//this object holds your errors.
private Dictionary<string, List<ValidationResult>> _errorsContainer = new Dictionary<string, List<ValidationResult>>();
//when this fires it notifies the UI the errors of this object have changed.
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
//This tells the UI there are errors.
public bool HasErrors
{
get { return this._errorsContainer.Count > 0; }
}
//this allows the UI to retrieve all errors for a given property
public IEnumerable GetErrors(string propertyName)
{
return this._errorsContainer[propertyName];
}
//This sets the error for a given property and fires the errors changed event
protected void SetError(string propertyName, IEnumerable<ValidationResult> errors)
{
List<ValidationResult> existingErrors;
if(this._errorsContainer.TryGetValue(propertyName, out existingErrors) != true)
{
this._errorsContainer[propertyName] = errors.ToList();
}
else
{
existingErrors.AddRange(errors);
}
this.RaiseErrorsChanged(propertyName);
}
//This clears the errors for a given property
protected void ClearErrors(string propertyName)
{
this._errorsContainer.Remove(propertyName);
this.RaiseErrorsChanged(propertyName);
}
//This raises the event that the errors of this object have changed.
protected void RaiseErrorsChanged(string propertyName)
{
if(this.ErrorsChanged != null)
{
this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
//inherited from dynamic object this returns the value for a given property.
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
//this gives you the name of the property.
string name = binder.Name;
return _propertyValues.TryGetValue(name, out result);
}
//inherited from dynamic object, this is called when a property is set.
public override bool TrySetMember(SetMemberBinder binder, object value)
{
string propertyName = binder.Name;
List<ValidationResult> validationErrors = new List<ValidationResult>();
//store the value in the propertyValues regardless if it is erroneous.
_propertyValues[propertyName] = value;
//this is where you test the value of the property.
if(value /* whatever condition you use to test it */)
{
//no errors so update the ui.
this.ClearErrors(propertyName);
}
else
{
//there was an error for this value.
ValidationResult result = new ValidationResult("The value is wrong.");
//add the error to the list of errors for this property
validationErrors.Add(result);
//update the error container telling it there are errors for this property.
//fire the errors changed event, and update ui.
this.SetError(propertyName, validationErrors);
}
return true;
}
}

MVVM-Light Toolkit -- How To Use PropertyChangedMessage

Can someone please post a working example of the PropertyChangedMessage being used? The description from the GalaSoft site states:
PropertyChangedMessage: Used to broadcast that a property changed in the sender. Fulfills the same purpose than the PropertyChanged event, but in a less tight way.
However, this doesn't seem to work:
private bool m_value = false;
public bool Value
{
get { return m_value ; }
set
{
m_value = value;
Messenger.Default.Send(new PropertyChangedMessage<bool>(m_value, true, "Value"));
}
This is related with the MVVM Light Messenger.
In your property definition yo use like this:
public string Name {
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
var oldValue = _name;
_name = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(Name, oldValue, value, true);
}
}
Then you can suscribe to any modification on the property using something like this:
Messenger.Default.Register<PropertyChangedMessage<string>>(
this, (e) => this.Name = e.NewValue
);
Look at this post and read about the MVVM Light Messenger
To broadcast:
Messenger.Default.Send<PropertyChangedMessage<string>>(oldValue, newValue, "PropertyName");
Daniel Castro commented on my question with the following question: "What do you expect from the code?"
The answer to this question prompted me to write this answer to my own question.
My expectations were, based on the badly written description for the PropertyChangedMessage class in the MVVM-Light documentation, that when I sent a PropertyChangedMessage then the RaisePropertyChanged method on the ViewModelBase class would get automatically called.
Apparently, however, it's the other way around. When you call RaisePropertyChanged, then that method has an overload where you can set a flag which determines whether or not a PropertyChangedMessage will be sent.
However, I want the functionality that I originally expected. I want to send off a new PropertyChangedMessage that automatically causes RaisePropertyChanged to be called. Here's how to do that.
Derive a new class from ViewModelBase with the following public NotifyPropertyChanged method which simply calls the protected RaisePropertyChanged method:
public abstract class MyViewModelBase : GalaSoft.MvvmLight.ViewModelBase
{
public void NotifyPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
}
Then derive a new class from PropertyChangedMessage which calls the new NotifyPropertyChanged method:
public class MyPropertyChangedMessage<T> : PropertyChangedMessage<T>
{
public MyPropertyChangedMessage(object sender, T oldValue, T newValue, string propertyName)
: base(sender, oldValue, newValue, propertyName)
{
var viewModel = sender as MyViewModelBase;
if (viewModel != null)
{
viewModel.NotifyPropertyChanged(propertyName);
}
}
public MyPropertyChangedMessage(object sender, object target, T oldValue, T newValue, string propertyName)
: base(sender, target, oldValue, newValue, propertyName)
{
var viewModel = sender as MyViewModelBase;
if (viewModel != null)
{
viewModel.NotifyPropertyChanged(propertyName);
}
}
}
I have tested this approach and verified that I can indeed write code like the following which causes the UI to update properly:
private bool m_value = false;
public bool Value
{
get { return m_value; }
set
{
Messenger.Default.Send(new MyPropertyChangedMessage<bool>(this, m_value, value, "Value"));
m_value = value;
}
}

Disable button if validation in model has error

Hi I make validation on error in my model class.
public class CurrentUser:IDataErrorInfo, INotifyPropertyChanged
{
//...
private string _validationResult;
private string _nick;
public string Nick
{
get { return _nick; }
set
{
_nick = value;
NotifyPropertyChanged("Nick");
}
}
public string ValidationResult
{
get { return _validationResult; }
private set
{
_validationResult = value;
NotifyPropertyChanged("ValidationResult");
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
#region Implementation of IDataErrorInfo
private string NickValid()
{
if (string.IsNullOrEmpty(Nick))
{
return NickNull;
}
if (Regex.IsMatch(Nick, "[^a-zA-Z0-9-_.]"))
{
return NickInvalidCharacters;
}
return string.Empty;
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get
{
ValidationResult = string.Empty;
switch (propertyName)
{
case "Nick":
ValidationResult = NickValid();
break;
default:
break;
}
return ValidationResult;
}
}
#endregion
}
This model class I use in view model and I bind Nick property of model class to the Text property of comboBox control.
Also I bind method LogOn from view model class on button click event in view. I would like disabale button if validation in model class has error:
View model:
[Export(typeof(ILogOnViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LogOnViewModel : Screen, ILogOnViewModel,
IPartImportsSatisfiedNotification
{
public CurrentUser CurrentUser { get; set; }
public bool CanLogOn
{
get
{
return string.IsNullOrWhiteSpace(CurrentUser.ValidationResult);
}
}
//bind on button click event
public void LogOn()
{}
}
Solution is simple set CanLogOn property on false if validation in CurrentUser (object) property has error.
But I don’t how notify property CanLogOn that in model class is not error. I run app and button is still disabled.
I need achive this behavior in model:
public string ValidationResult
{
get { return _validationResult; }
private set
{
_validationResult = value;
NotifyPropertyChanged("ValidationResult");
//notify property CanLogOn in view model class
}
}
Any advice? Thank.
Attach an event handler to the PropertyChanged event of the user in your viewmodel:
CurrentUser.PropertyChanged += new PropertyChangedEventHandler(CurrentUser_PropertyChanged);
Add send a notification if the ValidationResult changes:
void CurrentUser_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "ValidationResult") NotifyPropertyChanged("CanLogOn");
}
Note: If your reference CurrentUser is overwritten you need to add the event handler to the new object. You could do this by placing the attachment code in the setter of CurrentUser.

Resources