2009-03-19 24 views
6

Tôi nhận thấy rằng Project 2007 có các chức năng cho phép các thao tác có thể được hoàn tác để được đặt trong một mục ngăn xếp đơn lẻ hoặc "hoàn tác giao dịch". For example:Tôi có thể tạo một giao dịch hoàn tác trong Word hoặc Excel không? (VSTO)

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

Điều này có nghĩa là người dùng có thể hoàn tác tất cả các hành động trong một thao tác hoàn tác, thay vì 6 lần. Điều này sẽ là tuyệt vời để thực hiện trong Word và/hoặc Excel, như tôi đang làm một số điều trong VSTO mà thực hiện nhiều thay đổi cùng một lúc, và nó sẽ có một chút gây phiền nhiễu cho người dùng nếu họ phải bấm vào Hoàn tác nhiều lần nếu họ mắc lỗi. Mặc dù các chức năng cụ thể đó dường như không tồn tại, có ai biết nếu/làm thế nào điều này có thể được thực hiện theo một cách nào đó?

Trả lời

7

Bạn có thể mô phỏng hành vi giao dịch trong Word bằng cách ghi đè lệnh thường trình Hoàn tác và Làm lại trong VBA (Tôi không nghĩ rằng việc ghi đè các lệnh Word tích hợp có thể sử dụng VSTO một mình). Bắt đầu giao dịch được đánh dấu bằng cách thêm dấu trang, kết thúc được đánh dấu bằng cách xóa dấu trang.

Khi gọi hoàn tác, chúng tôi kiểm tra xem dấu trang giao dịch có tồn tại không và lặp lại hoàn tác cho đến khi điểm đánh dấu biến mất. Làm lại là làm việc theo cùng một cách. Cơ chế này hỗ trợ giao dịch undo/redo của tất cả các sửa đổi được thực hiện cho nội dung tài liệu. Tuy nhiên, để cho phép hoàn tác/làm lại các sửa đổi đối với các thuộc tính của tài liệu, cần phải thực hiện một cơ chế đặc biệt bằng cách sử dụng macro SetCustomProp. Thuộc tính tài liệu không nên được đặt trực tiếp mà chỉ thông qua macro này.

Cập nhật: Tôi quên đề cập rõ ràng rằng cách tiếp cận này chỉ hoạt động với các phím tắt và các lệnh của trình đơn, nhấp vào nút thanh công cụ vẫn thực hiện một bước hoàn tác. Do đó, chúng tôi quyết định thay thế các nút trên thanh công cụ bằng các nút tùy chỉnh. Mã này đã được sử dụng trong một thời gian dài Với Word 2003 (nó không được thử nghiệm với Word 2007, vì vậy hãy chuẩn bị cho sự ngạc nhiên;)

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 
+0

Wow! Tôi nghĩ tôi sẽ trả lời "có, nhưng nó không đẹp"! Tôi nghĩ rằng tôi sẽ xây dựng mà chỉ khi tôi cần, không muốn mess về quá nhiều cho bây giờ. Thú vị thứ mặc dù. – Gavin

+0

Có ai nhận được điều này để làm việc trong Word 2007 không? Tôi chỉ đang cố bắt đầu đơn giản; Tôi thử thêm: "Sub EditUndo()/MsgBox (" Hello ")/ActiveDocument.Undo/End Sub" vào một trong hai tài liệu đang mở (tôi đã thử lưu nó dưới dạng docm, quá) hoặc Normal.dotm.Không có nỗ lực nào trong số những lần thử này dường như gọi macro khi tôi nhấn control-z trong tài liệu từ. Cứu giúp? –

+0

@DGGenuine: Ghi đè lệnh 'EditUndo' cũng sẽ hoạt động trong Word 2007 và 2010. Macro cần nằm trong mô-đun trong tài liệu hiện tại hoặc trong mẫu được đính kèm. Bạn có chắc chắn rằng bạn chưa cấu hình lại phím tắt của mình? Có bất kỳ add-in khác hoạt động lộn xộn xung quanh với các lệnh Word tích hợp không? –

1

Excel có một số hỗ trợ được xây dựng sẵn (có giới hạn) để hoàn tác và làm lại như là một phần của kiến ​​trúc VBA của nó.

Tôi không quen với vsto, vì vậy tôi không biết nếu điều này sẽ giúp bạn ra ngoài, nhưng bạn có thể xem this SO question để biết thêm chi tiết.

+0

Cảm ơn, đã xem câu hỏi đó, không thực sự ưa thích ý nghĩ xây dựng hoàn tác trong bản thân mình, trông có vẻ đầy nguy hiểm. Ngoài ra VSTO về cơ bản là VBA ++ (đó là cách tôi muốn nghĩ về nó anyway), nhưng trong vấn đề này tôi không nghĩ rằng nó có thêm bất kỳ khả năng nào. – Gavin

2

Tôi đã nhai cái này một lúc. Đây là nỗ lực của tôi khi sử dụng một tài liệu ẩn, sau đó lấy WordOpenXML từ tài liệu ẩn và đặt nó vào tài liệu thực khi cần thiết để thực hiện bất kỳ số lượng hành động VSTO nào một lần hoàn tác.

//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();   
    } 
} 

Đây là lớp 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

Word 2010 cung cấp khả năng để làm điều này thông qua đối tượng Application.UndoRecord. Xem http://msdn.microsoft.com/en-us/library/hh128816.aspx

+0

Đó không phải là "giao dịch" thực sự - chỉ là đường để làm cho nó trông giống như một undo hoạt động trong ngăn xếp Hoàn tác - nếu bạn gọi .Undo ở mọi cấp độ * tất cả * của UndoRecords lồng nhau được Undone và hủy bỏ, nó khá lame. – BrainSlugs83

Các vấn đề liên quan