Validation firing too early - wpf

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.

Related

How to Bind Checked / Selected Items From CheckListBox in WPF by using MVVM Model (I am using "WPFToolkit.Extended.dll" to get CheckListBox Control)

CheckList Box from WPFToolKit. Below is XAML code (MainWindow.xaml)
<xctk:CheckListBox x:Name="SiteCheckList" Margin="0,0,512,0" Height="100" Width="150"
ItemsSource="{Binding SiteList}"
DisplayMemberPath="SiteName"
CheckedMemberPath="IsChecked">
</xctk:CheckListBox>
Below Properties added in Model Class. I would like to get Checked Items from CheckListBox to my string List (Model.cs).
This string List I will be using for further in project logic.
private string _SiteName;
public string SiteName
{
get { return _SiteName; }
set { _SiteName = value; }
}
private List<string> _SelectedSiteList;
public List<string> SelectedSiteList
{
get { return _SelectedSiteList; }
set
{
_SelectedSiteList = value;
}
}
View Model (ViewModel.cs)
class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Model> _SiteList;
public ObservableCollection<DataModel> SiteList
{
get { return _SiteList; }
set { _SiteList = value; }
}
public ViewModel()
{
SiteList = new ObservableCollection<Model>();
PoppulateSiteNames();
}
private void PoppulateSiteNames()
{
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
keyValuePairs = Files.ReadIni_KeyValue("SiteSection");
foreach (string Key in keyValuePairs.Keys)
{
keyValuePairs.TryGetValue(Key, out string LogTable);
SiteList.Add(new Model() { SiteName = LogTable });
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
Here I would like to get list of Checked / Selected Items from UI. If I don't want to write any code in MainWindow.cs i.e. CheckedChanged event then How I can do it by using Binding method ?
Updated Model Class with IsChecked Boolean Property.
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set { _IsChecked = value; }
}
Then ViewModel Updated with Below Function to Populate SiteList
private void PoppulateSiteNames()
{
Dictionary<string, string> keyValuePairs = Files.ReadIni_KeyValue(Vars.MSSQL_Section);
string [] Sites = keyValuePairs.Values.ToArray();
for (int i = 0; i < Sites.Length; i++)
{
SiteList.Add(new HistoricalDataModel { SiteName = Sites[i] });
}
}
Now, Finally I got CheckedItems in below Variable using LINQ
var checkedSites = from site in SiteList
where (site.IsChecked == true)
select new { site.SiteName };
Thank you all of you for responding my question and triggering me to think more.

Validation on WPF Options

I have been sitting on the internet now for 3hours with not much help.
I am trying to implement validation on my UI with the following requirements using the MVVM principle.
Currently by using DataAnnotations on my model:
Example:
private string _name;
[Required(ErrorMessage = "Name must be filled")]
public string Name
{
get { return _name; }
set { this.Update(x => x.Name, () => _name= value, _name, value); }
}
1) I want the validation only to be done when I click on a button (Submit)
2) If I have lets say 5 validations on the UI I want to display them in a list also.
I had a look at several ways and not sure which to use for best practices that suits my 2 requirements the best:
IDataInfoError?
INotifyDataErrorInfo?
DataAnnotations? (Current implementation)
Anybody that can point me in the right direction, tips anything?
You should read the following blog post: https://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/. It provides an example of you could implement validation using data annotations and the INotifyDataErrorInfo interface:
public class ViewModel : INotifyDataErrorInfo
{
private readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
private readonly Model _user = new Model();
public string Username
{
get { return _user.Username; }
set
{
_user.Username = value;
ValidateModelProperty(value, "Username");
}
}
public string Name
{
get { return _user.Name; }
set
{
_user.Name = value;
ValidateModelProperty(value, "Name");
}
}
protected void ValidateModelProperty(object value, string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
ICollection<ValidationResult> validationResults = new List<ValidationResult>();
ValidationContext validationContext =
new ValidationContext(_user, null, null) { MemberName = propertyName };
if (!Validator.TryValidateProperty(value, validationContext, validationResults))
{
_validationErrors.Add(propertyName, new List<string>());
foreach (ValidationResult validationResult in validationResults)
{
_validationErrors[propertyName].Add(validationResult.ErrorMessage);
}
}
RaiseErrorsChanged(propertyName);
}
protected void ValidateModel()
{
_validationErrors.Clear();
ICollection<ValidationResult> validationResults = new List<ValidationResult>();
ValidationContext validationContext = new ValidationContext(_user, null, null);
if (!Validator.TryValidateObject(_user, validationContext, validationResults, true))
{
foreach (ValidationResult validationResult in validationResults)
{
string property = validationResult.MemberNames.ElementAt(0);
if (_validationErrors.ContainsKey(property))
{
_validationErrors[property].Add(validationResult.ErrorMessage);
}
else
{
_validationErrors.Add(property, new List<string> { validationResult.ErrorMessage });
}
}
}
/* Raise the ErrorsChanged for all properties explicitly */
RaiseErrorsChanged("Username");
RaiseErrorsChanged("Name");
}
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; }
}
}

Windows Phone 8 - exception while Submit Changes

I have a problem with local database in Windows Phone 8 app.
It's my DatabaseManager and Models
public class DatabaseManager : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string DBConnectionString = "Data Source=isostore:/LocalMainDatabase.sdf";
// Pass the connection string to the base class.
public DatabaseManager(string connectionString) : base(connectionString) { }
// Specify a single table for the to-do items.
//public Table<GroupDatabaseModel> GroupDbModel;
public Table<GroupDatabaseModel> GroupDbModel;
}
[Table]
public class GroupDatabaseModel : INotifyPropertyChanged, INotifyPropertyChanging
{
private int id;
//private string name { get; set; }
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int Id
{
get
{
return id;
}
set
{
if (id != value)
{
NotifyPropertyChanging("Id");
id = value;
NotifyPropertyChanged("Id");
}
}
}
/*[Column(IsPrimaryKey = false, IsDbGenerated = true, DbType = "NVarChar(30) NOT NULL", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public string Name
{
get
{
return name;
}
set
{
if (name != value)
{
NotifyPropertyChanging("Name");
name = value;
NotifyPropertyChanged("Name");
}
}
}*/
public event PropertyChangingEventHandler PropertyChanging;
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
In my ViewModel I create database
DatabaseManager db;
using (db = new DatabaseManager("isostore:/LocalMainDatabase.sdf"))
{
if (db.DatabaseExists() == false)
{
// Create the database.
db.CreateDatabase();
GroupDatabaseModel k1 = new GroupDatabaseModel { Id = 11 };
db.GroupDbModel.InsertOnSubmit(k1);
try
{
db.SubmitChanges();
}
catch (Exception exx)
{
// Console.WriteLine(exx);
// Make some adjustments.
// ...
// Try again.
db.SubmitChanges();
}
// Define query to gather all of the to-do items.
var toDoItemsInDB = from GroupDatabaseModel todo in db.GroupDbModel select todo;
Unfortunately I get exception while submit changes :
"An exception of type 'System.NotSupportedException' occurred in
System.Data.Linq.ni.dll but was not handled in user code
Additional information: Insertion of a row consisting only of database
generated values is not supported in this data provider."
What is wrong ?

Again, ObservableCollection doesnt Update item

Thats my first project using MVVM , MVVM light.
I have a listbox, that gets refreshed from the PersonList Observable collection, adding and removing refresh it normal. the problem is when editing an item.
I looked for all the solutions for this problem, nothing worked, which make me think that I missed something.
so here is the code :
public class AdminViewModel : ApplicationPartBaseViewModel
{
private ObservableCollection<Person> personList;
public AdminViewModel()
{
this.context = new Entities();
this.SavePersonCommand = new RelayCommand(() => this.SavePerson ());
this.PersonList = new ObservableCollection<Peson>(context.Person.OrderBy(o => o.PersonName).ToList());
}
public ObservableCollection<Person> PersonList
{
get
{
return personList;
}
set
{
this.personList = value;
RaisePropertyChanged("PersonList");
}
}
private void SavePerson()
{
//Add and update code here
this.context.SaveChanges();
RaisePropertyChanged("PersonList");
}
}
Person Class is Autogenerated template from the DataModel edmx
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
public partial class Person
{
#region Primitive Properties
public virtual int PersonId
{
get;
set;
}
public virtual string PersonName
{
get;
set;
}
public virtual Nullable<int> PersonAge
{
get;
set;
}
#endregion
#region Navigation Properties
public virtual ICollection<Humans> Humans
{
get
{
if (_human == null)
{
var newCollection = new FixupCollection<Human>();
newCollection.CollectionChanged += FixupHuman;
_human = newCollection;
}
return _human;
}
set
{
if (!ReferenceEquals(_human, value))
{
var previousValue = _human as FixupCollection<Human>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupHuman;
}
_human = value;
var newValue = value as FixupCollection<Human>;
if (newValue != null)
{
newValue.CollectionChanged += FixupAssets;
}
}
}
}
private ICollection<Human> _human;
#endregion
#region Association Fixup
private void FixupHuman(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Human item in e.NewItems)
{
if (!item.Person.Contains(this))
{
item.Person.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Human item in e.OldItems)
{
if (item.Person.Contains(this))
{
item.Person.Remove(this);
}
}
}
}
#endregion
}
I thought that MVVM light update the item when I call RaisePropertyChanged.
I am so confused.
Thanks in advance.
First option is try to get your auto-generated class to implement INPC if you can. Have a look at Fody.PropertyChanged
If that's not possible, since it does have it's properties as "virtual", we can over-ride them in a derived class such as
public class ObservablePerson : Person, INotifyPropertyChanged {
public override int PersonId {
get {
return base.PersonId;
}
set {
base.PersonId = value;
OnPropertyChanged();
}
}
public override string PersonName {
get {
return base.PersonName;
}
set {
base.PersonName = value;
OnPropertyChanged();
}
}
public override int? PersonAge {
get {
return base.PersonAge;
}
set {
base.PersonAge = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in your AdminViewModel work with objects of type ObservablePerson than Person

How to create calculated Fields in Partial Classes - WPF

I am trying to use the calculated columns to display in my grid.
I have a partial class automatically generated by EF code generator with three properties: and i am trying to creating another partial class and add calculated field there for e.g.
Public partial class Employee
{
public decimal? totalSalary
{
get
{
return salary*wagerate+bonus;
}
}
}
It works fine for the first time but does not work when the salary/bonus/hours are changed. I am using these fields inside a grid
Here is my code generated by EF entity generator
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;
namespace Employees.Contract
{
[DataContract(IsReference = true)]
[KnownType(typeof(Department))]
[KnownType(typeof(PropertyType))]
public partial class Employee: IObjectWithChangeTracker, INotifyPropertyChanged,IDataErrorInfo
{
[NonSerialized]
private CLOS.Contract.Validation.DataErrorInfoSupport dataErrorInfoSupport;
public Employee()
{
dataErrorInfoSupport = new CLOS.Contract.Validation.DataErrorInfoSupport(this);
Init();
}
partial void Init();
string IDataErrorInfo.Error { get { return dataErrorInfoSupport.Error; } }
string IDataErrorInfo.this[string memberName] { get { return dataErrorInfoSupport[memberName]; } }
#region Primitive Properties
[DataMember]
public Nullable<decimal> Salary
{
get { return _salary; }
set
{
if (_salary != value)
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
private Nullable<decimal> _salary;
[DataMember]
public Nullable<decimal> WageRate
{
get { return _wageRate; }
set
{
if (_wageRate != value)
{
_wageRate = value;
OnPropertyChanged("WageRate");
}
}
}
private Nullable<decimal> _wageRate;
[DataMember]
public Nullable<decimal> Bonus
{
get { return _bonus; }
set
{
if (_bonus != value)
{
_bonus = value;
OnPropertyChanged("Bonus");
}
}
}
private Nullable<decimal> _bonus;
#endregion
#region Navigation Properties
[DataMember]
public Department Department
{
get { return _department; }
set
{
if (!ReferenceEquals(_department, value))
{
var previousValue = _department;
_department = value;
OnNavigationPropertyChanged("Department");
}
}
}
private Borrower _department;
[DataMember]
public PropertyType PropertyType
{
get { return _propertyType; }
set
{
if (!ReferenceEquals(_propertyType, value))
{
var previousValue = _propertyType;
_propertyType = value;
OnNavigationPropertyChanged("PropertyType");
}
}
}
private PropertyType _propertyType;
#endregion
#region ChangeTracking
protected virtual void OnPropertyChanged(String propertyName)
{
if (ChangeTracker.State != ObjectState.Added && ChangeTracker.State != ObjectState.Deleted)
{
ChangeTracker.State = ObjectState.Modified;
}
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnNavigationPropertyChanged(String propertyName)
{
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged{ add { _propertyChanged += value; } remove { _propertyChanged -= value; } }
private event PropertyChangedEventHandler _propertyChanged;
private ObjectChangeTracker _changeTracker;
[DataMember]
public ObjectChangeTracker ChangeTracker
{
get
{
if (_changeTracker == null)
{
_changeTracker = new ObjectChangeTracker();
_changeTracker.ObjectStateChanging += HandleObjectStateChanging;
}
return _changeTracker;
}
set
{
if(_changeTracker != null)
{
_changeTracker.ObjectStateChanging -= HandleObjectStateChanging;
}
_changeTracker = value;
if(_changeTracker != null)
{
_changeTracker.ObjectStateChanging += HandleObjectStateChanging;
}
}
}
private void HandleObjectStateChanging(object sender, ObjectStateChangingEventArgs e)
{
if (e.NewState == ObjectState.Deleted)
{
ClearNavigationProperties();
}
}
protected bool IsDeserializing { get; private set; }
[OnDeserializing]
public void OnDeserializingMethod(StreamingContext context)
{
IsDeserializing = true;
}
[OnDeserialized]
public void OnDeserializedMethod(StreamingContext context)
{
dataErrorInfoSupport = new CLOS.Contract.Validation.DataErrorInfoSupport(this);
IsDeserializing = false;
ChangeTracker.ChangeTrackingEnabled = true;
}
protected virtual void ClearNavigationProperties()
{
Department = null;
PropertyType = null;
}
#endregion
}
}
It also works if i put OnPropertyChanged("Salary") in Hours,Wage,Overtime Property in EF Generated class (which is not a good idea) because if the class gets regenerated , my code will be wiped out
Any help is appreciated. (Sorry for the formatting , this is my first question)
Thanks
In the partial class use the partial Init method to subscribe to the PropertyChanged event and when either the salary, wagerate or bonus property changes notify clients of the change of the totalSalary.
This way you do not need to modify the generated code. (that is why the Init method is partial).
public partial class Employee
{
partial void Init()
{
_propertyChanged += PropertyChangedHandler;
}
void PropertyChangedHandler(object sender, PropertyChangedEventArgs args)
{
if(args.PropertyName == "salary" ||
args.PropertyName == "wagerate" ||
args.PropertyName == "bonus")
{
OnPropertyChanged("totalSalary");
}
}
public decimal? totalSalary
{
get
{
return salary * wagerate + bonus;
}
}
}
this is why MVVM is a popular design pattern, if you wrap your Employee (a Model) in a new class (a ViewModel), it will be easier to customise before you hand it to the grid (the View).
However, a hacky way to get your current code working would be to attach to the current PropertyChanged event in your partial class and call OnPropertyChanged("Salary") if the current property name matches one of the dependent properties (watch out for recursion!)

Resources