It's a thing which annoyed me a little until I found out that users of later (2010 and on) versions of M/S Word than mine could do it. Then, while it still would never have featured anywhere near my top ten list of Most Annoying Things Ever, the green-eyed god on my be-chipped shoulder certainly hoisted it several feet further up the flag-pole of pesky annoyances….
If you have a macro which makes multiple changes, each of them is added to the undo stack separately, which means, obviously, that you have to hit the undo button or
Ctrl+Z multiple times to undo those changes. Which is kind of a bugger if you're unsure how many changes it actually made, either 'cause you've forgotten the workings of the macro or 'cause there's a few
if…then clauses in there.
Those rich gits wot could afford to upgrade to the later versions have the option to include an instruction for Word to treat the end result of the macro as a single undo function. One click and the whole lot's undone. Those of us with the earlier versions (mine's of 2003 vintage) can't. Which, of course, got me looking for a workaround. I did come up with one, but it involves switching to a second document, pasting in the text to be worked on, pasting it back into the original doc and closing the second. It's useful for some things, but for relatively simple changes it's often more trouble than it's worth. But now, if you haven't already guessed where this is headed, I've come up with a solution which is far less hassle to implement.
So okay, back to square one, taking a rather simple example. If we run this macro, which changes the selected text to red, italic, monospace…
Sub ChangeStuff() With Selection.Font .Name = "Courier New" .italic = True .Color = wdColorRed End With End Sub
… we'll end up with three items in the undo stack:
So this sneaky thought popped into my head: What if we told it to make the changes, copy the result, undo the changes, and paste the copied new version over the top of the still-selected old version? And—gadzooks!—it worked:
Sub ChangeStuff() With Selection.Font .Name = "Courier New" .italic = True .Color = wdColorRed End With Selection.Copy Application.ActiveDocument.Undo 3 Selection.Paste End Sub
A minor drawback is that the change is now labelled in the stack as a paste operation, but I seldom use the choose-an-undo-point drop-down anyway (I just keep hitting
Ctrl+Z until I get back to where I want to be), so for me that's not really a problem.
A more major problem is that it only works as-is, if we know for certain how many changes will be made. If there's an
if…then or two thrown in, or if the macro is performing a
find next loop, it's possible we might not be able to predict the number of changes. Here's that simple example again, but with a further formatting change which may or may not be implemented, depending on the length of the selected string:
Sub ChangeVariableStuff() With Selection.Font .Name = "Courier New" .italic = True .Color = wdColorRed End With If Len(Selection) > 10 Then Selection.Font.Size = "16" End If Selection.Copy Application.ActiveDocument.Undo ???????? Selection.Paste End Sub
My first thought was that a simple counter could be added to any macro I wanted to use the workaround on; then I realised that if the macro called other macros up in the course of its work, which themselves might cause unpredictable numbers of changes, I'd be into a whole nother realm of complication. Oh dear. But then I remembered a snippet of code I'd snagged from a message board at some point, played with for a while, and then consigned to a text document in case I ever found a use for it.
Well, Gentle Reader, I just found a use for it.
It's a function (and apologies for not linking the source—I never kept a note of it) which reports back the number of items currently in the undo stack. Put that together with the macro we already have, and we get this:
Function CountUndoStack() Dim myC As CommandBarComboBox On Error GoTo EmptyStack Set myC = CommandBars.FindControl(id:=128) CountUndoStack = myC.ListCount Exit Function EmptyStack: CountUndoStack = 0 End Function Sub ChangeVariableStuff() Dim iStack As Integer iStack = CountUndoStack With Selection.Font .Name = "Courier New" .italic = True .Color = wdColorRed End With If Len(Selection) > 10 Then Selection.Font.Size = "16" End If Selection.Copy Application.ActiveDocument.Undo CountUndoStack - iStack Selection.Paste End Sub
So now what we have is a macro which begins by calling up a function to get the number of items in the stack, makes the necessary changes, copies the changed text, calls the function up again, finds out how many times it needs to undo by taking the difference between the two counts, undoes that many operations, then, as earlier, pastes the new version of the text over the old version; thus making multiple changes which may be undone with a single click, effectively treating the entire operation of the macro as one single operation, just like the option in the 2010 onwards versions. Nice!
Well, nearly nice. It still removes whatever was on the clipboard before we began, and replaces it with whatever the string was we were working with. Not a heartbreaking problem, but it'd be nice if it didn't do it. Well, provided what was on the clipboard previously was text, we can do so, although we will, unfortunately, remove any formatting from the clipboard's contents. Still, better than nowt I suppose. Keep the previous version plus the function, and add this sub-routine [source] (which would be the one we'd actually point our button and/or keyboard combo at):
Sub ChangeStuffPreserveClpBrd() On Error Resume Next Dim MyData As DataObject Dim sClip As String Set MyData = New DataObject MyData.GetFromClipboard sClip = MyData.GetText ChangeVariableStuff Dim MyData2 As DataObject Dim sClip2 As String sClip2 = sClip Set MyData2 = New DataObject MyData2.SetText sClip2 MyData2.PutInClipboard End Sub
On Error line takes care of problems which arise should the contents of the clipboard be something other than text. Unfortunately, if such is the case, the clipboard will be empty at the end of the operation, so we'd have to re-copy the picture or whatever it was we'd previously copied; but if t'were filled with text at the beginning, it'd be filled with the same text (though unformatted) at the end.
The only other real drawback is that this only works if, at the end of the machinations of the main macro, the changed text is either still selected or can easily be reselected. If it isn't or can't though, it should still be possible to select the entire paragraph or, failing that, the entire document, to copy the new version, and then again to paste it over the old. In either case, we'd still end up with a one-click undo.
And that, laddies and ladies, is just about me lot. As ever, I hope this will be of use to someone.
You may use these HTML tags in comments
<a href="" title=""></a> <abbr title=""></abbr>
<acronym title=""></acronym> <blockquote></blockquote> <del></del>* <strike></strike>† <em></em>* <i></i>† <strong></strong>* <b></b>†
* is generally preferred over †