C# WPF MVVM undo system with INotifyPropertyChanged - c#

I am attempting to make an undo system, where when a property on an object that implements INotifyPropertyChanged is changed, the property name and its old value is pushed onto a stack via a KeyValuePair. When the user clicks "Undo" it then pops from the stack and uses reflection to set the property's value to its old value.
The problem with this is that it calls OnPropertyChanged again, so the property and its restored value is added to the undo stack a second time. On the other hand, I still want it to call OnPropertyChanged since I want the view to update its bindings.
There's obviously something wrong with how I'm designing it, but I can't seem to figure out another way of going about it.
Here's my model
internal class MyModel : INotifyPropertyChangedExtended
{
private string testProperty1 = "";
public string TestProperty1
{
get { return testProperty1; }
set {
var oldValue = testProperty1;
testProperty1 = value;
OnPropertyChanged(nameof(TestProperty1), oldValue);
}
}
private string testProperty2 = "";
public string TestProperty2
{
get { return testProperty2; }
set {
var oldValue = testProperty2;
testProperty2 = value;
OnPropertyChanged(nameof(TestProperty2), oldValue);
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName, object oldValue)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgsExtended(propertyName, oldValue));
}
}
}
Here's my INotifyPropertyChangedExtended interface
public class PropertyChangedEventArgsExtended : PropertyChangedEventArgs
{
public virtual object OldValue { get; private set; }
public PropertyChangedEventArgsExtended(string propertyName, object oldValue)
: base(propertyName)
{
OldValue = oldValue;
}
}
public class INotifyPropertyChangedExtended : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName, object oldValue)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgsExtended(propertyName, oldValue));
}
}
And here's my view model
internal class MyViewModel
{
public MyModel MyModel { get; set; } = new();
public Stack<KeyValuePair<string, object>> PropertyStateStack = new();
public RelayCommand Undo { get; set; }
public MyViewModel()
{
SetupCommands();
MyModel.PropertyChanged += MyModel_PropertyChanged;
}
private void MyModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var args = e as PropertyChangedEventArgsExtended;
if (args.OldValue != null)
{
PropertyStateStack.Push(new KeyValuePair<string, object>(args.PropertyName, args.OldValue));
}
}
private void SetupCommands()
{
Undo = new RelayCommand(o =>
{
KeyValuePair<string, object> propertyState = PropertyStateStack.Pop();
PropertyInfo? property = MyModel.GetType().GetProperty(propertyState.Key);
if (property != null)
{
property.SetValue(MyModel, Convert.ChangeType(propertyState.Value, property.PropertyType), null);
}
});
}
}
EDIT: I did research the "memento pattern" but I couldn't get it to work with INotifyPropertyChanged, since as soon as I set MyModel to a backup of it the bindings to the view stopped working.

Implementing Memento or a variant is the right way. Opposed to storing the particular modifying undo action e.g., Action<T> (another good solution), Memento has a higher memory footprint (as it stores the complete object state), but allows random access to the stored states.
The key point is that when implementing Memento properly, you don't have to rely on reflection, which will only make your code slow and heavy.
The following example uses the IEditableObject interface to implement the Memento pattern (variant). The implementation supports undo and redo. The TextBox class is implementing undo/redo in a similar way using the same interface. The advantage is that you have full control over when to record the object's state. You can even cancel the ongoing modification.
This example clones the complete object to backup the state. Because objects can be quite expensive, for example when they allocate resources, it could make sense to introduce an immutable data model that actually stores the values of the public editable properties. Now, instead of cloning the complete object you would only clone the immutable data model. This can improve the performance in critical scenarios.
See the example provided by the IEditableObject link above to learn how to introduce an immutable data model that holds the object's data.
The actual undo/redo logic is encapsulated in the example's abstract StateTracker<TStateObject> class. StateTracker<TStateObject> implements the aforementioned IEditableObject and the ICloneable interface. To add convenience, StateTracker<TStateObject> also implements a custom IUndoable interface (to enable anonymous usage of the public undo/redo API).
Every class that needs to support state tracking (undo/redo) must extend the abstract StateTracker<TStateObject> to provide a ICloneable.Clone and a StateTracker.UpdateState implementation.
The following example is very basic. It allows undo and redo, but does not support random access to undo/redo states. You would have to use an index based backing store like List<T> to implement such a feature.
IUndoable.cs
Enable anonymous access to the undo/redo API.
public interface IUndoable
{
bool TryUndo();
bool TryRedo();
}
StateTracker.cs
Encapsulates the actual undo/redo logic to avoid duplicate implementations
for each type that is supposed to support undo/redo.
You can consider to add a public UndoCommand and RedoCommand to this class and let the commands invoke TryUndo and TryRedo respectively.
public abstract class StateTracker<TStateObject> : IEditableObject, IUndoable, ICloneable
{
public bool IsInEditMode { get; private set; }
private Stack<TStateObject> UndoMemory { get; }
private Stack<TStateObject> RedoMemory { get; }
private TStateObject StateBeforeEdit { get; set; }
private bool IsUpdatingState { get; set; }
protected StateTracker()
{
this.UndoMemory = new Stack<TStateObject>();
this.RedoMemory = new Stack<TStateObject>();
}
public abstract TStateObject Clone();
protected abstract void UpdateState(TStateObject state);
object ICloneable.Clone() => Clone();
public bool TryUndo()
{
if (!this.UndoMemory.TryPop(out TStateObject previousState))
{
return false;
}
this.IsUpdatingState = true;
this.StateBeforeEdit = Clone();
this.RedoMemory.Push(this.StateBeforeEdit);
UpdateState(previousState);
this.IsUpdatingState = false;
return true;
}
public bool TryRedo()
{
if (!this.RedoMemory.TryPop(out TStateObject nextState))
{
return false;
}
this.IsUpdatingState = true;
this.StateBeforeEdit = Clone();
this.UndoMemory.Push(this.StateBeforeEdit);
UpdateState(nextState);
this.IsUpdatingState = false;
return true;
}
// Start recording the changes
public void BeginEdit()
{
if (this.IsInEditMode || this.IsUpdatingState)
{
return;
}
this.IsInEditMode = true;
// Create the snapshot before the instance is changed
this.StateBeforeEdit = Clone();
}
// Abort recording the changes
public void CancelEdit()
{
if (!this.IsInEditMode)
{
return;
}
// Restore the original state
UpdateState(this.StateBeforeEdit);
this.IsInEditMode = false;
}
// Commit recorded changes
public void EndEdit()
{
if (!this.IsInEditMode || this.IsUpdatingState)
{
return;
}
// Commit the snapshot of the original state after the instance was changed without cancellation
this.UndoMemory.Push(this.StateBeforeEdit);
this.IsInEditMode = false;
}
}
MyModel.cs
public class MyModel : StateTracker<MyModel>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyModel()
{
}
// Copy constructor
private MyModel(MyModel originalInstance)
{
// Don't raise PropertyChanged to avoid the loop of death
this.testProperty1 = originalInstance.TestProperty1;
this.testProperty2 = originalInstance.TestProperty2;
}
// Create a deep copy using the copy constructor
public override MyModel Clone()
{
var copyOfInstance = new MyModel(this);
return copyOfInstance;
}
protected override void UpdateState(MyModel state)
{
// UpdateState() is called by the StateTracker
// which internally guards against the infinite loop
this.TestProperty1 = state.TestProperty1;
this.TestProperty2 = state.TestProperty2;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string testProperty1;
public string TestProperty1
{
get => this.testProperty1;
set
{
this.testProperty1 = value;
OnPropertyChanged();
}
}
private string testProperty2;
public string TestProperty2
{
get => this.testProperty2;
set
{
this.testProperty2 = value;
OnPropertyChanged();
}
}
}
Example
The following example stores the state of a TextBox, that binds to a MyModel instance. When the TextBox receives focus, the MyModel.BeginEdit method is called to start recording the input. When the TextBox loses focus the recorded state is pushed onto the undo stack by calling the MyModel.EndEdit method.
MainWindow.xaml
<Window>
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<StackPanel>
<Button Content="Undo"
Click="OnUndoButtonClick" />
<Button Content="Redo"
Click="OnRedoButtonClick" />
<TextBox Text="{Binding TestProperty1, UpdateSourceTrigger=PropertyChanged}"
GotFocus="OnTextBoxGotFocus"
LostFocus="OnTextBoxLostFocus" />
</StackPanel>
</Window>
MainWindow.xaml.cs
Because of the defined interfaces we can handle undo/redo without knowing the actual data type.
private void OnTextBoxGotFocus(object sender, RoutedEventArgs e)
=> ((sender as FrameworkElement).DataContext as IEditableObject).BeginEdit();
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
=> ((sender as FrameworkElement).DataContext as IEditableObject).EndEdit();
private void OnUndoButtonClick(object sender, RoutedEventArgs e)
=> _ = ((sender as FrameworkElement).DataContext as IUndoable).TryUndo();
private void OnRedoButtonClick(object sender, RoutedEventArgs e)
=> _ = ((sender as FrameworkElement).DataContext as IUndoable).TryRedo();
An alternative flow could be that the MyModel class internally calls BeginEdit and EndEdit inside the relevant property setters (before accepting the new value and after accepting the new value). In case of the TextBox, the advantage of this solution is that it allows to record every single input.
In this scenario, the GotFocus and LostFocus event handlers previously defined on the TextBox (example above) are not needed and related code must be removed:
MyModel.cs
public class MyModel : StateTracker<MyModel>, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyModel()
{
}
// Copy constructor
private MyModel(MyModel originalInstance)
{
// Don't raise PropertyChanged to avoid the loop of death
this.testProperty1 = originalInstance.TestProperty1;
this.testProperty2 = originalInstance.TestProperty2;
}
// Create a deep copy using the copy constructor
public override MyModel Clone()
{
var copyOfInstance = new MyModel(this);
return copyOfInstance;
}
protected override void UpdateState(MyModel state)
{
// UpdateState() is called by the StateTracker
// which internally guards against the infinite loop
this.TestProperty1 = state.TestProperty1;
this.TestProperty2 = state.TestProperty2;
}
private void RecordPropertyChange<TValue>(ref TValue backingField, TValue newValue)
{
BeginEdit();
backingField = newValue;
EndEdit();
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string testProperty1;
public string TestProperty1
{
get => this.testProperty1;
set
{
RecordPropertyChange(ref this.testProperty1, value);
OnPropertyChanged();
}
}
private string testProperty2;
public string TestProperty2
{
get => this.testProperty2;
set
{
RecordPropertyChange(ref this.testProperty2, value);
OnPropertyChanged();
}
}
}
Remarks
If extending StateTracker is not an option (e.g., because it would introduce a multi-inheritance issue), you can always make use of composition (for example add a private property of type StateTracker to your undoable model to replace inheritance).
Just create a new class that extends StateTracker to implement the abstract members. Then define a private property of this new type in your undoable model. Now, let the model reference this private property to access the undo/redo API.
While composition is to be favored, this example chooses inheritance as this concept feels more natural to most. It may helps to understand the basic idea.

Related

Changes to collection do not update UI

I don't understand why when I update a object, my bound controls do not update.
The data displays fine initially, but when I want to refresh the data displayed in the UI nothing happens when I update the object. The object updates fine. The ViewModel does use INotifyPropertyChanged on all fields.
However if I update individual items directly, I can update my UI. As commented below.
I guess I've made a school boy error somewhere here?
UPDATE: I've added the model to the question. While I understand the answers, I don't understand how to implement it. Attempted to implement a collection changed event without success. Can I have some pointers please?
public partial class CisArrivalsPanel : UserControl
{
private ApiDataArrivalsDepartures _theArrivalsDepartures;
public CisArrivalsPanel()
{
InitializeComponent();
_theArrivalsDepartures = new ApiDataArrivalsDepartures();
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Kings Cross");
this.DataContext = _theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
//However this (when uncommented, and I comment out the above line) does update the UI**
//_theArrivalsDepartures.StationMovementList[0].OriginName = "test";
//_theArrivalsDepartures.StationMovementList[0].Platform = "0";
//_theArrivalsDepartures.StationMovementList[0].BestArrivalEstimateMins = "999";
//_theArrivalsDepartures.StationName = "test";
}
private void StationHeader_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Reload();
Debug.WriteLine(_theArrivalsDepartures.StationName);
foreach (var a in _theArrivalsDepartures.StationMovementList)
{
Debug.WriteLine(a.OriginName);
Debug.WriteLine(a.BestArrivalEstimateMins);
}
}
}
EDIT : Added Model
public class ApiDataArrivalsDepartures : INotifyPropertyChanged
{
private string _stationName;
[JsonProperty(PropertyName = "station_name")]
public string StationName {
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private List<StationListOfMovements> _stationMovementList;
public List<StationListOfMovements> StationMovementList
{
get
{
return _stationMovementList;
}
set
{
_stationMovementList = value;
NotifyPropertyChanged("StationMovementList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class StationListOfMovements : INotifyPropertyChanged
{
private string _originName;
[JsonProperty(PropertyName = "origin_name")]
public string OriginName {
get
{
return _originName;
}
set
{
_originName = value;
NotifyPropertyChanged("OriginName");
}
}
[JsonProperty(PropertyName = "destination_name")]
public string DestinationName { get; set; }
private string _platform;
[JsonProperty(PropertyName = "Platform")]
public string Platform {
get
{
return _platform;
}
set
{
_platform = value;
NotifyPropertyChanged("Platform");
}
}
private string _bestArrivalEstimateMins;
[JsonProperty(PropertyName = "best_arrival_estimate_mins")]
public string BestArrivalEstimateMins {
get
{
return _bestArrivalEstimateMins;
}
set
{
_bestArrivalEstimateMins = value;
NotifyPropertyChanged("BestArrivalEstimateMins");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
There are two pieces here pertaining to your collection (technically three):
If you want a new collection to propagate, the collection property has to raise PropertyChanged (sounds like it does)
If you want add/remove on the collection to propagate, you need to use a collection that implements INotifyCollectionChanged. ObservableCollection is a good choice.
If you want changes to the items in the container to propagate, then those items need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Make sure all those are covered, and the changes should appear on the UI as you expect.
You should update the DataContext and ItemsSource too.
void Reload()
{
//This does not update the UI**
_theArrivalsDepartures = MakeQuery.LiveTrainArrivals("London Paddington");
DataContext = theArrivalsDepartures;
ListBoxArr.ItemsSource = _theArrivalsDepartures.StationMovementList;
}
Use for the collection ObservableCollection , this class notify the ui when change to the collection occurred
your reload function works because the there is PropertyChanged on all the fields include this one
it notify the ui and reload the correct collection

Polling an object's public variable

I would like to notify a program immediately when there is a change in a bool variable that is a public variable of an object. For example;
say, an instance of class conn is created within a windows form application.
there is a Ready variable, a public variable of the class conn is present.
I would like to get notified whenever there is a change in this variable.
I did a quick research to solve this problem within stackoverflow but the answers suggested the use of property, which, I think is not suitable for my application.
I will assume you are referring to a field when you say public variable.
With few exceptions, it is preferable to not have public fields in C# classes, but rather private fields with public accessors:
class BadClass
{
public int Value; // <- NOT preferred
}
class GoodClass
{
private int value;
public int Value
{
get { return this.value; }
set { this.value = value; }
}
}
One of the reasons to structure your code this way is so you can do more than one thing in the property's getter and setters. An example that applies to your scenario is property change notification:
class GoodClass : INotifyPropertyChanged
{
private int value;
public int Value
{
get { return this.value; }
set
{
this.value = value;
this.OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name);
}
}
}
If you were to implement your class like this, you could use it this way:
void SomeMethod()
{
var instance = new GoodClass();
instance.PropertyChanged += this.OnPropertyChanged;
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
// Do something here.
}
}
If you change the Value property, not only will it change the value of the underlying field, but it will also raise the PropertyChanged event, and call your event handler.
You want to use the Observer pattern for this. The most straight forward way to do this in .NET is the event system. In the class conn, create an event:
public event EventHandler ReadyChanged;
and then when you create an instance of conn, subscribe to that event:
o.ReadyChanged += (s, e) =>
{
// do something
}
and then finally, when the flag changes in conn, fire the event via a new method named OnReadyChanged:
protected virtual void OnReadyChanged()
{
if (ReadyChanged != null) { ReadyChanged(this, new EventArgs()); }
}

Updating dependent properties using MVVM

Some properties on my viewmodel:
public ObservableCollection<Task> Tasks { get; set; }
public int Count
{
get { return Tasks.Count; }
}
public int Completed
{
get { return Tasks.Count(t => t.IsComplete); }
}
What's the best way to update these properties when Tasks changes?
My current method:
public TaskViewModel()
{
Tasks = new ObservableCollection<Task>(repository.LoadTasks());
Tasks.CollectionChanged += (s, e) =>
{
OnPropertyChanged("Count");
OnPropertyChanged("Completed");
};
}
Is there a more elegant way to do this?
With respect to Count, you don't have to do this at all. Simply bind to Tasks.Count and your bindings will get notified of the change by the ObservableCollection.
Completed is a different story, because this is outside of ObservableCollection. Still, from the level of the abstraction/interface, you really want Completed to be a property of that Tasks collection.
For this, I think a better approach would be to create "sub" view-model for your Tasks property:
public class TasksViewModel : ObservableCollection<Task>
{
public int Completed
{
get { return this.Count(t => t.IsComplete); }
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.PropertyName == "Count") NotifyCompletedChanged();
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
NotifyCompletedChanged();
}
void NotifyCompletedChanged()
{
OnPropertyChanged(_completedChangedArgs);
}
readonly PropertyChangedEventArgs _completedChangedArgs = new PropertyChangedEventArgs("Completed");
}
This gives you all of the benefits of the ObservableCollection, and effectively makes the Completed property part of it. We still haven't captured only the cases where the number of completed items truly changes, but we have reduced the number of redundant notifications somewhat.
Now the viewmodel just has the property:
public TasksViewModel Tasks { get; set; }
…and you can bind to Tasks, Tasks.Count, and Tasks.Completed with ease.
As an alternative, if you would rather create these other properties on the "main" view-model, you can take this notion of a subclassed ObservableCollection<T> to create one with some method where you can pass in an Action<string> delegate, which would represent raising a property change notification on the main view-model, and some list of property names. This collection could then effectively raise the property change notifications on the view-model:
public class ObservableCollectionWithSubscribers<T> : ObservableCollection<T>
{
Action<string> _notificationAction = s => { }; // do nothing, by default
readonly IList<string> _subscribedProperties = new List<string>();
public void SubscribeToChanges(Action<string> notificationAction, params string[] properties)
{
_notificationAction = notificationAction;
foreach (var property in properties)
_subscribedProperties.Add(property);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
NotifySubscribers();
}
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
NotifySubscribers();
}
void NotifySubscribers()
{
foreach (var property in _subscribedProperties)
_notificationAction(property);
}
}
You could even leave the property type as ObservableCollection<Task>.
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var tasks = new ObservableCollectionWithSubscribers<Task>();
tasks.SubscribeToChanges(Notify, "Completed");
Tasks = tasks;
}
public ObservableCollection<Task> Tasks { get; private set; }
public int Completed
{
get { return Tasks.Count(t => t.IsComplete); }
}
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string property)
{
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
Looks rather elegant to me. I really don't know how you'd make that more succinct.
(How odd to write an answer like this. If somebody actually comes up with something more elegant, I might delete this.)
Okay, I noticed one thing, unrelated to the original question: Your Tasks property has a public setter. Make it private set;, or you'll need to implement the set with a backing field so you can remove the delegate on the previous instance, replace and wire up the new one, and do OnPropertyChanged with "Tasks", "Count", and "Completed". (And seeing how Tasks is set in the constructor, I'm guessing private set; is the better option.)
Doesn't make notifying about Count and Completed more elegant, but it fixes a bug.
And many MVVM frameworks get the property name from a lambda, so that instead of OnPropertyChanged("Count"), you can write OnPropertyChanged(() => Count) so that it will follow renames done with the help of refactoring tools. I don't think renaming happens all that often, though, but it does avoid some string literals.

Bubbling INotifyPropertyChanged and nested properties

If I have the following layout:
public class A : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public B { get; set; }
}
public class B { public C { get; set; } }
public class C { public D { get; set; } }
public class D { public E { get; set; } }
//... add n classes
public class Z
{
public int Property
{
set
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
}
}
}
What is the cleanest way for me to notify A when A.B.C.D.E...Z.Property changes?
When anything inside of A changes, I want it to be flagged as "dirty" so I can tell the system that A needs to be saved.
I was actually working on this exact same problem just recently. My approach was to simply let B, C, D, etc. to manage their own Dirty state, and then modified A's IsDirty property as such:
public bool IsDirty
{
get
{
return _isDirty || B.IsDirty || C.IsDirty /* etc */;
}
}
To me, this is not only simple, but it makes the most sense. A is dirty if any of it's properties have changed, and B, C, D, etc are all properties of A.
I didn't test it, but following one should work. I don't remember why, but I think you cannot handle PropertyChanged events. You should declare your own delegate (VoidHandler).
public delegate void VoidHandler(object sender);
public class B // also C,D,E,...
{
// A.ItemChanged() will be wired to this SomethingChangedHandler.
// I heard you are saving. Exclude SomethingChangedHandler from save.
[field: NonSerialized]
public VoidHandler SomethingChangedHandler;
private c;
public C
{
set
{
// unwire handler from old instance of C
if(c != null)
c.SomethingChangedHandler -= ItemChanged;
// wire handler to new instance of C
value.SomethingChangedHandler += ItemChanged;
c = value;
// setting c is also change which require notification
ItemChanged(this);
}
get{}
}
// notify A about any change in B or in C
void ItemChanged(object sender)
{
if(SomethingChangedHandler != null)
SomethingChangedHandler(this);
}
}
For line of business application that have a common base class I do this as per
Implementing INotifyPropertyChanged - does a better way exist?
with some modifications to check for "bubbling" properties.
Base Class
public bool HasAlteredState { get; protected set; }
public event PropertyChangedEventHandler PropertyChanged;
private void propertyObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e.PropertyName);
}
protected virtual void RegisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
{
propertyObject.PropertyChanged += new PropertyChangedEventHandler(propertyObject_PropertyChanged);
}
protected virtual void DeregisterSubPropertyForChangeTracking(INotifyPropertyChanged propertyObject)
{
propertyObject.PropertyChanged -= propertyObject_PropertyChanged;
}
protected virtual void OnPropertyChanged(string propertyName)
{
this.HasAlteredState = true;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
if (field is INotifyPropertyChanged)
{
if (field != null) { this.DeregisterSubPropertyForChangeTracking((INotifyPropertyChanged)field); }
}
if (value is INotifyPropertyChanged)
{
if (value != null) { this.RegisterSubPropertyForChangeTracking((INotifyPropertyChanged)value); }
}
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
Sub classes
private IndividualName _name;
public IndividualName PersonName
{
get { return _name; }
set { SetField(ref _name, value, () => PersonName); }
}
Provides
Simple property change notification
Complex property change notification
Event "bubbling" from INotifyPropertyChanged implementations deeper in the object graph
Compile time checking that your property "name" actually refers to your property. i.e. avoid nasty bugs related to spelling a property name wrong when just using a string.
Performance
There is an associated performance hit to this approach... about 20% slower than just using a string. That said although the metrics and tracing say it's slower I cant actually tell the difference so the hit is worth it re: application maintenance for the kinds of apps I'm involved in developing.
Alternative Implementations
If base class is not an option you could go Extension method route.
For better performance you could have two different SetField methods; the first SetNotifyField would deal with properties which themselves implement INotifyPropertyChanged (as above) and the second SetField would deal with simple properties. i.e. cut out the
if (field is INotifyPropertyChanged)...

Injecting (INotifyPropertyChanged functionality) to an instance of an class

I have a class that implements INotifyPropertyChanged.
I create an instance of a class in some viewModel.
Is it possible to remove this functionality from the class and inject it after the instance was created? I heard that ICustomTypeDescriptor would make this happen, but i dont know how to use it.
public class C : ICustomNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int _id;
public string _name;
public int Id
{
get { return _id; }
set
{
if (_id == value)
{
return;
}
_id = value;
OnPropertyChanged("Id");
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
If you are just trying to prevent the notifications from being fired when the object is first created and properties set, you can add boolean flag(s) that is/are false until the properties have been set once. You only execute the notification if the flag is true.
Edit:
I don't think there's a clean way to get the functionality in there after removing all the INotifyPropertyChanged code, but there are many ways to control the functionality from outside the instance.
Please note that I wrote all this code in the text editor, not in VisualStudio; it has not been tested in any way.
Add a method to enable notifications:
public class OptionalNotification : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name) ...
bool _shouldNotify;
public void EnableNotifications()
{
_shouldNotify = true;
}
string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if(_someProperty == value) return
_someProperty = value;
if(_shouldNotify) OnPropertyChanged("SomeProperty");
}
}
}
You could do the same thing without the method, if you knew at the time of instantiation whether or not the instance should produce notifications, in which case you'd just need a boolean parameter in the constructor.
Another variation would be to use the Factory pattern, where your Factory has internal access to the boolean flag and sets it upon construction.
Encapsulate the condition in a proxy:
public interface IEntity : INotifyPropertyChanged
{
string SomeProperty { get; set; }
}
public class Entity : IEntity
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name) ...
string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if(_someProperty == value) return
_someProperty = value;
OnPropertyChanged("SomeProperty");
}
}
}
public class EntityNotificationProxy : IEntity
{
IEntity _inner;
public EntityNotificationProxy(IEntity entity)
{
_inner = entity;
_inner.PropertyChanged += (o,e) => { if(ShouldNotify) OnPropertyChanged(o,e); }
}
public bool ShouldNotify { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(object sender, PropertChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null) handler(sender, e);
}
public string SomeProperty
{
get { return _inner.SomeProperty; }
set
{
if(_inner.SomeProperty == value) return
_inner.SomeProperty = value;
}
}
}
Here your consuming classes get the entity proxy instead of the entity itself (but is none the wiser because it references only IEntity when you program to interfaces/abstractions). The wrapping of the proxy can happen in a factory or through an IoC container/DI framework.
The main advantage to this approach is that your entity maintains a pure INotifyPropertyChanged implementation, and the conditional aspect is handled from without. Another advantage is that it helps to enforce programming to abstractions and inversion of control.
The main disadvantage is that you'll need to create proxies for each INotifyPropertyChanged implementation that you want to have this conditional behaviour.
Create a registry to keep track of what instances should or should not raise notifications:
public static class PropertyNotificationRegistry
{
static IDictionary<INotifyPropertyChanged, bool> _registeredClasses
= new Dictionary<INotifyPropertyChanged, bool>;
static void Register(INotifyPropertyChanged o, bool shouldNotify)
{
if(!(_registeredClasses.ContainsKey(o)) _registeredClasses.Add(o, shouldNotify);
// could also implement logic to update an existing class in the dictionary
}
public static void ShouldNotifyWhenPropertiesChange(this INotifyPropertyChanged o)
{
Register(o, true);
}
public static void ShouldNotNotifyWhenPropertiesChange(this INotifyPropertyChanged o)
{
Register(o, false);
}
public static void NotifyPropertyChanged(this INotifyPropertyChanged o, Action notificationAction)
{
if(_registeredClasses.ContainsKey(o))
{
bool shouldNotify = _registeredClasses.Where(x => x.Key == o).Single().Value;
if(shouldNotify) notificationAction();
}
}
}
public class EntityUsingNotificationRegistry : INotifyPropertyChanged
{
... // all the standard INotifyPropertyChanged stuff
string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if(_someProperty == value) return;
_someProperty = value;
this.NotifyPropertyChanged(() => OnPropertyChanged("SomeProperty"));
}
}
}
public class SomethingInstantiatingOurEntity
{
public void DoSomething()
{
var entity1 = new EntityUsingNotificationRegistry();
entity1.ShouldNotifyWhenPropertiesChange();
var entity2 = new EntityUsingNotificationRegistry();
entity2.ShouldNotNotifyWhenPropertiesChange();
entity1.SomeProperty = "arbitrary string"; // raises event
entity2.SomeProperty = "arbitrary string"; // does not raise event
var entity3 = new EntityUsingNotificationRegistry();
entity3.SomeProperty = "arbitrary string"; // does not raise event
entity3.ShouldNotifyWhenPropertiesChange();
entity3.SomeProperty = "another arbitrary string"; // now raises event
}
}
Now, the registry has a distinct shortcoming in that it holds references to every instance and will prevent those instances from being picked up by the garbage collector. There may be a solution to this by implementing the registry with WeakReferences, but I'm not up-to-snuff on their usage to recommend a particular implementation.
This will not work. You COULD subclass and inject it, but you would have to change the byte-code to make sure the proper methods are CALLED - and that is the harder method.

Categories

Resources