6

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

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

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

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

4

4 回答 4

7

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

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

更新:我忘了明确提到这种方法只适用于键盘快捷键和菜单命令,单击工具栏按钮仍然可以单步撤消。因此,我们决定将工具栏按钮替换为自定义按钮。该代码已经在 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
于 2009-03-19T07:23:49.333 回答
4

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

于 2012-02-07T22:06:10.417 回答
2

我一直在咀嚼这个有一段时间了。这是我尝试使用隐藏文档,然后从隐藏文档中获取 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();         
    }
}

这是 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();
}
于 2009-07-21T05:59:20.587 回答
1

作为其 VBA 体系结构的一部分,Excel 具有一些(有限的)对撤消和重做的内置支持。

我不熟悉 vsto,所以我不知道这是否会对您有所帮助,但您可以查看这个 SO question了解更多详细信息。

于 2009-03-19T07:20:14.913 回答