This project has moved and is read-only. For the latest updates, please go here.
1
Vote

Allow pre/post changeset actions?

description

I have run into a couple situations where I need to run an action after a change is applied, either undoing or redoing. In this case, it's to simply "refresh" a view after a change, because trying to do it step-by-step is too complex. There is no obvious way to do it, but I built my own UndoBatch class that seems to work well.

What do you think? Should I send it as a pull request, possibly replacing the existing UndoBatch? Is there a preferred way?

Here it is:
        public class UndoBatchWithAction : IDisposable
        {
            public UndoBatchWithAction(ISupportsUndo instance, string description, bool consolidateChangesForSameInstance, Action preChangeAction = null, Action postChangeAction = null)
                : this(UndoService.Current[instance.GetUndoRoot()], description, consolidateChangesForSameInstance, preChangeAction, postChangeAction)
            {
            }

            public UndoBatchWithAction(UndoRoot root, string description, bool consolidateChangesForSameInstance, Action preChangeAction = null, Action postChangeAction = null)
            {
                if (this.PreChangeAction != null)
                    this.PreChangeAction();

                if (null == root)
                    return;

                _UndoRoot = root;
                this.PreChangeAction = preChangeAction;
                this.PostChangeAction = postChangeAction;
                this.AddInitialAction();

                root.BeginChangeSetBatch(description, consolidateChangesForSameInstance);
            }

            private UndoRoot _UndoRoot;
            private Action PostChangeAction;
            private Action PreChangeAction;

            private void AddInitialAction()
            {
                object target = null;
                var changeToRebindUponUndo = new DelegateChange(target,
                                this.PostChangeAction,
                                this.PreChangeAction,
                                new Tuple<object, string>(target, "Initial action"));

                this._UndoRoot.AddChange(changeToRebindUponUndo, "Act before changeset is applied.");
            }

            private void AddFinalAction()
            {
                object target = null;
                var changeToRebindUponRedo = new DelegateChange(target,
                                this.PreChangeAction,
                                this.PostChangeAction,
                                new Tuple<object, string>(target, "Final action."));

                this._UndoRoot.AddChange(changeToRebindUponRedo, "Act after changeset is applied.");
            }

            #region IDisposable Members

            private void Dispose(bool disposing)
            {
                if (disposing)
                {
                    if (null != _UndoRoot)
                    {
                        this.AddFinalAction();
                        _UndoRoot.EndChangeSetBatch();
                    }

                    if (this.PostChangeAction != null)
                        this.PostChangeAction();
                }
            }

            /// <summary>
            /// Disposing this instance will end the associated Undo batch.
            /// </summary>
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            #endregion
        }

comments

nallenwagner wrote Feb 21, 2014 at 10:09 PM

Hi @ses4j,

Thanks for the suggestion here.

I have two thoughts, but maybe these don't meet your exact scenario...

1 - UndoRoot has two events on it that should fire any time an undo or a redo happen. If you hook these events, you could do the refresh as a response.

2 - The Change class will inspect the "target" of the undo to see if it implements ISupportUndoNotification. If so, it'll call UndoHappened or RedoHappened after applying the changes to that object.

I suspect that your "refresh" needs to touch something outside your objects that are being undone. If so, then the UndoRoot might be the best option. Does this make sense and/or meet your goals? If not, can you help me understand more about your scenario?

Thanks,
Nathan

ses4j wrote Feb 22, 2014 at 11:12 PM

If I understand correctly, neither of those hooks are convenient for me because I don't want it to happen on ALL undos or even all undos to a particular class. It's really just one particular change section.

In my case, that change is about radically resetting the data context of a WPF data grid on a file load-type operation. I want the whole load batched into a single undo operation, and I want the grid to rebind after the entire change occurs (or de-occurs).

Thanks for the reply!
Scott

nallenwagner wrote Feb 23, 2014 at 2:32 AM

Thanks @ses4j. That makes sense.

I think what you've put together here looks like a great solution. Thanks for sharing it. I'll see what I can do to include it in future releases of the library, if you'd like.

Nathan

ses4j wrote Feb 23, 2014 at 3:03 AM

Sure. Like I said, happy to make a pull request if you want. Would you prefer it as simply a patch to UndoBatch with additional optional arguments, or as a new standalone class?