我可以在Word或Excel中创建撤消事务吗? (VSTO)

时间:2022-02-21 20:42:32

I notice that Project 2007 has the functions that allow operations that can be undone to be placed in a single stack item, or "undo transaction". For example:

我注意到Project 2007具有允许可以撤消的操作放在单个堆栈项中的功能,或“撤消事务”。例如:

Application.OpenUndoTransaction "Create 6 tasks"
Dim i As Integer
For i = 1 To 6
    ActiveProject.Tasks.Add "UndoMe " & i
Next
Application.CloseUndoTransaction 

What this means is that the user can undo all of the actions in a single undo action, rather than 6 times.

这意味着用户可以在单个撤消操作中撤消所有操作,而不是6次。

This would be great to implement in Word and/or Excel, as I'm doing some things in VSTO that make multiple changes at once, and it'll be a bit annoying for the user if they have to click on Undo several times if they make a mistake. Although those specific functions don't appear to exist, does anyone know if / how this can be done in some way?

这很适合在Word和/或Excel中实现,因为我在VSTO中做了一些可以同时进行多项更改的东西,如果用户必须多次单击撤消,这对用户来说会有点烦人。他们犯了一个错误。虽然这些特定功能似乎不存在,但是有人知道是否/如何以某种方式完成这项工作?

4 个解决方案

#1


7  

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.

您可以通过覆盖VBA中的撤消和重做命令例程来模拟Word中的事务行为(我不认为仅使用VSTO可以覆盖内置Word命令)。通过添加书签来标记事务的开始,通过删除书签来标记结束。

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.

在调用undo时,我们检查事务标记书签是否存在并重复撤消,直到标记消失。重做工作方式相同。此机制支持对文档内容执行的所有修改的事务性撤消/重做。但是,要允许撤消/重做对文档属性的修改,需要使用SetCustomProp宏实现特殊机制。不应直接设置文档属性,只能通过此宏设置。

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 ;)

更新:我忘了清楚地提到这种方法仅适用于键盘快捷键和菜单命令,单击工具栏按钮仍然会执行单步撤消。因此,我们决定用自定义按钮替换工具栏按钮。该代码已经使用了很长一段时间使用Word 2003(它没有使用Word 2007进行测试,所以要做好准备应对惊喜;)

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

#2


4  

Word 2010 provides the ability to do this via the Application.UndoRecord object. See http://msdn.microsoft.com/en-us/library/hh128816.aspx

Word 2010提供了通过Application.UndoRecord对象执行此操作的功能。请参阅http://msdn.microsoft.com/en-us/library/hh128816.aspx

#3


2  

I've been chewing on this one for a while. Here's my attempt at using a hidden document, then grabbing the WordOpenXML from the hidden document and placing it in the real document when needed to make any amount of VSTO actions a single undo.

我一直在咀嚼这个。这是我尝试使用隐藏文档,然后从隐藏文档中抓取WordOpenXML并在需要时将其放入真实文档中以使任何数量的VSTO操作成为单个撤消。

//Usage from ThisDocument VSTO Document level project
public partial class ThisDocument
{   
    //Used to buffer writing text & formatting to document (to save undo stack)
    public static DocBuffer buffer;

    //Attached Template
    public static Word.Template template;

    private void ThisDocument_Startup(object sender, System.EventArgs e)
    {           
        //Ignore changes to template (removes prompt to save changes to template)
        template = (Word.Template)this.Application.ActiveDocument.get_AttachedTemplate();
        template.Saved = true;            

        //Document buffer
        buffer = new DocBuffer();

        //Start buffer
        ThisDocument.buffer.Start();

        //This becomes one "undo"
        Word.Selection curSel = Globals.ThisDocument.Application.Selection;
        curSel.TypeText(" ");
        curSel.TypeBackspace();
        curSel.Font.Bold = 1;
        curSel.TypeText("Hello, world!");
        curSel.Font.Bold = 0;
        curSel.TypeText(" ");

        //end buffer, print out text
        ThisDocument.buffer.End();
    }

    void Application_DocumentBeforeClose(Microsoft.Office.Interop.Word.Document Doc, ref bool Cancel)
    {
        buffer.Close();
    }

    private void ThisDocument_Shutdown(object sender, System.EventArgs e)
    {
        buffer.Close();         
    }
}

Here is the DocBuffer Class:

这是DocBuffer类:

public class DocBuffer
{
    //Word API Objects
    Word._Document HiddenDoc;
    Word.Selection curSel;
    Word.Template template;

    //ref parameters
    object missing = System.Type.Missing;
    object FalseObj = false; //flip this for docbuffer troubleshooting
    object templateObj;

    //Is docbuffer running?
    public Boolean started{ get; private set; }

    //Open document on new object
    public DocBuffer()
    {
        //Clear out unused buffer bookmarks
        Word.Bookmarks bookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        bookmarks.ShowHidden = true;

        foreach (Word.Bookmark mark in bookmarks)
        {
            if (mark.Name.Contains("_buf"))
            {
                mark.Delete();
            }
        }

        //Remove trail of undo's for clearing out the bookmarks
        Globals.ThisDocument.UndoClear();

        //Set up template
        template = ThisDocument.template;
        templateObj = template;

        //Open Blank document, then attach styles *and update
        HiddenDoc = Globals.ThisDocument.Application.Documents.Add(ref missing, ref missing, ref missing, ref FalseObj);
        HiddenDoc.set_AttachedTemplate(ref templateObj);
        HiddenDoc.UpdateStyles();

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Make primary document active
        Globals.ThisDocument.Activate();

    }

    ~DocBuffer()
    {
        try
        {
            HiddenDoc.Close(ref FalseObj, ref missing, ref missing);
        }
        catch { }
    }

    public void Close()
    {
        try
        {
            HiddenDoc.Close(ref FalseObj, ref missing, ref missing);
        }
        catch { }
    }

    public void Start()
    {
        try
        {
            //Make hidden document active to receive selection
            HiddenDoc.Activate(); //results in a slight application focus loss
        }
        catch (System.Runtime.InteropServices.COMException ex)
        {
            if (ex.Message == "Object has been deleted.")
            {
                //Open Blank document, then attach styles *and update
                HiddenDoc = Globals.ThisDocument.Application.Documents.Add(ref missing, ref missing, ref missing, ref FalseObj);
                HiddenDoc.set_AttachedTemplate(ref templateObj);
                HiddenDoc.UpdateStyles();
                HiddenDoc.Activate();
            }
            else
                throw;
        }

        //Remove Continue Bookmark, if exists
        Word.Bookmarks hiddenDocBookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        if (hiddenDocBookmarks.Exists("Continue"))
        {
            object deleteMarkObj = "Continue";
            Word.Bookmark deleteMark = hiddenDocBookmarks.get_Item(ref deleteMarkObj);
            deleteMark.Select();
            deleteMark.Delete();
        }

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Keep track when started
        started = true;
    }

    //Used for non-modal dialogs to bring active document back up between text insertion
    public void Continue()
    {
        //Exit quietly if buffer hasn't started
        if (!started) return;

        //Verify hidden document is active
        if ((HiddenDoc as Word.Document) != Globals.ThisDocument.Application.ActiveDocument)
        {
            HiddenDoc.Activate();
        }

        //Hidden doc selection
        curSel = Globals.ThisDocument.Application.Selection;

        //Hidden doc range
        Word.Range bufDocRange;

        //Select entire doc, save range
        curSel.WholeStory();
        bufDocRange = curSel.Range;

        //Find end, put a bookmark there
        bufDocRange.SetRange(curSel.End, curSel.End);
        object bookmarkObj = bufDocRange;

        //Generate "Continue" hidden bookmark
        Word.Bookmark mark = Globals.ThisDocument.Application.ActiveDocument.Bookmarks.Add("Continue", ref bookmarkObj);
        mark.Select();

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Make primary document active
        Globals.ThisDocument.Activate();
    }

    public void End()
    {
        //Exit quietly if buffer hasn't started
        if (!started) return;

        //Turn off buffer started flag
        started = false;

        //Verify hidden document is active
        if ((HiddenDoc as Word.Document) != Globals.ThisDocument.Application.ActiveDocument)
        {
            HiddenDoc.Activate();
        }

        //Remove Continue Bookmark, if exists
        Word.Bookmarks hiddenDocBookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        hiddenDocBookmarks.ShowHidden = true;
        if (hiddenDocBookmarks.Exists("Continue"))
        {
            object deleteMarkObj = "Continue";
            Word.Bookmark deleteMark = hiddenDocBookmarks.get_Item(ref deleteMarkObj);
            deleteMark.Delete();
        }

        //Hidden doc selection
        curSel = Globals.ThisDocument.Application.Selection;

        //Hidden doc range
        Word.Range hiddenDocRange;
        Word.Range bufDocRange;

        //Select entire doc, save range
        curSel.WholeStory();
        bufDocRange = curSel.Range;

        //If cursor bookmark placed in, move there, else find end of text, put a bookmark there
        Boolean cursorFound = false;
        if (hiddenDocBookmarks.Exists("_cursor"))
        {
            object cursorBookmarkObj = "_cursor";
            Word.Bookmark cursorBookmark = hiddenDocBookmarks.get_Item(ref cursorBookmarkObj);
            bufDocRange.SetRange(cursorBookmark.Range.End, cursorBookmark.Range.End);
            cursorBookmark.Delete();
            cursorFound = true;
        }
        else
        {
            //The -2 is done because [range object].WordOpenXML likes to drop bookmarks at the end of the range
            bufDocRange.SetRange(curSel.End - 2, curSel.End - 2);
        }

        object bookmarkObj = bufDocRange;

        //Generate GUID for hidden bookmark
        System.Guid guid = System.Guid.NewGuid();
        String id = "_buf" + guid.ToString().Replace("-", string.Empty);
        Word.Bookmark mark = Globals.ThisDocument.Application.ActiveDocument.Bookmarks.Add(id, ref bookmarkObj);

        //Get OpenXML Text (Text with formatting)
        curSel.WholeStory();
        hiddenDocRange = curSel.Range;
        string XMLText = hiddenDocRange.WordOpenXML;

        //Clear out contents of buffer
        hiddenDocRange.Delete(ref missing, ref missing); //comment this for docbuffer troubleshooting

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Make primary document active
        Globals.ThisDocument.Activate();

        //Get selection from new active document
        curSel = Globals.ThisDocument.Application.Selection;

        //insert buffered formatted text into main document
        curSel.InsertXML(XMLText, ref missing);

        //Place cursor at bookmark+1 (this is done due to WordOpenXML ignoring bookmarks at the end of the selection)
        Word.Bookmarks bookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        bookmarks.ShowHidden = true;

        object stringObj = id;
        Word.Bookmark get_mark = bookmarks.get_Item(ref stringObj);
        bufDocRange = get_mark.Range;

        if (cursorFound) //Canned language actively placed cursor
            bufDocRange.SetRange(get_mark.Range.End, get_mark.Range.End);
        else //default cursor at the end of text
            bufDocRange.SetRange(get_mark.Range.End + 1, get_mark.Range.End + 1);
        bufDocRange.Select();
}

#4


1  

Excel has some (limited) built-in support for undo and redo as part of its VBA architecture.

作为VBA架构的一部分,Excel对undo和redo有一些(有限的)内置支持。

I'm not familiar with vsto, so I don't know if this will help you out, but you can take a look at this SO question for more details.

我不熟悉vsto,所以我不知道这是否会帮助你,但你可以看一下这个问题以获取更多细节。

#1


7  

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.

您可以通过覆盖VBA中的撤消和重做命令例程来模拟Word中的事务行为(我不认为仅使用VSTO可以覆盖内置Word命令)。通过添加书签来标记事务的开始,通过删除书签来标记结束。

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.

在调用undo时,我们检查事务标记书签是否存在并重复撤消,直到标记消失。重做工作方式相同。此机制支持对文档内容执行的所有修改的事务性撤消/重做。但是,要允许撤消/重做对文档属性的修改,需要使用SetCustomProp宏实现特殊机制。不应直接设置文档属性,只能通过此宏设置。

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 ;)

更新:我忘了清楚地提到这种方法仅适用于键盘快捷键和菜单命令,单击工具栏按钮仍然会执行单步撤消。因此,我们决定用自定义按钮替换工具栏按钮。该代码已经使用了很长一段时间使用Word 2003(它没有使用Word 2007进行测试,所以要做好准备应对惊喜;)

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

#2


4  

Word 2010 provides the ability to do this via the Application.UndoRecord object. See http://msdn.microsoft.com/en-us/library/hh128816.aspx

Word 2010提供了通过Application.UndoRecord对象执行此操作的功能。请参阅http://msdn.microsoft.com/en-us/library/hh128816.aspx

#3


2  

I've been chewing on this one for a while. Here's my attempt at using a hidden document, then grabbing the WordOpenXML from the hidden document and placing it in the real document when needed to make any amount of VSTO actions a single undo.

我一直在咀嚼这个。这是我尝试使用隐藏文档,然后从隐藏文档中抓取WordOpenXML并在需要时将其放入真实文档中以使任何数量的VSTO操作成为单个撤消。

//Usage from ThisDocument VSTO Document level project
public partial class ThisDocument
{   
    //Used to buffer writing text & formatting to document (to save undo stack)
    public static DocBuffer buffer;

    //Attached Template
    public static Word.Template template;

    private void ThisDocument_Startup(object sender, System.EventArgs e)
    {           
        //Ignore changes to template (removes prompt to save changes to template)
        template = (Word.Template)this.Application.ActiveDocument.get_AttachedTemplate();
        template.Saved = true;            

        //Document buffer
        buffer = new DocBuffer();

        //Start buffer
        ThisDocument.buffer.Start();

        //This becomes one "undo"
        Word.Selection curSel = Globals.ThisDocument.Application.Selection;
        curSel.TypeText(" ");
        curSel.TypeBackspace();
        curSel.Font.Bold = 1;
        curSel.TypeText("Hello, world!");
        curSel.Font.Bold = 0;
        curSel.TypeText(" ");

        //end buffer, print out text
        ThisDocument.buffer.End();
    }

    void Application_DocumentBeforeClose(Microsoft.Office.Interop.Word.Document Doc, ref bool Cancel)
    {
        buffer.Close();
    }

    private void ThisDocument_Shutdown(object sender, System.EventArgs e)
    {
        buffer.Close();         
    }
}

Here is the DocBuffer Class:

这是DocBuffer类:

public class DocBuffer
{
    //Word API Objects
    Word._Document HiddenDoc;
    Word.Selection curSel;
    Word.Template template;

    //ref parameters
    object missing = System.Type.Missing;
    object FalseObj = false; //flip this for docbuffer troubleshooting
    object templateObj;

    //Is docbuffer running?
    public Boolean started{ get; private set; }

    //Open document on new object
    public DocBuffer()
    {
        //Clear out unused buffer bookmarks
        Word.Bookmarks bookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        bookmarks.ShowHidden = true;

        foreach (Word.Bookmark mark in bookmarks)
        {
            if (mark.Name.Contains("_buf"))
            {
                mark.Delete();
            }
        }

        //Remove trail of undo's for clearing out the bookmarks
        Globals.ThisDocument.UndoClear();

        //Set up template
        template = ThisDocument.template;
        templateObj = template;

        //Open Blank document, then attach styles *and update
        HiddenDoc = Globals.ThisDocument.Application.Documents.Add(ref missing, ref missing, ref missing, ref FalseObj);
        HiddenDoc.set_AttachedTemplate(ref templateObj);
        HiddenDoc.UpdateStyles();

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Make primary document active
        Globals.ThisDocument.Activate();

    }

    ~DocBuffer()
    {
        try
        {
            HiddenDoc.Close(ref FalseObj, ref missing, ref missing);
        }
        catch { }
    }

    public void Close()
    {
        try
        {
            HiddenDoc.Close(ref FalseObj, ref missing, ref missing);
        }
        catch { }
    }

    public void Start()
    {
        try
        {
            //Make hidden document active to receive selection
            HiddenDoc.Activate(); //results in a slight application focus loss
        }
        catch (System.Runtime.InteropServices.COMException ex)
        {
            if (ex.Message == "Object has been deleted.")
            {
                //Open Blank document, then attach styles *and update
                HiddenDoc = Globals.ThisDocument.Application.Documents.Add(ref missing, ref missing, ref missing, ref FalseObj);
                HiddenDoc.set_AttachedTemplate(ref templateObj);
                HiddenDoc.UpdateStyles();
                HiddenDoc.Activate();
            }
            else
                throw;
        }

        //Remove Continue Bookmark, if exists
        Word.Bookmarks hiddenDocBookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        if (hiddenDocBookmarks.Exists("Continue"))
        {
            object deleteMarkObj = "Continue";
            Word.Bookmark deleteMark = hiddenDocBookmarks.get_Item(ref deleteMarkObj);
            deleteMark.Select();
            deleteMark.Delete();
        }

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Keep track when started
        started = true;
    }

    //Used for non-modal dialogs to bring active document back up between text insertion
    public void Continue()
    {
        //Exit quietly if buffer hasn't started
        if (!started) return;

        //Verify hidden document is active
        if ((HiddenDoc as Word.Document) != Globals.ThisDocument.Application.ActiveDocument)
        {
            HiddenDoc.Activate();
        }

        //Hidden doc selection
        curSel = Globals.ThisDocument.Application.Selection;

        //Hidden doc range
        Word.Range bufDocRange;

        //Select entire doc, save range
        curSel.WholeStory();
        bufDocRange = curSel.Range;

        //Find end, put a bookmark there
        bufDocRange.SetRange(curSel.End, curSel.End);
        object bookmarkObj = bufDocRange;

        //Generate "Continue" hidden bookmark
        Word.Bookmark mark = Globals.ThisDocument.Application.ActiveDocument.Bookmarks.Add("Continue", ref bookmarkObj);
        mark.Select();

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Make primary document active
        Globals.ThisDocument.Activate();
    }

    public void End()
    {
        //Exit quietly if buffer hasn't started
        if (!started) return;

        //Turn off buffer started flag
        started = false;

        //Verify hidden document is active
        if ((HiddenDoc as Word.Document) != Globals.ThisDocument.Application.ActiveDocument)
        {
            HiddenDoc.Activate();
        }

        //Remove Continue Bookmark, if exists
        Word.Bookmarks hiddenDocBookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        hiddenDocBookmarks.ShowHidden = true;
        if (hiddenDocBookmarks.Exists("Continue"))
        {
            object deleteMarkObj = "Continue";
            Word.Bookmark deleteMark = hiddenDocBookmarks.get_Item(ref deleteMarkObj);
            deleteMark.Delete();
        }

        //Hidden doc selection
        curSel = Globals.ThisDocument.Application.Selection;

        //Hidden doc range
        Word.Range hiddenDocRange;
        Word.Range bufDocRange;

        //Select entire doc, save range
        curSel.WholeStory();
        bufDocRange = curSel.Range;

        //If cursor bookmark placed in, move there, else find end of text, put a bookmark there
        Boolean cursorFound = false;
        if (hiddenDocBookmarks.Exists("_cursor"))
        {
            object cursorBookmarkObj = "_cursor";
            Word.Bookmark cursorBookmark = hiddenDocBookmarks.get_Item(ref cursorBookmarkObj);
            bufDocRange.SetRange(cursorBookmark.Range.End, cursorBookmark.Range.End);
            cursorBookmark.Delete();
            cursorFound = true;
        }
        else
        {
            //The -2 is done because [range object].WordOpenXML likes to drop bookmarks at the end of the range
            bufDocRange.SetRange(curSel.End - 2, curSel.End - 2);
        }

        object bookmarkObj = bufDocRange;

        //Generate GUID for hidden bookmark
        System.Guid guid = System.Guid.NewGuid();
        String id = "_buf" + guid.ToString().Replace("-", string.Empty);
        Word.Bookmark mark = Globals.ThisDocument.Application.ActiveDocument.Bookmarks.Add(id, ref bookmarkObj);

        //Get OpenXML Text (Text with formatting)
        curSel.WholeStory();
        hiddenDocRange = curSel.Range;
        string XMLText = hiddenDocRange.WordOpenXML;

        //Clear out contents of buffer
        hiddenDocRange.Delete(ref missing, ref missing); //comment this for docbuffer troubleshooting

        //Tell hidden document it has been saved to remove rare prompt to save document
        HiddenDoc.Saved = true;

        //Make primary document active
        Globals.ThisDocument.Activate();

        //Get selection from new active document
        curSel = Globals.ThisDocument.Application.Selection;

        //insert buffered formatted text into main document
        curSel.InsertXML(XMLText, ref missing);

        //Place cursor at bookmark+1 (this is done due to WordOpenXML ignoring bookmarks at the end of the selection)
        Word.Bookmarks bookmarks = Globals.ThisDocument.Application.ActiveDocument.Bookmarks;
        bookmarks.ShowHidden = true;

        object stringObj = id;
        Word.Bookmark get_mark = bookmarks.get_Item(ref stringObj);
        bufDocRange = get_mark.Range;

        if (cursorFound) //Canned language actively placed cursor
            bufDocRange.SetRange(get_mark.Range.End, get_mark.Range.End);
        else //default cursor at the end of text
            bufDocRange.SetRange(get_mark.Range.End + 1, get_mark.Range.End + 1);
        bufDocRange.Select();
}

#4


1  

Excel has some (limited) built-in support for undo and redo as part of its VBA architecture.

作为VBA架构的一部分,Excel对undo和redo有一些(有限的)内置支持。

I'm not familiar with vsto, so I don't know if this will help you out, but you can take a look at this SO question for more details.

我不熟悉vsto,所以我不知道这是否会帮助你,但你可以看一下这个问题以获取更多细节。