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.
Related
I have a Model called FieldModel. In ViewModel I am setting its properties through a json file parsing like this:
foreach (var field in innerClass.Item2.Properties)
{
FieldView fieldView = new FieldView(field);
fieldView.ClassName = classView.ClassName;
fieldView.IsAbstract = classView.IsAbstract;
FieldViewItems.Add(fieldView);
}
My question is: how to make the binding properly with the reload button in order to reload the content of json file when it's being modified ?
First implement a Command class, I prefer something like this :
public class GeneralCommand : ICommand
{
private Action ToBeExecutedAction;
private Func<bool> ExecutionValidatorFunc;
public GeneralCommand(Action toBeExecutedAction, Func<bool> executionValidatorFunc)
{
ToBeExecutedAction = toBeExecutedAction;
ExecutionValidatorFunc = executionValidatorFunc;
}
public bool CanExecute(object parameter)
{
return ExecutionValidatorFunc();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ToBeExecutedAction();
}
}
Now inside your ViewModel class, implement something like the following :
public class FieldModel : INotifyPropertyChanged
{
private GeneralCommand _generalCommand;
public FieldModel()
{
Action action = new Action(ChangeValue);
_generalCommand = new GeneralCommand(action, new Func<bool>(() => true));
}
public ICommand ReloadValues
{
get
{
return _generalCommand;
}
}
string _jsonText;
public string JsonText
{
get
{
return _jsonText;
}
}
private void ChangeValue()
{
//Change JsonText here
//Then raise event change to be updated
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TextJson"));//Here fill property name
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then from the Xaml bind your Reload button into command property ReloadValues inside your ViewModel object, and bind the JsonText property with a WPF control for example a Textbox.
Hope this is useful.
I have Model Class Employee:
public class Employee
{
public string Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
and View Model class EmployeeViewModel which contains an Employee Object
public class EmployeeViewModel : INotifyPropertyChanged
{
public EmployeeViewModel()
{
}
private Employee currentEmployee;
public Employee CurrentEmployee
{
get { return this.currentEmployee; }
set
{
this.currentEmployee = value;
this.NotifyPropertyChanged("CurrentEmployee");
}
}
//Some code .....
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Now the View (WPF) will use Employee object in the View Model as ItemSource to display Employee data
Now the question is: I have Update button on the view and when I change the Employee properties in the view (via text boxes) I want to update the model (so afterward i can update the database), how to update this model from the view.
As I checked there something weird with your Model Class. It is the one that should implement INotifyPropertyChanged then create backing fields for each property something like this.
Model Class
public class Employee:INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name= value;
OnPropertyChanged("Name");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
**ViewModel Class**
class EmployeeViewModel
{
private IList<Employee> _employees;
public EmployeeViewModel()
{
_employees= new List<Employee>
{
new Employee{ID=1, Name ="Emp1"},
new Employee{ID=2, Name="Emp2"}
};
}
public IList<Employee> Employees
{
get
{
return _employees;
}
set
{
_employees= value;
}
}
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
#endregion
}
}
You may put your logic inside OnPropertyChanged event. This method is always called whenever you made changes on the UI
If you are using ObservableCollection modify your List by searching the item based on ID then if found do the modification of values. Whatever changes you have made will automatically affect the UI items if they are bound to your ObservableCollection then use the modified collection to update your DB records
I am using WPF and MVVM for a project. I have a view with a GridView control. User can Insert/Update/Delete In Grid View. when any of the action happen changes reflect in ViewModel. This part is working Ok. But when I want to save the changes in Database I need to loop through each Item in ItemSource one by one. which takes the extra time to complete. I want to process only those Items which are changes.
To accomplish this , I add a boolean property in my Model to indicate whether the Item is changed or note. But problem is that I can not see any way to set this boolean property whenever any other property is changed.
Can any body help me how to do it?
EDIT
I have a SelectedItem Property , and I am assuming that whenever an Item is selected in GridView , User will update or insert the row. so on SelectedItem property I have set boolean property of SelectedItem to True. and while looping to save records I am saving all those records who have True in their boolean property. I know its not the perfact way, but right now I do not have any other way to do it. Your thoughts?
You could subscribe to the PropertyChanged event on your Model and set the Flag to True.
But keep in mind that you have to set the Flag to false after you loaded the data from the database, because the initialization of the model will also call the propertychanged event.
Example for class with IsDirty-Flag:
public class Sample : INotifyPropertyChanged
{
private int id;
private string name;
private bool isDirty;
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return id; }
set
{
if(id != value)
{
id = value;
RaisePropertyChanged("Id");
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
RaisePropertyChanged("Name");
}
}
}
public bool IsDirty
{
get { return isDirty; }
set
{
if (isDirty != value)
{
isDirty = value;
RaisePropertyChanged("IsDirty");
}
}
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (propertyName != "IsDirty")
{
IsDirty = true;
}
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
if you are using an ObservableCollection, you can also add an eventhandler to track the rows that are newly added or deleted
If you use MVVM you should have implementation of INotifyPropertyChanged. And you can add some logic to set up yours boolean property in OnPropertyChanged handler.
If you are willing to take a dependency on a build time tool you can do this with ILWeaving.
So if you combine Fody with the PropertyChanged addin then IsDirty functionality is supported out of the box.
Then Martins example can be simplified to this
public class Sample : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
public string Name { get; set; }
public bool IsChanged { get; set; }
}
Note the use if IsChanged instead of IsDirty.
And then this will exist in the compiled assembly
public class Sample : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int id;
public int Id
{
get { return id; }
set
{
if (id != value)
{
id = value;
IsChanged = true;
OnPropertyChanged("Id");
}
}
}
bool isChanged;
public bool IsChanged
{
get { return isChanged; }
set
{
if (isChanged != value)
{
isChanged = value;
OnPropertyChanged("IsChanged");
}
}
}
string name;
public string Name
{
get { return name; }
set
{
if (!string.Equals(name, value, StringComparison.Ordinal))
{
name = value;
IsChanged = true;
OnPropertyChanged("Name");
}
}
}
}
I am trying to figure out on how to trigger the PropertyChangedEvent when the middle layer of my binding changes. I will start with an example here:
public class MainViewModel :NotificationObject // Main DataContext
{
public SubViewModel SubVM{get; {_subVM = value; RaisePropertyChanged("SubVM");}} // observable property
public void DoChangeSubVM()
{
SubVM = new SubViewModel(); // doing this will not update the egControl
}
}
public class SubViewModel : NotificationObject
{
public Sub2ViewModel Sub2VM {get; set{_sub2VM = value; RaisePropertyChanged("Sub2VM");}} // observable property
}
public class Sub2ViewModel : NotificationObject
{
public int SomeProp {get; set {_someProp = value; RaisePropertyChanged("SomeProp");} // observable property
}
in the XAML:
<EgControl name="egControl" Content={Binding SubVM.Sub2VM.SomeProp} />
Now if I change the Sub2VM Property the egControl doesn't automagically get updated with the SomeProp value of the new Sub2VM instance. How does someone go about achieving this, with out manually having to raise all the Sub2ViewModel propertychanged events from Sub2VM property setter?
Using: Prism .NET 4.0
How does someone go about achieving this, with out manually having to raise all the Sub2ViewModel propertychanged events from Sub2VM property setter?
Answer
You have several possibilities:
Raise all property changed events in the setter, which you said you wanted to avoid. But it's a valid strategy to consider. If you know which properties are dependant on the results of another, then they will need to raise property changed in the setter for many properties.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
Raise all property changed events in the method, after the new ViewModel has been constructed.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
RaisePropertyChanged("FirstName");
}
}
public void UpdateFirstName(string firstName)
{
_FirstName = firstName;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
Use the setters to set some properties, thus triggering the already present property changed event.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
RaisePropertyChanged("FirstName");
}
}
public Person ClonePerson(Person rootPerson)
{
Person clone = new Person()
{
FirstName = rootPerson.FirstName;
LastName = rootPerson.LastName;
}
return clone;
}
}
Make a method that raises all property changed events, and call it in edge cases where you need to raise multiple changed events.
public class myViewModel
{
private string _FirstName
public string FirstName
{
get { return_FirstName };
set
{
_FirstName = value;
this.RaiseAllPropertyChanges();
}
}
public void RaiseAllPropertyChanges()
{
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
The end result is this: For any bound UI element to know that it must update, the property changed event for that property must be raised.
Okay, not sure about Prism, but generally speaking all three classes should be implementing property change notification. The simplist way to do so is with INotifyPropertyChanged. So SubViewModel should be more like:
public class SubViewModel : INotifyPropertyChanged
{
private Sub2ViewModel sub2vm;
public Sub2ViewModel Sub2VM
{
get
{
return sub2vm;
}
set
{
sub2vm = value;
OnPropertyChanged("Sub2VM");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Without property change notification, the UI doesn't know when to udpate a bound property.
One way to go about it is to create a constructor for both the SubViewModel and Sub2ViewModel that initializes all the properties to some default value. This will ensure that your properties are initialized and give you the ability to set the initial values.
I've a ViewModel class like this in a Prism / WPF project.
public class ContentViewModel : ViewModelBase, IContentViewModel
{
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
SaveCommand = new DelegateCommand(Save, CanSave);
}
public Person Person { get; set; }
public DelegateCommand SaveCommand { get; set; }
private void Save()
{
// Save actions here...
}
private bool CanSave()
{
return Person.Error == null;
}
}
The person type used in the above ViewModel is defined as follows:
public class Person : INotifyPropertyChanged, IDataErrorInfo
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
// other properties are implemented in the same way as above...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _error;
public string Error
{
get
{
return _error;
}
}
public string this[string columnName]
{
get
{
_error = null;
switch (columnName)
{
// logic here to validate columns...
}
return _error;
}
}
}
An instance of ContentViewModel is set as DataContext of a View. Inside the View I've used binding to Person as follows:
<TextBox Text="{Binding Person.FirstName, ValidatesOnDataErrors=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
When I make changes to TextBox which is binded to Person's properties like FirstName and click Save I could see the changes in ViewModel command handler. But if any of these properties fail in validation CanSave is never executed and button never gets disabled.
How do I disable a button based on DelegateCommand's CanExecute action handler in the above scenario?
In the constructor of ContentViewModel add this line
public ContentViewModel(IPersonService personService)
{
//GetPerson
Person.PropertyChanged +=person_PropertyChanged;
}
And write an method to handle that event in which you call either CommandManager.InvalidateRequerySuggested() or SaveCommand.RaiseCanExecuteChanged()
private void person_PropertyChanged(object sender, EventArgs args)
{
CommandManager.InvalidateRequerySuggested();
//SaveCommand.RaiseCanExecuteChanged()
}
Hope this works. :-)
try this with all the properties that can change error:
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("Error");
}
}
Alternatively
switch (columnName)
{
// logic here to validate columns...
OnPropertyChanged("Error");
}
The problem you are having is that the OnPropertyChanged is not being called when the error changes.
The next step is to subscribe to the person's propertychanged event when its created, and create a handler that checks for the propertychanged and then changes the boolean variable that the command uses.
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
Person.PropertyChanged+= PersonPropertyChangedHandler;
SaveCommand = new DelegateCommand(Save, personHasError);
}
bool personHasError = false;
void PersonPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Error")
{
if(Person.Error == null)
personHasError = true;
else
personHasError = false;
}
}
Hope this works. I built this by hand and didn't check it so let me know if its buggy or whatever and ill correct it
In the nutshell - you should call yourDelegateCommand.RaiseCanExecuteChanged() when you think that your CanExecute() return value can be changed.
In your example, you should notify through INotifyPropertyChanged interface that your Person.Error property is changed, subscribes to Person.PropertyChanged event in your ContentViewModel class and call SaveCommand.RaiseCanExecuteChanged() each time when your Person.Error is changed. Please be careful - in your scenario Person.Error isn't recalculated automatically when, for example, Person.FirstName is changed - you should do this manually.
UPDATED:
public class ContentViewModel : ViewModelBase, IContentViewModel
{
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
Person.PropertyChanged += Person_PropertyChanged;
SaveCommand = new DelegateCommand(Save, CanSave);
}
private void PersonPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
private void Save()
{
// Save actions here...
}
private bool CanSave()
{
return IsErrorPresented(Person);
}
private bool IsErrorPresented(object o)
{
if (!(o is IDataErrorInfo))
return false;
var propNames = o.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => p.Name);
var o2 = (o as IDataErrorInfo);
var errors = propNames.Select(p => o2[p])
.Where(p => !String.IsNullOrEmpty(p))
.ToList();
ValidationSummary.ErrorMessages = errors;
return errors.Count > 0;
}
}
<TextBox Text="{Binding Person.FirstName,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
If you will also specify PropertyChanged as UpdateSourceTrigger, your save button will be updated during your typing..