I m trying to do a simple validation on textbox, its a required field and cannot be empty. Initially value will be empty so when user do not enter any value into the field and directly clicks Save button then valitaion is not triggered. It works fine when user types something and then deletes the value from it then it works perfectly and shows the validation error message. Is there anyway to do validation check after user clicks save button.
[Display(Name = "Sometext", Description = "Some text")]
[Required(ErrorMessage = "Required Field")]
public string SomeText
{
get
{
return _someText;
}
set
{
if (_someText== value &&
value != string.Empty)
{
return;
}
Validate(value, "someText");//This calls Validator.ValidateProperty method
_someText= value;
FirePropertyChanged("someText");
}
}
Please suggest!
Thanks in advance
Sai
You could also call on your command execution
Validator.ValidateObject(this, new ValidationContext(this,null,null),true);
This should validate all properties on your viewmodel, assuming you call this from your viewmodel
edit: Response to comment
You could have a property like so (below) in your BaseViewModel (every viewmodel extends BaseViewModel), and then disallow save with a proper message
protected bool HasValidationErrors
{
get
{
try {
Validator.ValidateObject(this, new ValidationContext(this, null, null), true);
return false;
}
catch (ValidationException) { return true; }
}
}
In your command you would call it like so
public void SaveCommandExecuted(object parameter)
{
if (HasValidationErrors)
{
ShowValidationError();
}
}
Another thing, you could try is to bind the View event for validation errors to a listener in you viewmodel
MyProgram.ViewModels.BaseViewModel baseViewModel = page.Resources["DataSource"] as MyProgram.ViewModels.BaseViewModel;
page.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(baseModel.OnValidationError);
then in your BaseViewModel
private ObservableCollection<ValidationError> Errors { get; set; }
public void OnValidationError(object sender, ValidationErrorEventArgs e)
{
switch (e.Action)
{
case ValidationErrorEventAction.Added:
Errors.Add(e.Error);
break;
case ValidationErrorEventAction.Removed:
Errors.Remove(e.Error);
break;
default:
break;
}
}
then modify HasValidationErrors to
protected bool HasValidationErrors
{
get
{
try {
Validator.ValidateObject(this, new ValidationContext(this, null, null), true);
return this.Errors.Count != 0;
}
catch (ValidationException) { return true; }
}
}
Josh Twist has given a work around for this. This works perfectly..
http://www.thejoyofcode.com/Silverlight_Validation_and_MVVM_Part_II.aspx
Related
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.
I have two date fields: StartDate and EndDate. StartDate must be earlier than EndDate.
If the user changes the StartDate to something greater than the EndDate, a red border appears around that DatePicker, and vise versa. If the user changes the 2nd box so that the date range is now correct, the 1st box still has the Validation Error.
How can I validate both date fields when either one of them changes?
I'm using IDataErrorInfo
public string GetValidationError(string propertyName)
{
switch (propertyName)
{
case "StartDate":
if (StartDate > EndDate)
s = "Start Date cannot be later than End Date";
break;
case "EndDate":
if (StartDate > EndDate)
s = "End Date cannot be earlier than Start Date";
break;
}
return s;
}
I cannot simply raise a PropertyChange event because I need to validate both fields when either of them changes, so having both of them raise a PropertyChange event for the other will get stuck in an infinite loop.
I also do not like the idea of clearing the Date field if the other date returns a validation error.
The simplest way is to raise a PropertyChanged notification for in the setter for both properties that need to be validated like bathineni suggests
private DateTime StartDate
{
get { return _startDate; }
set
{
if (_startDate != value)
{
_startDate = value;
RaisePropertyChanged("StartDate");
RaisePropertyChanged("EndDate");
}
}
}
private DateTime EndDate
{
get { return _endDate; }
set
{
if (_endDate!= value)
{
_endDate= value;
RaisePropertyChanged("StartDate");
RaisePropertyChanged("EndDate");
}
}
}
However if that doesn't work for you, I figured out one way to validate a group of properties together, although your classes have to implement INotifyPropertyChanging in addition to INotifyPropertyChanged (I'm using EntityFramework and by default their classes implement both interfaces)
Extension Method
public static class ValidationGroup
{
public delegate string ValidationDelegate(string propertyName);
public delegate void PropertyChangedDelegate(string propertyName);
public static void AddValidationGroup<T>(this T obj,
List<string> validationGroup, bool validationFlag,
ValidationDelegate validationDelegate,
PropertyChangedDelegate propertyChangedDelegate)
where T : INotifyPropertyChanged, INotifyPropertyChanging
{
// This delegate runs before a PropertyChanged event. If the property
// being changed exists within the Validation Group, check for validation
// errors on the other fields in the group. If there is an error with one
// of them, set a flag to true.
obj.PropertyChanging += delegate(object sender, PropertyChangingEventArgs e)
{
if (validationGroup.Contains(e.PropertyName))
{
foreach(var property in validationGroup)
{
if (validationDelegate(property) != null)
{
validationFlag = true;
break;
}
}
}
};
// After the Property gets changed, if another field in this group was
// invalid prior to the change, then raise the PropertyChanged event for
// all other fields in the Validation Group to update them.
// Also turn flag off so it doesn't get stuck in an infinite loop
obj.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
if (validationGroup.Contains(e.PropertyName))
{
if (validationFlag && validationDelegate(e.PropertyName) == null)
{
validationFlag = false;
foreach(var property in validationGroup)
{
propertyChangedDelegate(property);
}
}
}
};
}
}
To use it, add the following call to the constructor of any class that should validate a group of properties together.
this.AddValidationGroup(
new List<string> { "StartDate", "EndDate" },
GetValidationError, OnPropertyChanged);
I've tested this with up to 3 properties in a Validation Group and it seems to work OK.
Use this trick, it prevents they call OnPropertyChanged each other :
private bool RPCfromStartDate = false;
private bool RPCfromEndDate = false;
public string this[string columnName]
{
get
{
string result = null;
switch (columnName)
{
case "StartDate":
if (StartDate.Date >= EndDate.Date)
{
result = "Start Date cannot be later than End Date";
}
if (!RPCfromEndDate)
{
RPCfromStartDate = true;
OnPropertyChanged("EndDate");
RPCfromStartDate = false;
}
case "EndDate":
if (StartDate.Date >= EndDate.Date)
{
result = "End Date cannot be earlier than Start Date";
}
if (!RPCfromStartDate)
{
RPCfromEndDate = true;
OnPropertyChanged("StartDate");
RPCfromEndDate = false;
}
break;
}
...
I generally add all of my validation errors to a dictionary, and have the validation template subscribe to that via the property name. In each property changed event handler, I can check any number of properties and add or remove their validation status as necessary.
Check this answer for how my implementation looks. Sorry it is in VB.NET, but should be fairly straightforward.
You can also subscribe to the SelectedDateChanged event handler and update needed binding.
BindingExpression expression = datePickerStartDate.GetBindingExpression(DatePicker.SelectedDateProperty);
if (expression != null)
{
expression.UpdateSource();
}
I have a window that I display as ShowDialog
in the window I have some textboxes binding to object that implement INotifyPropertyChannges and IDataErrorInfo.
I want that the OK button will enabled just if all thextboxes validted
and I want that just if the user click on OK buton the next move will occur.
I can bind the button to ICommand and check the textboxes valitation in CanExcute() but then what can I do in the Excute? the object dont know about the window.
I can also check the textboxes valitation and then raise event that all valid and enable the OK button but then there will be dupliacte code because I checked already in the IDataErrorInfo implmention.
So what is the right way?
Thanks in advance
You CanExecute should look like this.
public bool CanExecuteOK
{
get
{
if (DataModelToValidate.Error == null && DataModelToValidate.Errors.Count == 0) return true;
else return false;
}
}
Here Error and Errors properties are nothing but Wrapper over this[string propertyName] (implemented implicitly for IDataErrorInfo).
Here is Sample Model Class:
public class SampleModel: IDataErrorInfo, INotifyPropertyChanged
{
public SampleModel()
{
this.Errors = new System.Collections.ObjectModel.ObservableCollection<string>();
}
private string _SomeProperty = string.Empty;
public string SomeProperty
{
get
{
return _SomeProperty;
}
set
{
if (value != _SomeProperty)
{
_SomeProperty= value;
RaisePropertyChanged("SomeProperty");
}
}
}
....
....
//this keeps track of all errors in current data model object
public System.Collections.ObjectModel.ObservableCollection<string> Errors { get; private set; }
//Implicit for IDataErrorInfo
public string Error
{
get
{
return this[string.Empty];
}
}
public string this[string propertyName]
{
get
{
string result = string.Empty;
propertyName = propertyName ?? string.Empty;
if (propertyName == string.Empty || propertyName == "SomeProperty")
{
if (string.IsNullOrEmpty(this.SomeProperty))
{
result = "SomeProperty cannot be blank";
if (!this.Errors.Contains(result)) this.Errors.Add(result);
}
else
{
if (this.Errors.Contains("SomeProperty cannot be blank")) this.Errors.Remove("SomeProperty cannot be blank");
}
}
......
return result;
}
My code is here>>
public class Player:INotifyPropertyChanging
{
string addressBar;
public string Url
{
get {
return addressBar;
}
set { addressBar = value; OnPropertyChanged("Url"); }
}
public Regex regVillage = new Regex(#"\?doc=\d+&sys=[a-zA-Z0-9]{2}");
RelayCommand _AddAttackTask;
public ICommand AddAttackTask
{
get {
if (_AddAttackTask == null)
{
_AddAttackTask = new RelayCommand(param =>
{
}, param => this.CanAttack);
}
return _AddAttackTask;
}
}
public Boolean CanAttack
{
get{
if (Url == null) return false;
return regVillage.IsMatch(Url);
}
}
}
On the xaml, i have textbox and button. Textbox binded by url, button binded by AddAttackTask. When i change textbox value,Url changed.Main target is When changing url, button bring to enable or disable. But button always disabled.
I'm getting RelayCommand class from WPF Apps With The Model-View-ViewModel Design Pattern
What is wrong on my code?
Please fix my command binding!
I found it yourself.
Must call CommandManager.InvalidateRequerySuggested(); function after changing property
public string Url
{
get {
return addressBar;
}
set { addressBar = value; OnPropertyChanged("Url");
CommandManager.InvalidateRequerySuggested();
}
}
Apologies for cross posting (I asked this on the Silverlight Forum but got no response)
I have an entity that I am trying to use validation on so I have decorated a property like so:
[Required]
[StringLength(10)]
public string Code
{
get
{
return this.code;
}
set
{
if (this.code != value)
{
this.code = value;
this.SendPropertyChanged("Code");
}
}
}
I have a list of these objects bound to a grid. If I put an empty entry in, it shows a validation error. If I put too long a code in, I get a validation error. Perfect! Except...
I want to be able to stop the user from saving the entity so I added the following to my entity:
public bool IsValid()
{
try
{
this.Validate();
}
catch
{
return false;
}
return true;
}
public void Validate()
{
var ctx = new ValidationContext(this, null, null);
Validator.ValidateObject(this, ctx);
}
And when i go to save I call the IsValid method on each object and not save if it's false.
This works fine for the required attribute (it wont save if Code is empty) but not for StringLength (I can save with any length code).
I have reproduced this in a simple project here:
http://walkersretreat.co.nz/files/Slvalidation.zip
Can anyone help?
Thanks!
Mark
you should write as:
[CustomValidation( typeof( MyExtraClassValidation ), "Validate" )]
public class MyExtraClass : Entity, IEditableObject, INotifyPropertyChanged
{
/****/
}
public class MyExtraClassValidation
{
public MyExtraClassValidation ()
{}
public static ValidationResult Validate( MyExtraClass myExtraClass )
{
if ( /**class is not valid*/)
return new ValidationResult( "Oops" );
return ValidationResult.Success;
}
}
Of course, your interfaces may be ablolutely anorther, but I reccomend you use it.
Also, you can call validateHandler from your control and check, for example, after every user key pressing.