2009-03-19 18 views
6

Observo que Project 2007 tiene las funciones que permiten ubicar las operaciones que se pueden deshacer en un elemento de una sola pila, o "deshacer transacción". For example:¿Puedo crear una transacción de deshacer en 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 

Lo que esto significa es que el usuario puede deshacer todas las acciones en una sola acción de deshacer, más de 6 veces.

Esto sería genial para implementar en Word y/o Excel, ya que estoy haciendo algunas cosas en VSTO que hacen varios cambios a la vez, y será un poco molesto para el usuario si tiene que hacer clic en Deshace varias veces si comete un error. Aunque esas funciones específicas no parecen existir, ¿alguien sabe si/cómo se puede hacer de alguna manera?

Respuesta

7

Puede simular el comportamiento transaccional en Word sobrescribiendo las rutinas de comando Deshacer y Rehacer en VBA (no creo que la sobreescritura de comandos de Word integrados sea posible usando solo VSTO). El inicio de una transacción se marca al agregar un marcador, el final se marca al eliminar el marcador.

Al hacer una llamada para deshacer, comprobamos si el marcador de marca de la transacción está presente y repetimos el deshacer hasta que el marcador desaparezca. Rehacer está funcionando de la misma manera. Este mecanismo admite el deshacer/rehacer transaccional de todas las modificaciones hechas al contenido del documento. Sin embargo, para permitir deshacer/rehacer modificaciones de las propiedades del documento, se debe implementar un mecanismo especial utilizando la macro SetCustomProp. Las propiedades del documento no deberían establecerse directamente sino solo a través de esta macro.

Actualización: Olvidé mencionar claramente que este enfoque solo funciona con los atajos de teclado y los comandos de menú, al hacer clic en el botón de la barra de herramientas aún se realiza un solo paso de deshacer. Por lo tanto, decidimos reemplazar los botones de la barra de herramientas por botones personalizados. El código ha estado en uso durante bastante tiempo con Word 2003 (no se probó con Word 2007, así que esté preparado para 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

¡Guau! Creo que tomaré esa respuesta "sí, pero no es bonita". Creo que lo construiré solo si lo necesito, no quiero meterme demasiado por ahora. Interesante sin embargo. – Gavin

+0

¿Alguien ha conseguido esto para trabajar en Word 2007? Solo estoy tratando de comenzar simple; Intento agregar: "Sub EditUndo()/MsgBox (" Hello ")/ActiveDocument.Undo/End Sub" al documento abierto (traté de guardarlo como docm, también) o Normal.dotm.Ninguno de estos intentos parece invocar la macro cuando presiono control-z en el documento de Word. ¿Ayuda? –

+0

@DGGenuine: Sobrescribir el comando 'EditUndo' también debería funcionar en Word 2007 y 2010. La macro debe estar dentro de un módulo en el documento actual o en la plantilla adjunta. ¿Estás seguro de que no has reconfigurado tus atajos de teclado? ¿Hay algún otro complemento activo que se meta con los comandos de Word integrados? –

1

Excel tiene un soporte incorporado (limitado) para deshacer y rehacer como parte de su arquitectura VBA.

No estoy familiarizado con vsto, así que no sé si esto te ayudará, pero puedes echar un vistazo a this SO question para obtener más detalles.

+0

Gracias, vi esa pregunta también, en realidad no me apetecía la idea de construir deshacer en mí misma, parece llena de peligro. También VSTO es básicamente VBA ++ (así es como me gusta pensar de todos modos), pero en este aspecto particular, no creo que tenga capacidades adicionales. – Gavin

2

he estado masticando en este caso por un tiempo. Aquí está mi intento de usar un documento oculto, y luego tomar el archivo WordOpenXML del documento oculto y colocarlo en el documento real cuando sea necesario para deshacer cualquier cantidad de acciones de 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();   
    } 
} 

Aquí es la clase 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(); 
} 
Cuestiones relacionadas