-
Notifications
You must be signed in to change notification settings - Fork 4
Getting Started
- Make a ViewModel that inherits from TrackableViewModel
- Give it some properties, like this:
public bool ILikeCheese
{
get => Get<bool>();
set
{
if(Set(value))
MessageBox.Show("Why did you change your mind?");
} = true;
}
- For any collection you need, use the TrackableCollection (most often)
- Create your scope when you want to track changes, like this:
using (new TrackableScope("Undo me now!"))
{
... do some stuff ...
}
OR just set Globals.ScopeEachChange to true.
That's it! AccumulatorManager has bindable properties/commands for you to use in your UI for undoing anything you've tracked.
This is, unfortunately, quite simple if you are not acutely aware of how TrackableCollection/Dictionary locks for concurrency, due to the nature of most UI's (like WPF) that require the collection change and the CollectionChanged event to happen synchronously, the lock must then be set on the UI thread. I truly, heartily wish it weren't so and I have in fact worked very hard to find a way to not make it so, but WPF ended up killing all such aspirations after much gnashing of teeth. My experience with .NET Core Xaml has not lead me to believe that it is any more flexible than WPF in this area.
Given that the lock for any TrackableCollection/Dictionary happens on the UI thread, it is trivial to create a thread-locking situation. Presume you have this horrible extension method (you get the idea, even though this is truly a horrible method):
public static void AddAndRemoveOneSecondLater<TType>(this TrackableCollection<TType> coll, TType item)
{
lock(coll.SyncRoot)
{
coll.Add(item);
Thread.Sleep(60000);
coll.Remove(item);
}
}
If you have this method, and you're no amateur, you're not going to call it on the UI thread b/c of that Thread.Sleep() call because that would be bad form. Bad form indeed!
So, if called on a background thread of some sort (thread pool, let's say), you're going to lock on a background thread, and then the Add() will also lock on that same object on the UI Thread. Since Add() is synchronous, you have now got yourself a thread-lock b/c the UI thread is waiting on your background thread to release the lock, but your background thread is waiting on the Add() to finish on the UI thread.