You can simulate transactional behavior in Word by overwriting the Undo and Redo command routines in VBA (I don't think that overwriting built-in Word commands is possible using VSTO alone, though). The start of a transaction is marked by adding a bookmark, the end is marked by removing the bookmark.
When calling undo, we check whether the transaction mark bookmark is present and repeat the undo until the marker is gone. Redo is working the same way. This mechanism supports transactional undo/redo of all modifications done to the document content. However, to allow undo/redo of modifications to the document properties a special mechanism needs to be implemented using the SetCustomProp macro. Document properties should not be set directly but via this macro only.
Update: I forgot to clearly mention that this approach only works with the keyboard shortcuts and the menu commands, clicking the toolbar button still does a single-step undo. We therefore decided to replace the toolbar buttons with custom ones. The code has been in use for quite a while With Word 2003 (it's not tested with Word 2007, so be prepared for surprise ;)
Option Explicit
' string constants for Undo mechanism
Public Const BM_IN_MACRO As String = "_InMacro_"
Public Const BM_DOC_PROP_CHANGE As String = "_DocPropChange_"
Public Const BM_DOC_PROP_NAME As String = "_DocPropName_"
Public Const BM_DOC_PROP_OLD_VALUE As String = "_DocPropOldValue_"
Public Const BM_DOC_PROP_NEW_VALUE As String = "_DocPropNewValue_"
'-----------------------------------------------------------------------------------
' Procedure : EditUndo
' Purpose : Atomic undo of macros
' Note: This macro only catches the menu command and the keyboard shortcut,
' not the toolbar command
'-----------------------------------------------------------------------------------
Public Sub EditUndo() ' Catches Ctrl-Z
'On Error Resume Next
Dim bRefresh As Boolean
bRefresh = Application.ScreenUpdating
Application.ScreenUpdating = False
Do
If ActiveDocument.Bookmarks.Exists(BM_DOC_PROP_CHANGE) Then
Dim strPropName As String
Dim strOldValue As String
strPropName = ActiveDocument.Bookmarks(BM_DOC_PROP_NAME).Range.Text
strOldValue = ActiveDocument.Bookmarks(BM_DOC_PROP_OLD_VALUE).Range.Text
ActiveDocument.CustomDocumentProperties(strPropName).Value = strOldValue
End If
Loop While (ActiveDocument.Undo = True) _
And ActiveDocument.Bookmarks.Exists(BM_IN_MACRO)
Application.ScreenUpdating = bRefresh
End Sub
'-----------------------------------------------------------------------------------
' Procedure : EditRedo
' Purpose : Atomic redo of macros
' Note: This macro only catches the menu command and the keyboard shortcut,
' not the toolbar command
'-----------------------------------------------------------------------------------
Public Sub EditRedo() ' Catches Ctrl-Y
Dim bRefresh As Boolean
bRefresh = Application.ScreenUpdating
Application.ScreenUpdating = False
Do
If ActiveDocument.Bookmarks.Exists(BM_DOC_PROP_CHANGE) Then
Dim strPropName As String
Dim strNewValue As String
strPropName = ActiveDocument.Bookmarks(BM_DOC_PROP_NAME).Range.Text
strNewValue = ActiveDocument.Bookmarks(BM_DOC_PROP_NEW_VALUE).Range.Text
ActiveDocument.CustomDocumentProperties(strPropName).Value = strNewValue
End If
Loop While (ActiveDocument.Redo = True) _
And ActiveDocument.Bookmarks.Exists(BM_IN_MACRO)
Application.ScreenUpdating = bRefresh
End Sub
'-----------------------------------------------------------------------------------
' Procedure : SetCustomProp
' Purpose : Sets a custom document property
'-----------------------------------------------------------------------------------
Public Function SetCustomProp(oDoc As Document, strName As String, strValue As String)
Dim strOldValue As String
On Error GoTo existsAlready
strOldValue = ""
oDoc.CustomDocumentProperties.Add _
Name:=strName, LinkToContent:=False, Value:=Trim(strValue), _
Type:=msoPropertyTypeString
GoTo exitHere
existsAlready:
strOldValue = oDoc.CustomDocumentProperties(strName).Value
oDoc.CustomDocumentProperties(strName).Value = strValue
exitHere:
' support undo / redo of changes to the document properties
'On Error Resume Next
Dim bCalledWithoutUndoSupport As Boolean
If Not ActiveDocument.Bookmarks.Exists(BM_IN_MACRO) Then
ActiveDocument.Range.Bookmarks.Add BM_IN_MACRO, ActiveDocument.Range
bCalledWithoutUndoSupport = True
End If
Dim oRange As Range
Set oRange = ActiveDocument.Range
oRange.Collapse wdCollapseEnd
oRange.Text = " "
oRange.Bookmarks.Add "DocPropDummy_", oRange
oRange.Collapse wdCollapseEnd
oRange.Text = strName
oRange.Bookmarks.Add BM_DOC_PROP_NAME, oRange
oRange.Collapse wdCollapseEnd
oRange.Text = strOldValue
oRange.Bookmarks.Add BM_DOC_PROP_OLD_VALUE, oRange
oRange.Collapse wdCollapseEnd
oRange.Text = strValue
oRange.Bookmarks.Add BM_DOC_PROP_NEW_VALUE, oRange
oRange.Bookmarks.Add BM_DOC_PROP_CHANGE
ActiveDocument.Bookmarks(BM_DOC_PROP_CHANGE).Delete
Set oRange = ActiveDocument.Bookmarks(BM_DOC_PROP_NEW_VALUE).Range
ActiveDocument.Bookmarks(BM_DOC_PROP_NEW_VALUE).Delete
If Len(oRange.Text) > 0 Then oRange.Delete
Set oRange = ActiveDocument.Bookmarks(BM_DOC_PROP_OLD_VALUE).Range
ActiveDocument.Bookmarks(BM_DOC_PROP_OLD_VALUE).Delete
If Len(oRange.Text) > 0 Then oRange.Delete
Set oRange = ActiveDocument.Bookmarks(BM_DOC_PROP_NAME).Range
ActiveDocument.Bookmarks(BM_DOC_PROP_NAME).Delete
If Len(oRange.Text) > 0 Then oRange.Delete
Set oRange = ActiveDocument.Bookmarks("DocPropDummy_").Range
ActiveDocument.Bookmarks("DocPropDummy_").Delete
If Len(oRange.Text) > 0 Then oRange.Delete
If bCalledWithoutUndoSupport And ActiveDocument.Bookmarks.Exists(BM_IN_MACRO) Then
ActiveDocument.Bookmarks(BM_IN_MACRO).Delete
End If
End Function
'-----------------------------------------------------------------------------------
' Procedure : SampleUsage
' Purpose : Demonstrates a transaction
'-----------------------------------------------------------------------------------
Private Sub SampleUsage()
On Error Resume Next
' mark begin of transaction
ActiveDocument.Range.Bookmarks.Add BM_IN_MACRO
Selection.Text = "Hello World"
' do other stuff
' mark end of transaction
ActiveDocument.Bookmarks(BM_IN_MACRO).Delete
End Sub
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…