Problems with MUF on ObservableCollections

Mar 13, 2012 at 9:49 PM

We have a very large project in development and trying to implement MUF.  In some areas we can get it to work fine, on other ObservableCollections it does not seem to track correctly.  Obviously something we are doing wrong.  Are there any more code examples or documentation that goes more in depth.  the PhotoTagger App was so simple (very elegant!) but did not even cover undoing collections as well as the documentation provided is very limited.  For example ISupportsUndoNotification and IUndoMetadata says it is not needed for the Undo system but is needed for the DefaultChangeFactory, why???  We are using the DefaultChangeFactory and it works in some places and not others so it gets me to wondering about these two interfaces which we have not used but are suspecting are needed in some way.   Thanks.


Mar 13, 2012 at 10:52 PM

IUndoMetadata is optional, you don't have to implement this interface to make the whole framework work. If you want to (temporary) disable undo/redo on some properties within a class that implements ISupportsUndo, then you need to implement this interface. Take a look at this discussion for more details or maybe better, take look at diffs of change set #14224

ISupportsUndoNotification is also optional. I didn't implement this interface in my project (because I don't need it) and DefaultChangeFactory works fine. Any problems with DefaultChangeFactory I think are caused by wrong GetUndoRoot() method implementation

Mar 14, 2012 at 1:05 AM

Hi @jritchie777,

Sorry to hear that you've had issues getting MUF working. I'm happy to help with these questions.

@tuvok's comments are good and correct. You don't need these interfaces to make things work, but they enable more control over how the undo/redo lifecycle.

If possible, can you describe more about what you're seeing?

In the mean time, here are some things to keep in mind, and some areas that I've seen issues in my apps:

  1. UndoService.Current[key] is basically a dictionary. The key is usually the "root" of your object hierarchy.
    1. Pass in the same "key" to get back the same UndoRoot.
  2. UndoRoot holds the undo / redo stacks. In general, you'll have one UndoRoot for each "document" in your app. If you don't have "documents", then you can think in terms of "scopes" or "domains" of undo / redo actions.
  3. DefaultChangeFactory is just one way to capture changes to the objects.
    1. It works using anonymous delegates / lambdas and reflection. This works for desktop apps since everything tends to stay in memory. If your scenario is different, then you might need to create a different way to do this. I can help in this area if you have questions.
    2. In some cases, you may want to manually create a ChangeSet and push it on the undo stack. I've done this in portions of the application that didn't fit the standard situation, like screen navigation changes that I wanted to show up on the undo stack.
  4. ObservableCollections should be supported by the DefaultChangeFactory. You'll need to hook the CollectionChanged event of each collection and then pass the required parameters into the DefaultChangeFactory.OnCollectionChanged(...) method.
  5. ISupportsUndo is the primary interface needed by the DefaultChangeFactory.
    1. It's main job is to return the object instance that represents the root of the hierarchy.
    2. The instance returned is used to get the UndoRoot via the UndoService.Current[root] method.
  6. ISupportsUndo kind of assumes that your "leaf nodes" in your object hierarchy know how to get their "parent". This means that any object in the hierarchy can walk back to the top level parent.
    1. Getting this right can be non-trivial. Entity framework's Self Tracking Entities provide a nice implementation that will automatically keep these "bidirectional" object references and collections consistent. For example, adding ObjA to a collection on ObjB will result in ObjB updating the associated property on ObjA. That means that ObjA knows it is in the collection on ObjB and vice versa.
    2. If the "parent" approach isn't desired, then you are free to use an alternate object as the "key". Ultimately, it's just the "key" used to get the appropriate UndoRoot out of the dictionary.
  7. If you're not seeing changes in the UI, then it might not be the fault of the Undo usage. It could be that the objects are not properly implementing INotifyPropertyChanged. I've had a number of cases where I would undo a change, but not "see" it. It turned out the the model was changing, but not notifying the UI of that change.

I hope that helps a little. If I didn't answer a question that you have, please post more and I'll do what I can do. And if you can, please let me know more about what you're seeing in your implementation. We can take this to email if you don't want to post details on a public forum.

Thanks for taking a look at MUF.

- Nathan

Mar 14, 2012 at 2:19 PM

Thanks Tuvok & Nathan.  Let me go through all your good advice and see if I can get things working.  Thanks!!


Mar 14, 2012 at 11:56 PM


If we are talking about monitoring collections - is there any particular reason why you didn't cover all the cases of collection change in DefaultChangeFactory (I mean not implemented collection change actions)? I feel it was lack of time but maybe there were some problems with complexity of these cases I can't see yet...

Mar 15, 2012 at 12:59 AM

Hi @tuvok,

I think at one point I started to work on Replace, Move, Reset. But for some reason I backed out that code.

If these are useful to people, then we can probably get them implemented.

Do you use or see Replace / Move / Reset in your implementations that use ObservableCollection?

- Nathan

Mar 15, 2012 at 3:00 PM

Hi Nathan,

Yes, I need them. I think the most problematic would be Reset because you have to clone the whole List or do something similar as you cannot pass it with NotifyCollectionChangedEventArgs because Reset action disallows passing anything with Reset

Mar 15, 2012 at 3:07 PM

Hi @tuvok,

Are you interested in assisting with implementation of these? If so, that would be awesome. If not, I'll see if I can get these changes in over the next few days.

- Nathan

Mar 15, 2012 at 3:12 PM

OK, I'll try to implement these 3 actions. I can send you changed files or you can add me proper access rights to the repository. It's up to you, for me there's no difference

Mar 15, 2012 at 3:12 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.