Можно ли создать транзакцию отмены в Word или Excel? (VSTO)
Я замечаю, что Project 2007 имеет функции, которые позволяют операции, которые могут быть отменены, помещаться в один элемент стека или "отменить транзакцию". например:
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, которые делают несколько изменений сразу, и это будет немного раздражать пользователей, если они должны нажать на "отменить" несколько раз, если они делают ошибку. Хотя эти конкретные функции, похоже, не существуют, кто-нибудь знает, если / как это можно сделать каким-то образом?
4 ответов
вы можете имитировать транзакционное поведение в Word, перезаписывая процедуры отмены и повтора в VBA (я не думаю, что перезапись встроенных команд Word возможна только с помощью VSTO). Начало транзакции помечается добавлением закладки, конец-удалением закладки.
при вызове отмены мы проверяем, присутствует ли закладка метки транзакции и повторяем отмену, пока маркер не исчезнет. Повтор работает точно так же. Этот механизм поддерживает транзакционную отмену / повтор всех изменений, внесенных в содержимое документа. Однако, чтобы разрешить отмену / повтор изменений свойств документа, необходимо реализовать специальный механизм с помощью макроса SetCustomProp. Свойства документа не должны устанавливаться напрямую, а только через этот макрос.
Update: я забыл четко упомянуть, что этот подход работает только с сочетаниями клавиш и командами меню, нажатие кнопки панели инструментов все еще делает одношаговую отмену. Мы поэтому решил заменить кнопки панели инструментов на пользовательские. Код довольно долго использовался в Word 2003 (он не тестировался с Word 2007, поэтому будьте готовы к сюрпризу ;)
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 предоставляет возможность сделать это с помощью приложение.Undorecord с
я жевал это на некоторое время. Вот моя попытка использовать скрытый документ, а затем захватить 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();
}
Excel имеет некоторую (ограниченную) встроенную поддержку отмены и повтора как часть своей архитектуры VBA.
Я не знаком с vsto, поэтому я не знаю, поможет ли это вам, но вы можете взглянуть на это так вопрос для получения более подробной информации.