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; }
}
}
Related
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.
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 ?
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
I've just discovered that WPF Markup extension instances are reused in control templates. So each copy of the control template gets the same set of markup extensions.
This doesn't work if you want the extension to maintain some state per control it is attached to. Any idea how to solve this.
Don't store state in the Markup extension. Store it another way. For example.
public abstract class DynamicMarkupExtension : MarkupExtension
{
public class State
{
public object TargetObject { get; set; }
public object TargetProperty { get; set; }
public void UpdateValue(object value)
{
if (TargetObject != null)
{
if (TargetProperty is DependencyProperty)
{
DependencyObject obj = TargetObject as DependencyObject;
DependencyProperty prop = TargetProperty as DependencyProperty;
Action updateAction = () => obj.SetValue(prop, value);
// Check whether the target object can be accessed from the
// current thread, and use Dispatcher.Invoke if it can't
if (obj.CheckAccess())
updateAction();
else
obj.Dispatcher.Invoke(updateAction);
}
else // TargetProperty is PropertyInfo
{
PropertyInfo prop = TargetProperty as PropertyInfo;
prop.SetValue(TargetObject, value, null);
}
}
}
}
public sealed override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
State state = new State();
if (target != null)
{
state.TargetObject = target.TargetObject;
state.TargetProperty = target.TargetProperty;
return ProvideValueInternal(serviceProvider, state);
}
else
{
return null;
}
}
protected abstract object ProvideValueInternal(IServiceProvider serviceProvider, State state);
}
is a base class for handling the type of problem where you need to update the property the markup
extension is attached to at run time. For example a markup extension for binding to ISubject as
<TextBox Text="{Markup:Subscription Path=Excenter, ErrorsPath=Errors}"/>
using the SubscriptionExtension as below. I had had trouble with the code when I used it
within templates but I fixed it so the MarkupExtension did not store state in itself
using ReactiveUI.Ext;
using ReactiveUI.Subjects;
using ReactiveUI.Utils;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : DynamicMarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
[ConstructorArgument("errorsPath")]
public PropertyPath ErrorsPath { get; set; }
public SubscriptionExtension() { }
Maybe<Exception> currentErrorState = Maybe.None<Exception>();
public SubscriptionExtension(PropertyPath path, PropertyPath errorsPath)
{
Path = path;
ErrorsPath = errorsPath;
}
class Proxy : ReactiveObject, IDataErrorInfo, IDisposable
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
public string Error
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public string this[string columnName]
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public IObservable<Maybe<Exception>> Errors { get; set; }
public Maybe<Exception> currentError = Maybe.None<Exception>();
private CompositeDisposable Subscriptions = new CompositeDisposable();
public Proxy(IObservable<Maybe<Exception>> errors)
{
Errors = errors;
var subscription = errors.Subscribe(e => currentError = e);
Subscriptions.Add(subscription);
}
public void Dispose()
{
Subscriptions.Dispose();
}
}
protected override object ProvideValueInternal(IServiceProvider serviceProvider, DynamicMarkupExtension.State state )
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
DependencyPropertyChangedEventHandler myd = delegate(object sender, DependencyPropertyChangedEventArgs e){
state.UpdateValue(MakeBinding(serviceProvider, frameworkElement));
};
frameworkElement.DataContextChanged += myd;
return MakeBinding(serviceProvider, frameworkElement);
}
private object MakeBinding(IServiceProvider serviceProvider, FrameworkElement frameworkElement)
{
var dataContext = frameworkElement.DataContext;
if (dataContext is String)
{
return dataContext;
}
ISubject<string> subject = Lens.Empty<string>().Subject;
IObservable<Maybe<Exception>> errors = Observable.Empty<Maybe<Exception>>();
Binding binding;
Proxy proxy = new Proxy(errors);
bool madeit = false;
if (dataContext != null)
{
subject = GetProperty<ISubject<string>>(dataContext, Path);
if (subject != null)
{
errors = GetProperty<IObservable<Maybe<Exception>>>
(dataContext
, ErrorsPath) ?? Observable.Empty<Maybe<Exception>>();
proxy = new Proxy(errors);
}
madeit = true;
}
if(!madeit)
{
subject = new BehaviorSubject<string>("Binding Error");
}
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.TwoWayBindTo(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e, v) => subscription.Dispose();
binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value"),
ValidatesOnDataErrors = true
};
return binding.ProvideValue(serviceProvider);
}
private static T GetProperty<T>(object context, PropertyPath propPath)
where T : class
{
if (propPath==null)
{
return null;
}
try
{
object propValue = propPath.Path
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue as T;
}
catch (NullReferenceException e)
{
throw new MemberAccessException(propPath.Path + " is not available on " + context.GetType(),e);
}
}
}
}
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.