2009-03-19 8 views
6

Ho notato che Project 2007 ha le funzioni che consentono operazioni che possono essere annullate per essere inserite in un singolo elemento dello stack, o "annulla transazione". For example:Posso creare una transazione di annullamento in Word o Excel? (VSTO)

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

Ciò significa che l'utente può annullare tutte le azioni in una singola operazione annullata, anziché 6 volte.

Questo sarebbe ottimo per implementare in Word e/o Excel, poiché sto facendo alcune cose in VSTO che apportano più modifiche contemporaneamente, e sarà un po 'fastidioso per l'utente se devono fare clic su Annulla più volte se commettono un errore. Anche se queste funzioni specifiche non sembrano esistere, qualcuno sa se/come questo può essere fatto in qualche modo?

risposta

7

È possibile simulare il comportamento delle transazioni in Word sovrascrivendo le routine di comando Annulla e Ripristina in VBA (non penso che sovrascrivere i comandi di Word incorporati sia possibile utilizzando solo VSTO, però). L'inizio di una transazione è contrassegnato dall'aggiunta di un segnalibro, la fine è contrassegnata dalla rimozione del segnalibro.

Durante la chiamata di annullamento, controlliamo se il segnalibro del segno di transazione è presente e ripetiamo l'annullamento fino a quando il marcatore non è andato. Redo funziona allo stesso modo. Questo meccanismo supporta l'annullamento/ripristino delle transazioni di tutte le modifiche apportate al contenuto del documento. Tuttavia, per consentire l'annullamento/ripristino delle modifiche alle proprietà del documento, è necessario implementare un meccanismo speciale utilizzando la macro SetCustomProp. Le proprietà del documento non dovrebbero essere impostate direttamente ma solo tramite questa macro.

Aggiornamento: Ho dimenticato di menzionare chiaramente che questo approccio funziona solo con le scorciatoie da tastiera e i comandi di menu, facendo clic sul pulsante della barra degli strumenti fa ancora un annullamento in un solo passaggio. Abbiamo quindi deciso di sostituire i pulsanti della barra degli strumenti con quelli personalizzati. Il codice è stato in uso per un po 'Con Word 2003 (non è testato con Word 2007, in modo da essere pronti per la sorpresa;)

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! Penso che prenderò quella risposta "sì, ma non è carina"! Penso che lo costruirò solo se ne avrò bisogno, per ora non voglio troppe cose. Cose interessanti comunque. – Gavin

+0

Qualcuno ha ottenuto che funzioni in Word 2007? Sto solo cercando di iniziare in modo semplice; Provo ad aggiungere: "Sub EditUndo()/MsgBox (" Hello ")/ActiveDocument.Undo/End Sub" al documento aperto (ho provato a salvarlo come docm) o Normal.dotm.Nessuno di questi tentativi sembra richiamare la macro quando premo control-z nella parola documento. Aiuto? –

+0

@DGGenuine: la sovrascrittura del comando 'EditUndo' dovrebbe funzionare anche in Word 2007 e 2010. La macro deve essere all'interno di un modulo nel documento corrente o nel modello allegato. Sei sicuro di non aver riconfigurato le scorciatoie da tastiera? Ci sono altri componenti aggiuntivi attivi che funzionano con i comandi Word incorporati? –

1

Excel ha un supporto (limitato) integrato per annullare e ripristinare come parte della sua architettura VBA.

Non ho familiarità con vsto, quindi non so se questo ti aiuterà, ma puoi dare un'occhiata a this SO question per maggiori dettagli.

+0

Grazie, ho visto anche quella domanda, non pensavo davvero al pensiero di costruire un annullamento in me stesso, sembra irto di pericoli. Anche VSTO è fondamentalmente VBA ++ (è così che mi piace pensarlo comunque), ma in questo particolare riguardo non penso che abbia capacità extra. – Gavin

2

ho masticare su questo per un po'. Ecco il mio tentativo di utilizzare un documento nascosto, quindi di prendere il WordOpenXML dal documento nascosto e di inserirlo nel documento reale quando necessario per rendere qualsiasi azione di VSTO un singolo annullamento.

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

Qui è la classe 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 offre la possibilità di fare questo tramite l'oggetto Application.UndoRecord. Vedi http://msdn.microsoft.com/en-us/library/hh128816.aspx

+0

Tuttavia non è una vera "transazione" - è solo zucchero per far sembrare un singolo annullare l'operazione nello stack di annullamento - se si chiama .Undo a qualsiasi livello * tutti * degli annidati UndoRecords sono annullati e interrotti, è piuttosto zoppo. – BrainSlugs83