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

我注意到Project 2007的function允许撤销的操作被放置在单个堆栈项目中,或“撤销事务”。 例如:

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,一次做多个更改,这将是一个恼人的用户,如果他们必须点击撤消多次,如果他们犯了一个错误。 虽然这些特定的function似乎不存在,有没有人知道是否/如何以某种方式做到这一点?

你可以通过在VBA中覆盖Undo和Redo命令例程来模拟Word中的事务行为(尽pipe如此,我不认为用VSTO单独覆盖内置的Word命令是可能的)。 通过添加书签来标记事务的开始,结束时通过删除书签来标记。

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

更新:我忘了清楚地提到,这种方法只适用于键盘快捷键和菜单命令,点击工具栏button仍然是单步撤消。 因此,我们决定用自定义工具栏replace工具栏button。 这个代码已经在Word 2003中使用了很长一段时间了(它没有在Word 2007中testing过,所以要做好准备);

 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提供了通过Application.UndoRecord对象执行此操作的function。 请参阅http://msdn.microsoft.com/en-us/library/hh128816.aspx

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

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

我不熟悉VSTO,所以我不知道这是否会帮助你,但你可以看看这个SO问题的更多细节。