Excel vba отмена действия

Хитрости »

24 Февраль 2012              61042 просмотров


Как отменить действия макроса

Многие из тех, кто программирует в VBA знают, что после действий макроса пропадает возможность отмены действий. И если с отменой тех действий, которые были совершены до выполнения макроса совершенно точно можно распрощаться(невозможно будет это сделать), то отменить действия макроса возможно. И рано или поздно каждый программирующий в VBA задается вопросом: как можно отменить действия, совершенные макросом? Для начала надо понять, в каких ситуациях это нам надо. Например был выполнен код, который испортил или удалил данные в файле, но эти данные еще нужны. Самое простое, что можно сделать это закрыть файл без сохранения и открыть заново. Все данные будут на месте(если, конечно, в коде не было строки, сохраняющей файл). Второй способ: это перед выполнением макроса делать резервную копию файла — тогда Ваши исходные данные всегда будут целы.


Но как же сделать отмену действий макроса через стандартную кнопку на панели или сочетанием клавиш

Ctrl

+

Z

и можно ли? Ответ — можно. Но сразу вопрос: а насколько это нужно? В каких ситуациях это может пригодиться? Я навскидку сразу не сказал бы, если бы не являлся разработчиком программ и надстроек в среде Microsoft Excel. Именно в надстройках отмена действий наиболее востребована, на мой взгляд. Например надстройка объединяет ячейки. Объединили случайно и…В стандартной ситуации после такого макроса нельзя отменить действия. Закрывать файл без сохранения? Как-то некрасиво получается, если продукт является коммерческим. И тогда приходится извращаться и пытаться сделать возможным отмену действий макроса. В моей надстройке MulTEx в некоторых командах отмена действий команд как раз и применяется. Наиболее распространенное решение по отмене действий макроса заключается в запоминании свойств изменяемых ячеек:

'Создаем свой пользовательский тип данных
Type SaveRange
    vFormula As Variant
    sAddr As String
    lColor As Long
    lColorIndex As Long
End Type
'Переменные для запоминания данных
Public wbWBook As Excel.Workbook
Public wsSh As Excel.Worksheet
Public vOldVals() As SaveRange
'---------------------------------------------------------------------------------------
' Procedure : Fill_Numbers
' Purpose   : Основная процедура. Это тот код, который вносит изменения на лист
'             и действия которого нам необходимо отменить
'             Процедура заполняет выделенные ячейки номерами
'             и изменяет цвет заливки
'---------------------------------------------------------------------------------------
Sub Fill_Numbers()
    Dim rCell As Range, li As Long
    '   Сначала запоминаем значения выделенных ячеек на листе
    ReDim vOldVals(1 To Selection.Count)
    'Запоминаем активную книгу
    'это на случай, если отмена действий будет производиться из другой книги
    Set wbWBook = ActiveWorkbook
    'Запоминаем активный лист
    'на случай, если отмена действий будет производиться из другого листа
    Set wsSh = ActiveSheet
    'Запоминаем значения(заносим в массив)
    li = 1
    For Each rCell In Selection
        'запоминаем адрес ячейки
        vOldVals(li).sAddr = rCell.Address
        'запоминаем формулу(если нет формулы - значение)
        vOldVals(li).vFormula = rCell.Formula
        'запоминаем цвет заливки ячейки
        vOldVals(li).lColor = rCell.Interior.Color
        'запоминаем индекс цвета заливки(чтобы на заливать бесцветные ячейки)
        vOldVals(li).lColorIndex = rCell.Interior.ColorIndex
        li = li + 1
    Next rCell
    '======================================
    'Выполняем основные действия(собственно тот код, который надо будет отменить)
    li = 1
    For Each rCell In Selection
        rCell = li
        rCell.Interior.ColorIndex = li
        li = li + 1
    Next rCell
    '======================================
    'Назначаем стандартному вызову отмены действий выполнение нашего макроса возвращения значений
    Application.OnUndo "Отменить заполнение ячеек номерами", "Restore_Vals"
End Sub
 
'---------------------------------------------------------------------------------------
' Procedure : Restore_Vals
' Purpose   : Процедура отмены действия(возврат значений)
'---------------------------------------------------------------------------------------
Sub Restore_Vals()
    Dim li As Long
    'В случае непредвиденной ошибки переходим на метку
    'и показываем сообщение об ошибке
    On Error GoTo Erreble
    'Активируем книгу, в которой были сделаны изменения
    wbWBook.Activate
    'Активируем лист, в котором были сделаны изменения
    wsSh.Activate
    'Возвращаем значения
    For li = 1 To UBound(vOldVals)
        Range(vOldVals(li).sAddr).Formula = vOldVals(li).vFormula
        'если заливка была безцветная, то ColorIndex = xlNone
        'значит выставляем безцветность
        If Not vOldVals(li).lColorIndex = xlNone Then
            Range(vOldVals(li).sAddr).Interior.Color = vOldVals(li).lColor
        Else 'если цвет был - возвращаем его
            Range(vOldVals(li).sAddr).Interior.ColorIndex = xlNone
        End If
    Next li
    Exit Sub
 
    'Показываем сообщение о невозможности отмены действия
Erreble:
    MsgBox "Нельзя отменить действие!", vbCritical, "Error"
End Sub

Комментарии к коду я старался сделать максимально подробными, поэтому думаю, что больше нечего разъяснять. К тому же по древней традиции я приложил к статье пример с данным кодом :) Единственное, что могу добавить: пользовательский тип SaveRange может быть дополнен еще какими-либо переменными, помимо vFormula, sAddr и lColor. Например цвет границ ячейки, цвет шрифта и т.д. Все зависит от того, какие изменения Вы будете делать кодом и что захотите затем вернуть.

Скачать пример

  Отменить действия макроса.xls (62,0 KiB, 3 360 скачиваний)


Код, приведенный выше, несомненно хорош, но если кол-во изменяемых ячеек достаточно велико, то код будет очень замедлять работу. Поэтому если есть возможность добавлять/удалять листы в книгах, то можно схитрить: сделать резервную копию листа, лист сделать очень скрытым и как только потребуется отмена действия — вернуть этот лист, удалив исходный(с уже испорченными данными):

'Переменные для запоминания данных
Public wbWBook As Workbook
Public wsSh As Worksheet, wsActSh As Worksheet, sSh_Name As String, lShPoz As Long
'---------------------------------------------------------------------------------------
' Procedure : Fill_Numbers
' Purpose   : Основная процедура. Это тот код, который вносит изменения на лист
'             и действия которого нам необходимо отменить
'             Процедура заполняет выделенные ячейки номерами
'             и изменяет цвет заливки
'---------------------------------------------------------------------------------------
Sub Fill_Numbers()
    Dim rCell As Range, li As Long
    'Запоминаем активную книгу
    'это на случай, если отмена действий будет производиться из другой книги
    Set wbWBook = ActiveWorkbook
    'Запоминаем активный лист
    'на случай, если отмена действий будет производиться из другого листа
    Set wsActSh = ActiveSheet
    lShPoz = wsActSh.Index
    sSh_Name = wsActSh.Name
    Application.ScreenUpdating = 0
    wsActSh.Copy , wbWBook.Sheets(wbWBook.Sheets.Count)
    Set wsSh = wbWBook.Sheets(wbWBook.Sheets.Count)
    wsSh.Visible = xlVeryHidden
    wsActSh.Activate
    Application.ScreenUpdating = 1
    '======================================
    'Выполняем основные действия(собственно тот код, который надо будет отменить)
    li = 1
    For Each rCell In Selection
        rCell = li
        rCell.Interior.ColorIndex = li
        li = li + 1
    Next rCell
    '======================================
    'Назначаем стандартному вызову отмены действий выполнение нашего макроса возвращения значений
    Application.OnUndo "Отменить заполнение ячеек номерами", "Restore_Vals"
End Sub
 
'---------------------------------------------------------------------------------------
' Procedure : Restore_Vals
' Purpose   : Процедура отмены действия(возврат значений)
'---------------------------------------------------------------------------------------
Sub Restore_Vals()
    'В случае непредвиденной ошибки переходим на метку
    'и показываем сообщение об ошибке
    On Error GoTo Erreble
    Application.ScreenUpdating = 0
    'Активируем книгу, в которой были сделаны изменения
    wbWBook.Activate
    'делаем видимым резервный лист
    wsSh.Visible = -1
    'Удаляем исходный лист, данные в котором уже изменены
    Application.DisplayAlerts = 0
    wsActSh.Delete
    Application.DisplayAlerts = 1
    'назначаем резервному листу имя исходного
    wsSh.Name = sSh_Name
    wsSh.Move wbWBook.Sheets(lShPoz)
    'Активируем резервный лист
    wsSh.Activate
    Application.ScreenUpdating = 0
    Exit Sub
    'Показываем сообщение о невозможности отмены действия
Erreble:
    MsgBox "Нельзя отменить действие!", vbCritical, "www.excel-vba.ru"
End Sub

Скачать пример

  Tips_Restore_Macro_HiddenSh.xls (45,0 KiB, 2 366 скачиваний)

Конечно, в этом приеме тоже есть недостаток — если на этот лист ссылаются формулы из других листов есть большой шанс получить в этих формулах ошибку

#ССЫЛКА!(#REF!)

, т.к. исходный лист удаляется.
В этом случае можно из резервного листа копировать все ячейки и вставлять на рабочий лист. Да и вообще можно много чего придумать — вплоть до сохранения и последующего извлечения резервных копий файлов. Все как всегда зависит от задач и ситуации.


И самая большая ложка дегтя к обоим кодам: VBA не позволяет таким образом делать МНОГОКРАТНЫЕ отмены действий(многократное Ctrl+Z), что делает бесполезными попытки запомнить пошагово несколько разных изменений макросами. Отменить можно будет все равно только последнее запомненное действие.


Статья помогла? Поделись ссылкой с друзьями!

  Плейлист   Видеоуроки


Поиск по меткам



Access
apple watch
Multex
Power Query и Power BI
VBA управление кодами
Бесплатные надстройки
Дата и время
Записки
ИП
Надстройки
Печать
Политика Конфиденциальности
Почта
Программы
Работа с приложениями
Разработка приложений
Росстат
Тренинги и вебинары
Финансовые
Форматирование
Функции Excel
акции MulTEx
ссылки
статистика

 

RoMuLX

Пользователь

Сообщений: 8
Регистрация: 16.06.2017

The_Prist, хорошо, расскажу полную ситуацию) внимание: много букв))
У меня есть инженерный расчетный файл. Он считает таблицы без макросов.
А макрос как раз на отслеживание входа из этой темы:

http://www.planetaexcel.ru/techniques/5/196/

Но тут проблема, что окошко при выходе не вылезает, да и всегда идет сохранение файла (значит можно что-то по ошибке испортить и выйти).

И что бы правильно работало добавил форму при выходе:

http://www.planetaexcel.ru/forum/index.php?PAGE_NAME=message&FID=1&TID=71378&…

В итоге у меня форма fmQueryClose на «сохранить»  «не сохранять»  «отмена».

1. Если жмем «сохранить» — то в реестр добавляется пользователь+дата, потом скрывает все листы, потом идет сейв, потом выход.
2. Если жмем «не сохранять» — то откатывает все изменения, потом скрывает все листы, потом идет сейв, потом выход.
3. Если жмем «отмена» — то просто закрывается форма и ничего больше.

Во всех этих вариантах стандартного диалога при выходе не будет.

и такой код у меня склепался из вышеперечисленных источников:

Код
Private Sub Workbook_Open() 

For Each sh In ActiveWorkbook.Worksheets 'отображаем все листы 
   sh.Visible = True 
Next sh 
Worksheets("Предупреждение").Visible = xlSheetVeryHidden  'скрываем лист ПРЕДУПРЕЖДЕНИЕ 

Worksheets("Реестр изменений").Rows("2:2").Insert Shift:=xlDown 'вставляем между строками 1 и 2 новую строку 
Worksheets("Реестр изменений").Rows("1001:1001").Delete Shift:=xlUp 'удаляем строку 1001 (реестр на 1000 строк) 
Worksheets("Реестр изменений").Cells(2, 1) = Environ("USERNAME") 'запись в первую ячейку второй строки 
Worksheets("Реестр изменений").Cells(2, 2) = Now 'запись во вторую ячейку второй строки 
ActiveWorkbook.Save 

End Sub 

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) 

Worksheets("Реестр изменений").Rows("2:2").Insert Shift:=xlDown 'вставляем между строками 1 и 2 новую строку 
Worksheets("Реестр изменений").Rows("1001:1001").Delete Shift:=xlUp 'удаляем строку 1001 (реестр на 1000 строк) 
Worksheets("Реестр изменений").Cells(2, 1) = Environ("USERNAME") 'запись в первую ячейку второй строки 
Worksheets("Реестр изменений").Cells(2, 3) = Now 'запись в третью ячейку второй строки 
  
End Sub 

Private Sub Workbook_BeforeClose(Cancel As Boolean) 
    
   If Not Me.Saved Then 

       fmQueryClose.Show 

       Select Case fmQueryClose.DialogResult 
           Case VbMsgBoxResult.vbYes: Me.Save 
               Worksheets("Предупреждение").Visible = True 'скрываем все листы, кроме листа ПРЕДУПРЕЖДЕНИЕ 
               For Each sh In ActiveWorkbook.Worksheets 
                   If sh.Name = "Предупреждение" Then 
                       sh.Visible = True 
                   Else 
                       sh.Visible = xlSheetVeryHidden 
                   End If 
               Next sh 
               ActiveWorkbook.Save 
           Case VbMsgBoxResult.vbNo: Me.Saved = True 
                
>>>> Тут как раз и нужен код на откат всех изменений <<<< 

               Worksheets("Предупреждение").Visible = True 'скрываем все листы, кроме листа ПРЕДУПРЕЖДЕНИЕ 
               For Each sh In ActiveWorkbook.Worksheets 
                   If sh.Name = "Предупреждение" Then 
                       sh.Visible = True 
                   Else 
                       sh.Visible = xlSheetVeryHidden 
                   End If 
               Next sh 
               ActiveWorkbook.Save 

           Case VbMsgBoxResult.vbCancel: Cancel = True 
            
       End Select 
       Unload fmQueryClose 
   End If 
End Sub 

При выходе без сохранения я подумал именно про откаты(Undo), т.к. вручную-то я могу после открытия откатить все, что исправил в ячейках, зажатием Ctrl+Z.
Получаем только открытый открытый файл, но после отката нужно скрыть все страницы.

Цитата
До какого момента, кстати?

Откат до открытия файла

Цитата
Как понять, когда закончить откатывать?

Закончить откат, когда уже нечего отменять (кнопка Undo будет неактивна)

Цитата
Ведь количество откатов может быть разным на разных ПК.

Да, кто-то может 1 ячейку косякнуть и выйти, а кто-то и 40.
в файле в принципе не более 100 ячеек для ввода данных (остальное ВПРом подцепляется в зависимости от того что ввели в исходных + математические формулы )

Цитата
Да и событие-то какое: перед сохранением.

Да, уже подумал что лучше перед выходом + форма (то о чем я писал выше. Надеюсь из кода понятно что я хочу)

Цитата
Через Undo это сделать нереально.

Спасибо, я не знал. Надеюсь, сейчас подскажите, как сделать то, что мне нужно)

Ну если что, буду использовать методы для отмены действий макросов (по идее должно помочь)

Изменено: RoMuLX17.06.2017 13:18:13

Отмена действий макроса

Мурад

Дата: Понедельник, 07.04.2014, 17:57 |
Сообщение № 1

Группа: Проверенные

Ранг: Ветеран

Сообщений: 509


Репутация:

17

±

Замечаний:
0% ±


Excel 2007

Добрый вечер! Поскольку я — чайник в вопросе VBA, такой глупый вопрос: как отменить действие макроса? Всякое бывает. Отвлекли, ошибочно выбрал в книге макросов тот, что насилует данные и вместо любимой таблицы имеем не пойми что. При этом, кнопка отмены действия на экране неактивна, а копии такого файла нет…
Это уже самоубийство какое-то, а не макрос.

 

Ответить

anvg

Дата: Вторник, 08.04.2014, 02:54 |
Сообщение № 2

Группа: Друзья

Ранг: Ветеран

Сообщений: 581


Репутация:

271

±

Замечаний:
0% ±


2016, 365

Цитата

Это уже самоубийство какое-то, а не макрос

Самое простое, пропишите в макросе создание копии листа в этой же книге, в случае чего, просто перекопируете его содержимое либо, удалив испорченный, переименуете.

 

Ответить

igrtsk

Дата: Вторник, 08.04.2014, 09:06 |
Сообщение № 3

Группа: Проверенные

Ранг: Обитатель

Сообщений: 307


Репутация:

50

±

Замечаний:
0% ±


Excel 2016

Действие макроса обычным порядком отменить нельзя.
Либо, как указано выше, резервируете данные, либо закрываете файл без сохранения.


Инструктор по применению лосей в кавалерийских частях РККА

 

Ответить

Alex_ST

Дата: Вторник, 08.04.2014, 09:06 |
Сообщение № 4

Группа: Друзья

Ранг: Участник клуба

Сообщений: 3176


Репутация:

604

±

Замечаний:
0% ±


2003

ну, если уж речь идёт о том, что

ошибочно выбрал в книге макросов тот, что насилует данные

, то значит придётся в каждый макрос из Personal.xls прописывать в начале сохранение копии всей книги (ведь не известно, какие листы текущей книги изуродует случайно выбранный макрос). И как-то эти бэкапы ещё и отслеживать, вызывать по UnDo и удалять по окончании работы.
А если учесть ещё и то, что макросы могут обрабатывать данные не только в своей книге, а вообще любые файлы на компе и даже в интранете, то бэкапать при каждом вызове макроса Вам придётся много и долго… :)
Так что не парьтесь с отменой действия макроса, а будьте внимательны при работе с макросами. А в сами макросы, критически изменяющие какие-нибудь данные, нужно в начале вставить подтверждения выполнения с возможностью отказаться.



С уважением,
Алексей
MS Excel 2003 — the best!!!

 

Ответить

Мурад

Дата: Вторник, 08.04.2014, 10:05 |
Сообщение № 5

Группа: Проверенные

Ранг: Ветеран

Сообщений: 509


Репутация:

17

±

Замечаний:
0% ±


Excel 2007

Я потому-то и создал данный топ, что у меня команда UnDo не активна после любого действия макроса. В настройках ничего не трогал. Пока что пользуюсь записью макроса через ленту для однотипных действий. Стоит нажать выполнить на макросе, как кнопка отмены действия становится серой.

 

Ответить

Alex_ST

Дата: Вторник, 08.04.2014, 10:24 |
Сообщение № 6

Группа: Друзья

Ранг: Участник клуба

Сообщений: 3176


Репутация:

604

±

Замечаний:
0% ±


2003

Ну Вам же ответили:

Действие макроса обычным порядком отменить нельзя

не парьтесь с отменой действия макроса, а будьте внимательны при работе с макросами.

Хитро-мудрые крутые специалисты, конечно, придумали кое-какие «костыли», в некоторых случаях заменяющие UnDo. Но далеко не во всех!
Вот, например, ЗДЕСЬ
Но это всё большие тормоза и приписывать начальные фрагменты кода, создающие бэкап, нужно будет ко всем макросам.
Автоматически выполнять процедуры «предохранения» перед вызовом любого макроса не удастся.



С уважением,
Алексей
MS Excel 2003 — the best!!!

 

Ответить

Мурад

Дата: Вторник, 08.04.2014, 10:46 |
Сообщение № 7

Группа: Проверенные

Ранг: Ветеран

Сообщений: 509


Репутация:

17

±

Замечаний:
0% ±


Excel 2007

Спасибо за оперативность всем ответившим!

 

Ответить

I always save immediately before running my macros (during testing at least) then, if everything goes pear-shaped, I can just exit without saving and re-open it.

Baking it into the actual macro, you’ll have to basically record the old state of everything that changes (cell contents, formulae, formatting and so on) in a list then have an undo macro which plays back that list in reverse order.

For example if your macro changes a cell C22 contents from «3» to «7» and formatting from «general» to «number, 2 decimals), your list would be:

C22 value  3
C22 format general

Playing this back in reverse order (with another macro) would revert the changes.

You could have a whole extra sheet to hold the macro undo information such as:

Step   Cell   Type    Value
----   ----   -----   -------
   1   C22    value         3
       C22    format  general
   2...

It wouldn’t integrate very well with the ‘real’ undo unfortunately, but I don’t think there’s any way around that.

Здравствуйте.
Есть макрос на VBA для преобразования документа MSWord.
Необходимо реализовать отмену всех действий макроса.

На текущий момент реализовано так:

Dim undoCount
...
undoCount = 0
...
If checkbox1.value = True Then
    Selection.typeText "text1"
    undoCount = undoCount + 1
Else
    Selection.typeText "text2"
    Selection.typeText "text3"
    undoCount = undoCount + 2
End If
...
For i=1 to undoCount
    ActiveDocument.Undo
Next

Не нравится, что ветвления и циклы надо учитывать.
Есть подозрение, что это — быдлокодие.

задан 9 мар 2016 в 19:14

ReinRaus's user avatar

ReinRausReinRaus

17.6k3 золотых знака43 серебряных знака84 бронзовых знака

6

Вам нужно уметь группировать несколько действий в одну группу для Undo/Redo. Для этого можно использовать undo record.

Вот эта статья описывает использование для MS Word. Пример оттуда:

Dim objUndo As UndoRecord 

' работа этой функции будет оформлена как один элемент списка Undo/Redo     
Sub AddDocMetadata() 
Dim rngFooter As Range 

Set objUndo = Application.UndoRecord 

' Начинаем кастомную запись действий для undo, устанавливаем своё название
objUndo.StartCustomRecord ("Add Doc Metadata") 

  ' сами действия:   
  Set rngFooter = ActiveDocument.Sections(1) _ 
        .Footers(wdHeaderFooterPrimary).Range 

  With rngFooter 
        .Delete 
        .Fields.Add Range:=rngFooter, Type:=wdFieldFileName, Text:="p" 
        .InsertAfter Text:=vbTab & vbTab 
        .Collapse Direction:=wdCollapseStart 
        .Fields.Add Range:=rngFooter, Type:=wdFieldAuthor 
  End With 

' заканчиваем запись
objUndo.EndCustomRecord 

End Sub 

Заметьте, что здесь, в отличие от аналогичной фичи MS Project, вызов Undo во время записи custom record’а отменяет все действия от начала. Это может быть не то, что вам надо.

ответ дан 12 мар 2016 в 9:53

VladD's user avatar

VladDVladD

206k27 золотых знаков288 серебряных знаков520 бронзовых знаков

5

Для старых версий Word можно применить следующий костыль:
Создаем закладку с уникальным именем и если требуется, то делаем Undo, пока закладка не исчезнет, если нужно оставить изменения, то удаляем закладку.

Const undoBookmark = "undoBookmark__"

Sub startUndoRecord()
    ActiveDocument.Bookmarks.Add undoBookmark
End Sub

Sub undoAllActions()
    While ActiveDocument.Bookmarks.Exists( undoBookmark )
        ActiveDocument.Undo
    Wend
End Sub

Sub confirmAllActions()
    ActiveDocument.Bookmarks( undoBookmark ).Delete
End Sub

Идея костыля взята здесь:
https://stackoverflow.com/a/661312/276994
но там много лишнего, реальные задачи гораздо проще, чем множество вспомогательных действий, которые реализованы там

Дух сообщества's user avatar

ответ дан 16 мар 2016 в 14:15

ReinRaus's user avatar

ReinRausReinRaus

17.6k3 золотых знака43 серебряных знака84 бронзовых знака

Понравилась статья? Поделить с друзьями:
  • Excel vba открыть файл если он не открыт
  • Excel vba открыть файл в скрытом режиме
  • Excel vba открыть только форму
  • Excel vba открыть ссылку в браузере
  • Excel vba открыть каталог