Data validation on ShowDialog window in WPF - wpf

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

Related

Winforms Designer custom property does not keep its value when I build

I have a control that I subclass from Label (as an example) and add a custom property to it. I have a custom editor that allows me to pick an object of a custom type. The custom type is attributed with the converter and and editor. Editing works fine - I click in my property cell in the VS designer property grid and the ellipsis displays. I click that and I get my custom form displaying my list to choose from. I make a selection and that selection shows up in my property grid. All good. However, I can not seem to clear that value by backspacing over it like I can with most properties. And more importantly, as soon as I build the project the property value disappears. Before I build, I can go from control to control and the value is set properly, but as soon as I build (or save and close the form) the property loses its value. My custom control looks like this:
public class MyLabel : Label
{
private MyAlias _alias;
public MyAlias Alias
{
get
{
return _alias;
}
set
{
if (_alias != value)
{
_alias = value;
}
}
}
}
The custom type:
[System.ComponentModel.TypeConverter(typeof(MyConverter))]
[System.ComponentModel.Editor(MyTypeEditor, System.Drawing.Design.UITypeEditor)]
public class MyAlias
{
private string _aliasName;
public string AliasName
{
get
{
return _aliasName;
}
set
{
if (_aliasName != value)
{
_aliasName = value;
}
}
}
public MyAlias(string aliasName)
{
if (aliasName == null)
{
return;
}
_aliasName = aliasName;
}
public override string ToString()
{
return _aliasName;
}
}
Custom converter:
public class MyConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (!(value is string))
{
return base.ConvertFrom(context, culture, value);
}
return new MyAlias((string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
MyAlias alias = (MyAlias)value;
return alias.ToString();
}
}
Custom Editor:
public partial class AliasEditorForm : Form
{
private object _value;
public object Value
{
get
{
return _value;
}
set
{
if (_value != value)
{
_value = value;
}
}
}
public AliasEditorForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// Button does have DialogResult property set to OK
_value = new MyAlias(...text string from form controls here...);
this.Close();
}
}
public class MyTypeEditor : UITypeEditor
{
protected IWindowsFormsEditorService edSvc = null;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (null != context && null != context.Instance && null != provider)
{
edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (null != edSvc)
{
AliasEditorForm form = new AliasEditorForm();
form.Value = value;
DialogResult r = edSvc.ShowDialog(form);
return (DialogResult.OK == r) ? form.Value : value;
}
}
return value;
}
}
It's been a long time since I've worked in WinForms and I never really did much dabbling with design-time stuff so forgive me if I've done something terribly stupid. If I didn't provide enough info, let me know. Thanks in advance!
EDIT: I notice it is not generating the code for the property in the form.Designer.cs file. Still not sure why...
EDIT: Changed the code for the type converter. I can now clear the property by backspacing. I still can't get the designer to generate the code to preserve the property value.
Dennis

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.

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.

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.

CommandBinding question. How to enable command button

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

Resources