MVVM-Light Toolkit -- How To Use PropertyChangedMessage - wpf

Can someone please post a working example of the PropertyChangedMessage being used? The description from the GalaSoft site states:
PropertyChangedMessage: Used to broadcast that a property changed in the sender. Fulfills the same purpose than the PropertyChanged event, but in a less tight way.
However, this doesn't seem to work:
private bool m_value = false;
public bool Value
{
get { return m_value ; }
set
{
m_value = value;
Messenger.Default.Send(new PropertyChangedMessage<bool>(m_value, true, "Value"));
}

This is related with the MVVM Light Messenger.
In your property definition yo use like this:
public string Name {
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
var oldValue = _name;
_name = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(Name, oldValue, value, true);
}
}
Then you can suscribe to any modification on the property using something like this:
Messenger.Default.Register<PropertyChangedMessage<string>>(
this, (e) => this.Name = e.NewValue
);
Look at this post and read about the MVVM Light Messenger
To broadcast:
Messenger.Default.Send<PropertyChangedMessage<string>>(oldValue, newValue, "PropertyName");

Daniel Castro commented on my question with the following question: "What do you expect from the code?"
The answer to this question prompted me to write this answer to my own question.
My expectations were, based on the badly written description for the PropertyChangedMessage class in the MVVM-Light documentation, that when I sent a PropertyChangedMessage then the RaisePropertyChanged method on the ViewModelBase class would get automatically called.
Apparently, however, it's the other way around. When you call RaisePropertyChanged, then that method has an overload where you can set a flag which determines whether or not a PropertyChangedMessage will be sent.
However, I want the functionality that I originally expected. I want to send off a new PropertyChangedMessage that automatically causes RaisePropertyChanged to be called. Here's how to do that.
Derive a new class from ViewModelBase with the following public NotifyPropertyChanged method which simply calls the protected RaisePropertyChanged method:
public abstract class MyViewModelBase : GalaSoft.MvvmLight.ViewModelBase
{
public void NotifyPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
}
Then derive a new class from PropertyChangedMessage which calls the new NotifyPropertyChanged method:
public class MyPropertyChangedMessage<T> : PropertyChangedMessage<T>
{
public MyPropertyChangedMessage(object sender, T oldValue, T newValue, string propertyName)
: base(sender, oldValue, newValue, propertyName)
{
var viewModel = sender as MyViewModelBase;
if (viewModel != null)
{
viewModel.NotifyPropertyChanged(propertyName);
}
}
public MyPropertyChangedMessage(object sender, object target, T oldValue, T newValue, string propertyName)
: base(sender, target, oldValue, newValue, propertyName)
{
var viewModel = sender as MyViewModelBase;
if (viewModel != null)
{
viewModel.NotifyPropertyChanged(propertyName);
}
}
}
I have tested this approach and verified that I can indeed write code like the following which causes the UI to update properly:
private bool m_value = false;
public bool Value
{
get { return m_value; }
set
{
Messenger.Default.Send(new MyPropertyChangedMessage<bool>(this, m_value, value, "Value"));
m_value = value;
}
}

Related

WPF How to set Validation.HasError property on controls manually?

I have a wpf window which fires validation when a user interacts with the control (got into the control and change the value which results in updated property) and upon property changed, validation fire and displayed as it should.
But I want to show all validation errors on the screen manually when a user clicks on the save button without traversing the controls, otherwise how it suppose to look if the user loads the screen and click on the save button.
Even if I create a method like IsValid() and call it upon clicking on the save button, it validates the whole form and tell me if it is valid or not but the red border around text boxes won't be showing(because Validation.HasError property is not being updated), which is I need because in a form of several
controls I need to notify the user about the exact control that is causing the problem.
You can get the sample project with the problem from this link
https://1drv.ms/u/s!AuCr-YEWkmWUiopdQ-eZ17IC7IAJnA
When we validate a property without traversing it. It won't update Validate.HasError property of the control. The solution to this was plain old simple NotifyPropertyChanged(propertyName).
I was using NotifyPropertyChanged when my property value changes(in the set) but without traversing it, it never fires.
So either we should call NotifyPropertyChanged when property's validation failed or we should call NotifyPropertyChanged(null) which notify all the control's to refresh their properties.
Adding full implementation of my INotifyDataErrorInfo
public class NotifyDataErrorInfoBase<T> : INotifyDataErrorInfo
{
public NotifyDataErrorInfoBase(T model)
{
Model = model;
}
public T Model { get; set; }
protected void SetValue<TValue>(string propertyName, TValue value)
{
typeof(T).GetProperty(propertyName).SetValue(Model, value);
ValidateProperty<TValue>(propertyName);
}
public bool ValidateAllProperties()
{
List<KeyValuePair<string, Type>> lstOfProperties = typeof(T).GetProperties().
Select(u => new KeyValuePair<string, Type>(u.Name, u.PropertyType)).ToList();
foreach (var property in lstOfProperties)
{
Type currentType = property.Value;
if (property.Value == typeof(string))
{
ValidateProperty<string>(property.Key);
}
else if (property.Value == typeof(int))
{
ValidateProperty<int>(property.Key);
}
}
return !HasErrors;
}
private void ValidateProperty<TValue>([CallerMemberName]string propertyName = null)
{
ClearErrors(propertyName);
var validationContext = new ValidationContext(Model) { MemberName = propertyName };
List<ValidationResult> results = new List<ValidationResult>();
var userName = GetValue<TValue>(propertyName);
Validator.TryValidateProperty(userName, validationContext, results);
if (results.Any())
{
foreach (var item in results)
{
AddError(propertyName, item.ErrorMessage);
}
}
}
protected TValue GetValue<TValue>(string propertyName)
{
return (TValue)typeof(T).GetProperty(propertyName).GetValue(Model);
}
Dictionary<string, List<string>> _lstOfErrors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => _lstOfErrors.Any();
public IEnumerable GetErrors(string propertyName)
{
return _lstOfErrors.ContainsKey(propertyName) ? _lstOfErrors[propertyName] : null;
}
protected void AddError(string propertyName, string errorMessage)
{
if (!_lstOfErrors.ContainsKey(propertyName))
{
_lstOfErrors[propertyName] = new List<string>();
}
_lstOfErrors[propertyName].Add(errorMessage);
}
protected void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
protected void ClearErrors(string propertyName)
{
if (_lstOfErrors.ContainsKey(propertyName))
_lstOfErrors.Remove(propertyName);
}
}

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.

Simple small INotifyPropertyChanged implementation

Say I have the following class:
public MainFormViewModel
{
public String StatusText {get; set;}
}
What is the easiest smallest way to get my changes to StatusText to reflect to any controls that bind to it?
Obviously I need to use INotifyPropertyChanged, but is there a cool way to do it that does not clutter up my code? need lots of files? etc?
Note: If this is a dupe then I am sorry. I searched and could not find any thing but using T4 code Generation which does not sound easy (to setup at least).
Unfortunately C# doesn't offer an easy mechanism to do that automatically... It has been suggested to create a new syntax like this :
public observable int Foo { get; set; }
But I doubt it will ever be included in the language...
A possible solution would to use an AOP framework like Postsharp, that way you just need to decorate your properties with an attribute:
public MainFormViewModel : INotifyPropertyChanged
{
[NotifyPropertyChanged]
public String StatusText {get; set;}
}
(haven't tried, but I'm pretty sure Postsharp allows you to do that kind of thing...)
UPDATE: OK, I managed to make it work. Note that it's a very crude implementation, using reflection on a private field to retrieve the delegate... It could certainly be improved, but I'll leave it to you ;)
[Serializable]
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
{
public override void OnSetValue(LocationInterceptionArgs args)
{
object oldValue = args.GetCurrentValue();
object newValue = args.Value;
base.OnSetValue(args);
if (args.Instance is INotifyPropertyChanged)
{
if (!Equals(oldValue, newValue))
{
RaisePropertyChanged(args.Instance, args.LocationName);
}
}
}
private void RaisePropertyChanged(object instance, string propertyName)
{
PropertyChangedEventHandler handler = GetPropertyChangedHandler(instance);
if (handler != null)
handler(instance, new PropertyChangedEventArgs(propertyName));
}
private PropertyChangedEventHandler GetPropertyChangedHandler(object instance)
{
Type type = instance.GetType().GetEvent("PropertyChanged").DeclaringType;
FieldInfo propertyChanged = type.GetField("PropertyChanged",
BindingFlags.Instance | BindingFlags.NonPublic);
if (propertyChanged != null)
return propertyChanged.GetValue(instance) as PropertyChangedEventHandler;
return null;
}
}
Note that your class still need to implement the INotifyPropertyChanged interface. You just don't have to explicitly raise the event in your property setters.
Have a go of this http://code.google.com/p/notifypropertyweaver/
All you need to do is implement INotifyPropertyChanged
So your code will look like
public MainFormViewModel : INotifyPropertyChanged
{
public String StatusText {get; set;}
#region INotifyPropertyChanged Implementation
}
The build task will compile this (you never see the below code)
public MainFormViewModel : INotifyPropertyChanged
{
public String StatusText {get; set;}
private string statusText;
public string StatusText
{
get { return statusText; }
set
{
if (value!= statusText)
{
statusText = value;
OnPropertyChanged("StatusText");
}
}
}
#region INotifyPropertyChanged Implementation
}
By leveraging EqualityComparer.Default you can reduce the property setter code down to one line as follows:
private int unitsInStock;
public int UnitsInStock
{
get { return unitsInStock; }
set { SetProperty(ref unitsInStock, value, "UnitsInStock"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T field, T value, string name)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
If your view models inherit from a base class that defines the SetProperty method and the PropertyChanged event, then the amount of code required to support INotifyPropertyChanged in your child view models becomes very minimal (1 line).
This approach is more verbose then the code weaving methods mentioned in other answers, but doesn't require you to modify your build process to accomplish it.
Be sure to take a look at the upcoming C# 5 Caller Info attributes as well as it looks like they will allow us to avoid using a magic string in the method without the performance cost of reflection.
UPDATE (March 1st, 2012):
The .NET 4.5 Beta is out, and with it, you can further refine the above code to this which removes the need for the string literal in the caller:
private int unitsInStock;
public int UnitsInStock
{
get { return unitsInStock; }
set
{
SetProperty(ref unitsInStock, value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
I have a blog post that talks about it in slightly more detail.
Ive always liked this method
private string m_myString;
public string MyString
{
get { return m_myString; }
set
{
if (m_myString != value)
{
m_myString = value;
NotifyPropertyChanged("MyString");
}
}
}
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
or for less code bloat
set
{
m_myString = value;
NotifyPropertyChanged("MyString");
}
I have a base class called "Model". It exposes a protected object called DataPoints, which is essentially a dictionary.
C#
public String StatusText {
get {
return (string)DataPoints["StatusText"];
}
set {
DataPoints["StatusText"] = value;
}
}
VB
public Property StatusText as String
get
return DataPoints!StatusText
end get
set
DataPoints!StatusText = value
end set
end property
When you set a value in the DataPoints dictionary it does the following:
Checks to make sure the value actually changed.
Saves the new value
Sets the IsDirty property to true.
Raises the Property Changed event for the named property as well as the IsDirty and IsValid properties.
Since it is a dictionary, it also makes loading objects from a database or XML file really easy.
Now you may think reading and writing to dictionary is expensive, but I've been doing a lot of performance testing and I haven't found any noticable impact from this in my WPF applications.
The PropertyChanged.Fody NuGet package does this.
https://github.com/Fody/PropertyChanged
Add the PropertyChanged.Fody package to your project.
Reference PropertyChanged in your model: using PropertyChanged;
Add the [ImplementPropertyChanged] attribute to your class.
All of the properties in the class will now magically implement INotifyPropertyChanged. Note - Fody works by modifying the emitted IL so you will never actually see the code in VS - it just magically does it.
Additional docs:
https://github.com/Fody/PropertyChanged/wiki/Attributes

Silverlight: INotifyPropertyChanged does not seem to work

I haven't implement this pattern for a while (and when I did it was in 2, as opposed to 3), and I have several examples that all seem straight forward, but I can't work out what I have done wrong in the below piece of code (The Items are not updated when the property event fires):
public partial class Index : Page
{
private IndexViewModel _vm;
public Index()
{
InitializeComponent();
_vm = new IndexViewModel(19);
this.TheDataGrid.ItemsSource = _vm.Rows;
}
public class IndexViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
this.PropertyChanged(this, e);
}
public SortableCollectionView Rows
{
get
{
return _rows;
}
set
{
if (_rows == value)
return;
_rows = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Rows"));
}
}
This does not refresh my datagrid... as a 'hack' I have had to pass the datagrid object into my viewmodel and bind it there:
public IndexViewModel(int containerModelId, DataGrid shouldNotNeed)
{
ContainerModelId = containerModelId;
LoadOperation<vwColumn> headings = _ttasContext.Load(_ttasContext.GetRecordColumnsQuery(ContainerModelId));
headings.Completed += (sender2, e2) =>
{
//load data
LoadOperation<vwDataValue> data = _ttasContext.Load(_ttasContext.GetRecordsQuery(ContainerModelId, null));
data.Completed += (sender3, e3) =>
{
Rows = FormatData(data, headings);
shouldNotNeed.ItemsSource = Rows;
};
};
}
Assigning _vm.Rows to TheDataGrid.ItemsSource does not wire any change notification callback automatically. Try this:
in xaml:
<... x:Name=TheDataGrid ItemsSource={Binding Rows}>
In code:
this.DataContext = _vm;
As Codism points out your main problem is you need to use binding to take advantage of an INotifyPropertyChanged. However I would recommend this implementation pattern:-
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name);
}
...
set
{
if (_rows != value)
{
_rows = value;
NotifyPropertyChanged("Rows");
}
}
Note that this approach minimises the impact on a an object instance whose properties are not being observed. In the original pattern you create instances of PropertyChangedEventArgs and calls to the event delegate going off regardless of whether anything is actually listening.
this.TheDataGrid.ItemsSource = _vm.Rows
When a collection is assigned as the ItemsSource of a DataGird , any changes made to the collection can be observed by the DataGrid if the source implements INotifyCollectionChanged.
From your code sample , I can't tell if the type SortableCollectionView implements INotifyCollectionChanged or inherits from ObservableCollection.
Implementing INotifyCollectionChanged would mean that you can't reset the backing field _rows for property Rows , you can clear items in the collection and add them as needed.
Hope this helps

PropertyChanged notification for calculated properties

I'm developing an application in Silverlight2 and trying to follow the Model-View-ViewModel pattern. I am binding the IsEnabled property on some controls to a boolean property on the ViewModel.
I'm running into problems when those properties are derived from other properties. Let's say I have a Save button that I only want to be enabled when it's possible to save (data has been loaded, and we're currently not busy doing stuff in the database).
So I have a couple of properties like this:
private bool m_DatabaseBusy;
public bool DatabaseBusy
{
get { return m_DatabaseBusy; }
set
{
if (m_DatabaseBusy != value)
{
m_DatabaseBusy = value;
OnPropertyChanged("DatabaseBusy");
}
}
}
private bool m_IsLoaded;
public bool IsLoaded
{
get { return m_IsLoaded; }
set
{
if (m_IsLoaded != value)
{
m_IsLoaded = value;
OnPropertyChanged("IsLoaded");
}
}
}
Now what I want to do is this:
public bool CanSave
{
get { return this.IsLoaded && !this.DatabaseBusy; }
}
But note the lack of property-changed notification.
So the question is: What is a clean way of exposing a single boolean property I can bind to, but is calculated instead of being explicitly set and provides notification so the UI can update correctly?
EDIT: Thanks for the help everyone - I got it going and had a go at making a custom attribute. I'm posting the source here in case anyone's interested. I'm sure it could be done in a cleaner way, so if you see any flaws, add a comment or an answer.
Basically what I did was made an interface that defined a list of key-value pairs to hold what properties depended on other properties:
public interface INotifyDependentPropertyChanged
{
// key,value = parent_property_name, child_property_name, where child depends on parent.
List<KeyValuePair<string, string>> DependentPropertyList{get;}
}
I then made the attribute to go on each property:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class NotifyDependsOnAttribute : Attribute
{
public string DependsOn { get; set; }
public NotifyDependsOnAttribute(string dependsOn)
{
this.DependsOn = dependsOn;
}
public static void BuildDependentPropertyList(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
var obj_interface = (obj as INotifyDependentPropertyChanged);
if (obj_interface == null)
{
throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name));
}
obj_interface.DependentPropertyList.Clear();
// Build the list of dependent properties.
foreach (var property in obj.GetType().GetProperties())
{
// Find all of our attributes (may be multiple).
var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false);
foreach (var attribute in attributeArray)
{
obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name));
}
}
}
}
The attribute itself only stores a single string. You can define multiple dependencies per property. The guts of the attribute is in the BuildDependentPropertyList static function. You have to call this in the constructor of your class. (Anyone know if there's a way to do this via a class/constructor attribute?) In my case all this is hidden away in a base class, so in the subclasses you just put the attributes on the properties. Then you modify your OnPropertyChanged equivalent to look for any dependencies. Here's my ViewModel base class as an example:
public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
// fire for dependent properties
foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname)))
{
PropertyChanged(this, new PropertyChangedEventArgs(p.Value));
}
}
}
private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>();
public List<KeyValuePair<string, string>> DependentPropertyList
{
get { return m_DependentPropertyList; }
}
public ViewModel()
{
NotifyDependsOnAttribute.BuildDependentPropertyList(this);
}
}
Finally, you set the attributes on the affected properties. I like this way because the derived property holds the properties it depends on, rather than the other way around.
[NotifyDependsOn("Session")]
[NotifyDependsOn("DatabaseBusy")]
public bool SaveEnabled
{
get { return !this.Session.IsLocked && !this.DatabaseBusy; }
}
The big caveat here is that it only works when the other properties are members of the current class. In the example above, if this.Session.IsLocked changes, the notification doesnt get through. The way I get around this is to subscribe to this.Session.NotifyPropertyChanged and fire PropertyChanged for "Session". (Yes, this would result in events firing where they didnt need to)
The traditional way to do this is to add an OnPropertyChanged call to each of the properties that might affect your calculated one, like this:
public bool IsLoaded
{
get { return m_IsLoaded; }
set
{
if (m_IsLoaded != value)
{
m_IsLoaded = value;
OnPropertyChanged("IsLoaded");
OnPropertyChanged("CanSave");
}
}
}
This can get a bit messy (if, for example, your calculation in CanSave changes).
One (cleaner? I don't know) way to get around this would be to override OnPropertyChanged and make the call there:
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "IsLoaded" /* || propertyName == etc */)
{
base.OnPropertyChanged("CanSave");
}
}
You need to add a notification for the CanSave property change everywhere one of the properties it depends changes:
OnPropertyChanged("DatabaseBusy");
OnPropertyChanged("CanSave");
And
OnPropertyChanged("IsEnabled");
OnPropertyChanged("CanSave");
How about this solution?
private bool _previousCanSave;
private void UpdateCanSave()
{
if (CanSave != _previousCanSave)
{
_previousCanSave = CanSave;
OnPropertyChanged("CanSave");
}
}
Then call UpdateCanSave() in the setters of IsLoaded and DatabaseBusy?
If you cannot modify the setters of IsLoaded and DatabaseBusy because they are in different classes, you could try calling UpdateCanSave() in the PropertyChanged event handler for the object defining IsLoaded and DatabaseBusy.

Resources