I have a ViewModel with an Observable Collection Property
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get
{
return currentSensorAreasList;
}
set
{
if (currentSensorAreasList != value)
{
currentSensorAreasList = value;
OnPropertyChanged(PROPERTY_NAME_CURRENT_SENSOR_AREAS_LIST);
}
}
}
Then in my xaml i have a binding
ItemsSource="{Binding CurrentSensorAreasList}">
This Observable Collection is updated trought a method that can be call in the viewModel constructor or when a collectionchanged handler from another list gets called.
I just clear the list and then add a fewer new items. While debugging i see all my new items updated on the list. But the UI does not get updated.
When i regenerate the viewModel and then this update method gets call in the constructor the list gets updated in the UI.
Any ideas?? I don't know if the problem comes when i call the method from a handler.....
UPDATE #1
As requested i'm going the code when I update the list
I have tested two ways to do this update
private void UpdateList1()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList.Clear();
CurrentSensorAreasList.AddRange(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList));
//AddRange is an extension method.
}
}
private void UpdateList2()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList = new ObservableCollection<GeographicArea>(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList))
}
}
Both cases works when i call it from the constructor. Then I have Other Lists where the Areas changes, and i get notified via CollectionChanged Handlers.
private void globalAreaManagerList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (AreaManager newItem in e.NewItems)
{
newItem.AreaList.CollectionChanged += AreaList_CollectionChanged;
}
}
if (e.OldItems != null)
{
foreach (AreaManager oldItem in e.OldItems)
{
oldItem.AreaList.CollectionChanged -= AreaList_CollectionChanged;
}
}
UpdateList();
}
private void AreaList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateList();
}
So when i use UpdateList1 seems to work more times but suddenly the Binding is broken and then this update does not show in the UI.
If you wish change exactly an instance of collection I recomend using DependecyProperty for that case.
Here is:
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get { return (ObservableCollection<GeographicArea>)GetValue(CurrentSensorAreasListProperty); }
set { SetValue(CurrentSensorAreasListProperty, value); }
}
public static readonly DependencyProperty CurrentSensorAreasListProperty =
DependencyProperty.Register("CurrentSensorAreasList", typeof(ObservableCollection<GeographicArea>), typeof(ownerclass));
Where ownerclass - a name of class where you put this property.
But the better way is create only one instance of ObservaleCollection and then just change its items. I mean Add, Remove, and Clear methods.
Related
I have a DataGrid nested in a DockPanel. The DockPanel serves as a data context:
DockPanel1.DataContext = GetData();
The GetData() method returns an ObservableCollection.
The ObservableCollection can be modified in the DataGrid as well as in a few textboxes nested in the DockPanel. I also navigate through the collection using a DataView.
I'd like to detect if the collection has been modified and warn a user when he/she tries to close the application without saving data.
Is there any built-in mechanism that I could use (a kind of "IsDirty" flag on the collection or on the view)? If not, I guess I will have to monitor all the controls and detect any changes manually.
Thanks,
Leszek
In order to detect changes in the collection itself, you would have to attach a CollectionChanged handler. If you also need to detect changes in the objects contained in the collection, you would have to attach PropertyChanged handlers to every object (provided that the objects implements INotifyPropertyChanged).
An implementation would basically look like this:
var collection = GetData();
collection.CollectionChanged += OnCollectionChanged;
...
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddPropertyChanged(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemovePropertyChanged(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
RemovePropertyChanged(e.OldItems);
AddPropertyChanged(e.NewItems);
break;
}
...
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
private void AddPropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged += OnPropertyChanged;
}
}
}
private void RemovePropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged -= OnPropertyChanged;
}
}
}
To elaborate a bit on Clemens' answer above, here's the simple way to use these events (on the collection, and on the contained items) to implement an IsDirty flag such as you described:
public class DirtyCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
private bool isDirty = false;
public bool IsDirty
{
get { return this.isDirty; }
}
public void Clean()
{
this.isDirty = false;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// We aren't concerned with how the collection changed, just that it did.
this.isDirty = true;
// But we do need to add the handlers to detect property changes on each item.
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
this.AddPropertyChanged(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
this.RemovePropertyChanged(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
this.RemovePropertyChanged(e.OldItems);
this.AddPropertyChanged(e.NewItems);
break;
}
base.OnCollectionChanged(e);
}
private void AddPropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged += OnItemPropertyChanged;
}
}
}
private void RemovePropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged -= OnItemPropertyChanged;
}
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// A property of a contained item has changed.
this.isDirty = true;
}
}
The code should be fairly self-explanatory.
You can, of course, remove the "where T : INotifyPropertyChanged" to allow objects that don't implement that interface to be stored in the collection, but then you won't be notified of any property changes on them, as without that interface, they can't notify you of them.
And should you want to keep track of not only that the collection is dirty but also how, some additions in OnCollectionChanged and OnItemPropertyChanged to record the information passed in the event args would do that nicely.
I have a DataGrid in a WPF application which has for its ItemsSource a custom collection that I wrote. The collection enforces that all its items satisfy a certain requirement (namely they must be between some minimum and maximum values).
The collection's class signature is:
public class CheckedObservableCollection<T> : IList<T>, ICollection<T>, IList, ICollection,
INotifyCollectionChanged
where T : IComparable<T>, IEditableObject, ICloneable, INotifyPropertyChanged
I want to be able to use the DataGrid feature in which committing an edit on the last row in the DataGrid results in a new item being added to the end of the ItemsSource.
Unfortunately the DataGrid simply adds a new item created using the default constructor. So, when adding a new item, DataGrid indirectly (through its ItemCollection which is a sealed class) declares:
ItemsSource.Add(new T())
where T is the type of elements in the CheckedObservableCollection. I would like for the grid to instead add a different T, one that satisfies the constraints imposed on the collection.
My questions are: Is there a built in way to do this? Has somebody done this already? What's the best (easiest, fastest to code; performance is not an issue) way to do this?
Currently I just derived DataGrid to override the OnExecutedBeginEdit function with my own as follows:
public class CheckedDataGrid<T> : DataGrid where T : IEditableObject, IComparable<T>, INotifyPropertyChanged, ICloneable
{
public CheckedDataGrid() : base() { }
private IEditableCollectionView EditableItems {
get { return (IEditableCollectionView)Items; }
}
protected override void OnExecutedBeginEdit(ExecutedRoutedEventArgs e) {
try {
base.OnExecutedBeginEdit(e);
} catch (ArgumentException) {
var source = ItemsSource as CheckedObservableCollection<T>;
source.Add((T)source.MinValue.Clone());
this.Focus();
}
}
}
Where MinValue is the smallest allowable item in the collection.
I do not like this solution. If any of you have advice I would be very appreciative!
Thanks
This problem is now semi-solvable under 4.5 using the AddingNewItem event of the DataGrid. Here is my answer to a similar question.
I solved the problem by using DataGrid's AddingNewItem event. This almost entirely undocumented event not only tells you a new item is being added, but also [allows lets you choose which item is being added][2]. AddingNewItem fires before anything else; the NewItem property of the EventArgs is simply null.
Even if you provide a handler for the event, DataGrid will refuse to allow the user to add
rows if the class doesn't have a default constructor. However, bizarrely (but thankfully) if you do have one, and set the NewItem property of the AddingNewItemEventArgs, it will never be called.
If you choose to do this, you can make use of attributes such as [Obsolete("Error", true)] and [EditorBrowsable(EditorBrowsableState.Never)] in order to make sure no one ever invokes the constructor. You can also have the constructor body throw an exception
Decompiling the control lets us see what's happening in there...
For anybody interested, I ended up solving the problem by just deriving from BindingList<T> instead of ObservableCollection<T>, using my derived class as the ItemsSource in a regular DataGrid:
public class CheckedBindingList<T> : BindingList<T>, INotifyPropertyChanged where T : IEditableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Predicate<T> _check;
private DefaultProvider<T> _defaultProvider;
public CheckedBindingList(Predicate<T> check, DefaultProvider<T> defaultProvider) {
if (check == null)
throw new ArgumentNullException("check cannot be null");
if (defaultProvider != null && !check(defaultProvider()))
throw new ArgumentException("defaultProvider does not pass the check");
_check = check;
_defaultProvider = defaultProvider;
}
/// <summary>
/// Predicate the check item in the list against.
/// All items in the list must satisfy Check(item) == true
/// </summary>
public Predicate<T> Check {
get { return _check; }
set {
if (value != _check) {
RaiseListChangedEvents = false;
int i = 0;
while (i < Items.Count)
if (!value(Items[i]))
++i;
else
RemoveAt(i);
RaiseListChangedEvents = true;
SetProperty(ref _check, value, "Check");
ResetBindings();
}
}
}
public DefaultProvider<T> DefaultProvider {
get { return _defaultProvider; }
set {
if (!_check(value()))
throw new ArgumentException("value does not pass the check");
}
}
protected override void OnAddingNew(AddingNewEventArgs e) {
if (e.NewObject != null)
if (!_check((T)e.NewObject)) {
if (_defaultProvider != null)
e.NewObject = _defaultProvider();
else
e.NewObject = default(T);
}
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e) {
switch (e.ListChangedType) {
case (ListChangedType.ItemAdded):
if (!_check(Items[e.NewIndex])) {
RaiseListChangedEvents = false;
RemoveItem(e.NewIndex);
if (_defaultProvider != null)
InsertItem(e.NewIndex, _defaultProvider());
else
InsertItem(e.NewIndex, default(T));
RaiseListChangedEvents = true;
}
break;
case (ListChangedType.ItemChanged):
if (e.NewIndex >= 0 && e.NewIndex < Items.Count) {
if (!_check(Items[e.NewIndex])) {
Items[e.NewIndex].CancelEdit();
throw new ArgumentException("item did not pass the check");
}
}
break;
default:
break;
}
base.OnListChanged(e);
}
protected void SetProperty<K>(ref K field, K value, string name) {
if (!EqualityComparer<K>.Default.Equals(field, value)) {
field = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
This class is incomplete, but the implementation above is enough for validating lists of statically-typed (not built by reflection or with the DLR) objects or value types.
I want a list of items that can display either an item's 'greek' or 'english' name, depending on a user toggling between the two. All of the items in the list implement INPC.
Since each item has a GreekName property and an RomanName property, the strategy I am using is to simply change the items DisplayName property. Unit tests and log output indicate that the DisplayName for each item does change and does fire INPC, but the list does not update.
The list is an ObservableCollection. I am wondering if this fails to update because the hash code doesn't change? Does that mean that the only way to replace the item in the list with a new one?
Some code below...
Cheers,
Berryl
public class MasterViewModel : ViewModelBase
{
public ObservableCollection<DetailVm> AllDetailVms
{
get { return _allDetailVms; }
}
private readonly ObservableCollection<DetailVm> _allDetailVms;
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (DetailVm vm in e.NewItems) vm.PropertyChanged += OnGreekGodChanged;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (DetailVm vm in e.OldItems) vm.PropertyChanged -= OnGreekGodChanged;
}
private void OnGreekGodChanged(object sender, PropertyChangedEventArgs e)
{
var detailVm = (DetailVm)sender;
// if DisplayName has changed we want to refresh the view & its filter
var displayName = ExprHelper.GetPropertyName<DetailVm>(x => x.DisplayName);
if (e.PropertyName == displayName)
Log.Info("'{0} reports it's display name has changed", detailVm.DisplayName);
}
private void _flipGreekOrRomanDisplay(string newName, Func<DetailVm, string> property)
{
foreach (var detailVm in _allDetailVms)
{
Log.Info("To '{0}', before change: '{1}'", newName, detailVm.DisplayName);
detailVm.DisplayName = property(detailVm);
Log.Info("To '{0}', after change: '{1}'", newName, detailVm.DisplayName);
}
NameFilterLabelText = newName;
NotifyOfPropertyChange(() => NameFilterLabelText);
NotifyOfPropertyChange(() => UseGreekName);
NotifyOfPropertyChange(() => UseRomanName);
}
}
My idiocy - my databinding was off. The code was fine and the view updates by virtue of it's items firing INPC, as expected.
I have a WPF ListView bound to a CollectionViewSource. The source of that is bound to a property, which can change if the user selects an option.
When the list view source is updated due to a property changed event, everything updates correctly, but the view is not refreshed to take into account any changes in the CollectionViewSource filter.
If I attach a handler to the Changed event that the Source property is bound to I can refresh the view, but this is still the old view, as the binding has not updated the list yet.
Is there a decent way to make the view refresh and re-evaluate the filters when the source changes?
Cheers
Updating the CollectionView.Filter based on a PropertyChanged event is not supported by the framework.
There are a number of solutions around this.
1) Implementing the IEditableObject interface on the objects inside your collection, and calling BeginEdit and EndEdit when changing the property on which the filter is based.
You can read more about this on the Dr.WPF's excellent blog here : Editable Collections by Dr.WPF
2) Creating the following class and using the RefreshFilter function on the changed object.
public class FilteredObservableCollection<T> : ObservableCollection<T>
{
public void RefreshFilter(T changedobject)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, changedobject, changedobject));
}
}
Example:
public class TestClass : INotifyPropertyChanged
{
private string _TestProp;
public string TestProp
{
get{ return _TestProp; }
set
{
_TestProp = value;
RaisePropertyChanged("TestProp");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
FilteredObservableCollection<TestClass> TestCollection = new FilteredObservableCollection<TestClass>();
void TestClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TestProp":
TestCollection.RefreshFilter(sender as TestClass);
break;
}
}
Subscribe to the PropertyChanged event of the TestClass object when you create it, but don't forget to unhook the eventhandler when the object gets removed, otherwise this may lead to memory leaks
OR
Inject the TestCollection into the TestClass and use the RefreshFilter function inside the TestProp setter.
Anyhow, the magic here is worked by the NotifyCollectionChangedAction.Replace which updates the item entirely.
Are you changing the actual collection instance assigned to the CollectionViewSource.Source, or are you just firing PropertyChanged on the property that it's bound to?
If the Source property is set, the filter should be recalled for every item in the new source collection, so I'm thinking something else is happening. Have you tried setting Source manually instead of using a binding and seeing if you still get your behavior?
Edit:
Are you using CollectionViewSource.View.Filter property, or the CollectionViewSource.Filter event? The CollectionView will get blown away when you set a new Source, so if you had a Filter set on the CollectionView it won't be there anymore.
I found a specific solution for extending the ObservableCollection class to one that monitors changes in the properties of the objects it contains here.
Here's that code with a few modifications by me:
namespace Solution
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e != null) // There's been an addition or removal of items from the Collection
{
Unsubscribe(e.OldItems);
Subscribe(e.NewItems);
base.OnCollectionChanged(e);
}
else
{
// Just a property has changed, so reset the Collection.
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
protected override void ClearItems()
{
foreach (T element in this)
element.PropertyChanged -= ContainedElementChanged;
base.ClearItems();
}
private void Subscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged += ContainedElementChanged;
}
}
private void Unsubscribe(IList iList)
{
if (iList != null)
{
foreach (T element in iList)
element.PropertyChanged -= ContainedElementChanged;
}
}
private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
// Tell the Collection that the property has changed
this.OnCollectionChanged(null);
}
}
}
Maybe a bit late to the party but just in case
You can also use CollectionViewSource.LiveSortingProperties
I found it through this blog post.
public class Message : INotifyPropertyChanged
{
public string Text { get; set; }
public bool Read { get; set; }
/* for simplicity left out implementation of INotifyPropertyChanged */
}
public ObservableCollection<Message> Messages {get; set}
ListCollectionView listColectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(Messages);
listColectionView.IsLiveSorting = true;
listColectionView.LiveSortingProperties.Add(nameof(Message.Read));
listColectionView.SortDescriptions.Add(new SortDescription(nameof(Message.Read), ListSortDirection.Ascending));
I found a relatively simple method to do this.
I changed the readonly ICollectionView property to get/set and added the raised property event:
Property TypeFilteredCollection As ICollectionView
Get
Dim returnVal As ICollectionView = Me.TypeCollection.View
returnVal.SortDescriptions.Add(New SortDescription("KeyName", ListSortDirection.Ascending))
Return returnVal
End Get
Set(value As ICollectionView)
RaisePropertyChanged(NameOf(TypeFilteredCollection))
End Set
End Property
Then to update, i just used:
Me.TypeFilteredCollection = Me.TypeFilteredCollection
This clearly won't work if you don't have somewhere to trigger that update though.
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.