2009-03-19 5 views
6

Je remarque que Project 2007 a les fonctions qui permettent de placer les opérations qui peuvent être annulées dans un seul élément de pile, ou "annuler la transaction". For example:Puis-je créer une transaction d'annulation dans Word ou Excel? (VSTO)

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

Ce que cela signifie est que l'utilisateur peut annuler toutes les actions en une seule action undo, plutôt que 6 fois.

Ce serait génial de mettre en œuvre dans Word et/ou Excel, car je fais certaines choses dans VSTO qui effectuent plusieurs changements à la fois, et ce sera un peu ennuyeux pour l'utilisateur s'ils doivent cliquer sur Défaites plusieurs fois s'ils font une erreur. Bien que ces fonctions spécifiques ne semblent pas exister, quelqu'un sait-il si/comment cela peut être fait d'une manière ou d'une autre?

Répondre

7

Vous pouvez simuler le comportement transactionnel dans Word en écrasant les routines de commande Undo et Redo dans VBA (je ne pense pas que l'écrasement des commandes Word intégrées soit possible en utilisant uniquement VSTO). Le début d'une transaction est marqué par l'ajout d'un signet, la fin est marquée par la suppression du signet. Lorsque vous appelez Annuler, nous vérifions si le signet de la marque de transaction est présent et répétez l'annulation jusqu'à ce que le marqueur soit parti. Redo travaille de la même manière. Ce mécanisme prend en charge l'annulation/la restauration transactionnelle de toutes les modifications apportées au contenu du document. Cependant, pour permettre l'annulation/la reprise des modifications apportées aux propriétés du document, un mécanisme spécial doit être implémenté à l'aide de la macro SetCustomProp. Les propriétés du document ne doivent pas être définies directement mais uniquement via cette macro.

Mise à jour: J'ai oublié de mentionner clairement que cette approche ne fonctionne qu'avec les raccourcis clavier et les commandes de menu, en cliquant sur le bouton de la barre d'outils fait toujours une seule étape. Nous avons donc décidé de remplacer les boutons de la barre d'outils par des boutons personnalisés. Le code a été utilisé pendant un certain temps avec Word 2003 (il n'est pas testé avec Word 2007, alors soyez prêt pour la surprise;)

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! Je pense que je vais prendre cette réponse "oui, mais ce n'est pas joli"! Je pense que je ne construirai ça que si j'en ai besoin, je ne veux pas trop en faire pour l'instant. Des choses intéressantes cependant. – Gavin

+0

Quelqu'un at-il obtenu ce pour travailler dans Word 2007? J'essaie juste de commencer simple; J'essaie d'ajouter: "Sub EditUndo()/MsgBox (" Bonjour ")/ActiveDocument.Undo/End Sub" soit au document ouvert (j'ai essayé de l'enregistrer comme docm, aussi) ou Normal.dotm.Aucune de ces tentatives ne semble invoquer la macro lorsque j'appuie sur control-z dans le document Word. Aidez-moi? –

+0

@DGGenuine: L'écrasement de la commande 'EditUndo' devrait également fonctionner dans Word 2007 et 2010. La macro doit être à l'intérieur d'un module dans le document actuel ou dans le modèle joint. Êtes-vous sûr de ne pas avoir reconfiguré vos raccourcis clavier? Y a-t-il d'autres compléments actifs qui dérangent avec les commandes Word intégrées? –

1

Excel dispose d'un support intégré (limité) pour annuler et rétablir dans le cadre de son architecture VBA.

Je ne suis pas familier avec vsto, donc je ne sais pas si cela va vous aider, mais vous pouvez jeter un oeil à this SO question pour plus de détails.

+0

Merci, vu cette question aussi, n'a pas vraiment envie de la construction de se défaire en moi-même, semble lourde de danger. Aussi VSTO est fondamentalement VBA ++ (c'est comme ça que j'aime penser de toute façon), mais à cet égard, je ne pense pas qu'il a des capacités supplémentaires. – Gavin

2

J'ai mâcher sur celui-ci pendant un certain temps. Voici ma tentative d'utilisation d'un document caché, puis en saisissant le WordOpenXML à partir du document caché et en le plaçant dans le document réel lorsque cela est nécessaire pour rendre n'importe quelle quantité d'actions VSTO une seule annulation.

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

Voici la DocBuffer Classe:

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