(Submitted by ses4j on Codeplex)
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
}
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 6:12 PM [x]
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 22, 2014 at 9:32 PM [x]
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 22, 2014 at 10:03 PM [x]
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?