【问题标题】:Can I create an undo transaction in Word or Excel? (VSTO)我可以在 Word 或 Excel 中创建撤消事务吗? (VSTO)
【发布时间】:2010-10-14 06:14:40
【问题描述】:

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

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 中做一些事情会同时进行多项更改,如果用户必须单击撤消,这会有点烦人几次,如果他们犯了错误。尽管这些特定功能似乎不存在,但有谁知道是否/如何以某种方式完成?

【问题讨论】:

    标签: .net excel vba ms-word vsto


    【解决方案1】:

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

    【讨论】:

    • 这不是一个真正的“事务”——它只是让它看起来像撤销堆栈中的一个单一撤销操作——如果你在任何级别调用 .Undo all i> 嵌套的 UndoRecords 被 Undone 和 aborted,这很糟糕。
    【解决方案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();
    }
    

    【讨论】:

      【解决方案3】:

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

      调用撤消时,我们检查事务标记书签是否存在并重复撤消直到标记消失。重做的工作方式相同。此机制支持对文档内容所做的所有修改的事务性撤消/重做。但是,为了允许撤消/重做对文档属性的修改,需要使用 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
      

      【讨论】:

      • 哇!我想我会接受这个答案“是的,但它并不漂亮”!我想我只会在需要的时候建立它,现在不想搞太多。不过有趣的东西。
      • 有没有人让它在 Word 2007 中工作?我只是想从简单开始;我尝试将:“Sub EditUndo() / MsgBox ("Hello") / ActiveDocument.Undo / End Sub”添加到打开的文档(我也尝试将其保存为docm)或Normal.dotm。当我在 word 文档中按 control-z 时,这些尝试似乎都没有调用宏。帮忙?
      • @DGGenuine:覆盖EditUndo 命令应该也适用于 Word 2007 和 2010。宏需要位于当前文档或附加模板中的模块内。你确定你没有重新配置你的键盘快捷键吗?是否有任何其他活动的加载项与内置 Word 命令混淆?
      • @0xA3:我确信 Word 正在执行撤消操作,因为撤消操作实际上发生在文档中。但是我的 EditUndo 过程没有被执行。 Cindy Meister 建议在 Word 2007 中可能不会调用 EditUndo:social.msdn.microsoft.com/Forums/vi-VN/worddev/thread/… 也许该功能在某些版本的 Word 中被破坏(我使用的是 Word 2007 SP2 12.0.6545.5004)。奇怪的是,如果我用 EditRedo 尝试同样的方法,它会起作用。
      • @DCGenuine:解决方法可能如下:将EditUndo 重命名为EditUndo1,将Ctrl-ZAlt-Backspace 键盘快捷键分配给此宏,并替换功能区中的撤消按钮.
      【解决方案4】:

      Excel 在其 VBA 架构中具有一些(有限的)对撤消和重做的内置支持。

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

      【讨论】:

      • 谢谢,也看到了这个问题,虽然我并没有真正想到要在自己身上建立撤消,但看起来充满了危险。 VSTO 也基本上是 VBA++(反正我是这么想的),但在这方面我不认为它有任何额外的功能。
      猜你喜欢
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 2013-03-06
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多