Можно ли создать транзакцию отмены в Word или Excel? (VSTO)

Я замечаю, что 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 ответов


вы можете имитировать транзакционное поведение в Word, перезаписывая процедуры отмены и повтора в VBA (я не думаю, что перезапись встроенных команд Word возможна только с помощью VSTO). Начало транзакции помечается добавлением закладки, конец-удалением закладки.

при вызове отмены мы проверяем, присутствует ли закладка метки транзакции и повторяем отмену, пока маркер не исчезнет. Повтор работает точно так же. Этот механизм поддерживает транзакционную отмену / повтор всех изменений, внесенных в содержимое документа. Однако, чтобы разрешить отмену / повтор изменений свойств документа, необходимо реализовать специальный механизм с помощью макроса SetCustomProp. Свойства документа не должны устанавливаться напрямую, а только через этот макрос.

Update: я забыл четко упомянуть, что этот подход работает только с сочетаниями клавиш и командами меню, нажатие кнопки панели инструментов все еще делает одношаговую отмену. Мы поэтому решил заменить кнопки панели инструментов на пользовательские. Код довольно долго использовался в 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

Word 2010 предоставляет возможность сделать это с помощью приложение.Undorecord с


я жевал это на некоторое время. Вот моя попытка использовать скрытый документ, а затем захватить 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();
}

Excel имеет некоторую (ограниченную) встроенную поддержку отмены и повтора как часть своей архитектуры VBA.

Я не знаком с vsto, поэтому я не знаю, поможет ли это вам, но вы можете взглянуть на это так вопрос для получения более подробной информации.