Как определить, когда книга закрывается? - PullRequest
5 голосов
/ 31 октября 2019

Событие Workbook.BeforeClose срабатывает, когда книга собирается закрыться, но до появления сообщения о сохранении, которое позволяет отменить его.

Как определить, когда книга уже закрывается послеточка, в которой его можно отменить, не удаляя и не заменяя сообщение о сохранении на пользовательское?

Один из найденных мной обходных путей online заключается в использовании события вместе с Workbook. Деактивировать событие , которое выглядит следующим образом:

Код в рабочей книге:

Private Sub Workbook_BeforeClose(ByRef Cancel As Boolean)

  closing_event = True
  check_time = VBA.Now + VBA.TimeSerial(Hour:=0, Minute:=0, Second:=1)
  Excel.Application.OnTime EarliestTime:=check_time, Procedure:="disable_closing_event"

End Sub

Private Sub Workbook_Deactivate()

  If closing_event Then
    VBA.MsgBox Prompt:="Closing event."
    Excel.Application.OnTime EarliestTime:=check_time, Procedure:="disable_closing_event", Schedule:=False
  End If

End Sub

Код в модуле:

Public closing_event As Boolean
Public check_time As Date

Public Sub disable_closing_event()

  closing_event = False

End Sub

Один очень специфический крайний случай, когдаон срабатывает неправильно, если вы щелкнете, чтобы закрыть книгу и менее чем за одну секунду закрыть сообщение о сохранении (нажмите Esc , чтобы сделать это достаточно быстро) и переключиться на другую книгу ( Alt + Tab ) запускает событие Деактивировать, когда переменная условия closing_event все еще имеет значение True, поскольку disable_closing_event все еще не установило его на False (запланировано на Application.OnTime , когда проходит одна секунда).

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

Редактировать:

принятый ответ , по моему мнению, является лучшим решением из всех текущих ответов. Я изменил его для своих нужд и предпочел следующий код в рабочей книге:

Private WorkbookClosing As Boolean

Private Sub Workbook_BeforeClose(Cancel As Boolean)
  WorkbookClosing = True
End Sub

Private Sub Workbook_Deactivate()
  If WorkbookClosing And ThisWorkbook.Name = ActiveWindow.Caption Then
    Workbook_Closing
  Else
    WorkbookClosing = False
  End If
End Sub

Private Sub Workbook_Closing()
  MsgBox "Workbook_Closing event."
End Sub

Ответы [ 7 ]

3 голосов
/ 13 ноября 2019

Это эволюция моего первого ответа - он обнаруживает проблему с крайним регистром, сравнивая ActiveWindow.Caption с ThisWorkbook.Name, чтобы он мог обнаружить эту проблему и справиться с ней. Это не самое элегантное решение, но я считаю, что оно работает.

Весь код в Рабочей книге, большая часть - в Деактивации

Public ByeBye As String

Private Sub Workbook_BeforeClose(Cancel As Boolean)
   ByeBye = "B4C"
End Sub

Private Sub Workbook_Deactivate()
   If ByeBye = "B4C" Then
      If ActiveWindow.Caption = ThisWorkbook.Name Then
         If ThisWorkbook.Saved Then
            MsgBox "No problem - Closing after Saving"
         Else
            MsgBox "No problem - Closing without Saving"
         End If
      Else
         If ThisWorkbook.Saved Then
            MsgBox "No problem - New Workbook Activation"
         Else
            MsgBox "Oops Try Again You Cannot Activate '" & ActiveWindow.Caption & "' until '" & ThisWorkbook.Name & "' has completed processing & IT HAS NOW COMPLETED", vbOKOnly, "Hiding"
            ThisWorkbook.Activate
         End If
      End If
   Else
      MsgBox "No problem - Just Hiding"
   End If
   ByeBye = "Done"
End Sub

Private Sub Workbook_Open()
   ByeBye = "OPENED"
End Sub

В ответ на комментарий о сохранении я проверил это на 7 возможных комбинацияхследующим образом

 1) Closing without Edits - No Saving Involved ... MsgBox Prompted with ... No problem - Closing after Saving       
 2) Not closing - Just Switch Workbook - Whether Edited or Not ... MsgBox Prompted with ... No problem - Just Hiding        
 3) Not closing - Switch Workbook - After Edit & Cancel ... MsgBox Prompted with ... Oops Try Again …       
 4) Closing and saving ... MsgBox Prompted with ... No problem - Closing after Saving       
 5) Closing and Saving after a prior Cancel ... MsgBox Prompted with ... No problem - Closing after Saving      
 6) Closing but Not Saving ... MsgBox Prompted with ... No problem - Closing without Saving         
 7) Closing but not Saving after a prior Cancel ... MsgBox Prompted with ... No problem - Closing without Saving        
2 голосов
/ 13 ноября 2019

Я думаю, что попытка отменить событие закрытия является неправильным подходом к тому, что вы пытаетесь сделать. Лучшим подходом было бы иметь функцию, которая only вызывается, когда рабочая книга фактически закрывается.

Спасибо за комментарии о том, что OnTime не вызывается, пока открыто диалоговое окно, поскольку оно указало мне в правильном направлении. Нам нужно проверить время между деактивацией рабочей книги и закрытием самой рабочей книги или диалогового окна сохранения. Использование функции Excel.Application.OnTime для установки этого времени закрытия означает, что это возможно, поскольку оно может быть отложено до закрытия диалога сохранения.

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

Сначала я столкнулся с проблемами при повторном открытии книги, чтобы запустить процедуру .OnTime, поэтому необходимо добавить искусственную задержку в функцию деактивации, чтобы книга не "t закрыт, пока не будет установлено время закрытия. Используя код отсюда - Задержка макроса, чтобы завершить события , мы можем выполнить это.

В ThisWorkbook

Option Explicit

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Excel.Application.OnTime EarliestTime:=Now, Procedure:="SetCloseTime"
End Sub

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
    If Timer < CloseTime + 0.2 Then Call CloseProcedure
End Sub

Private Sub Workbook_Deactivate()
    Delay (0.3)
    If Timer < CloseTime + 0.4 Then Call CloseProcedure
End Sub

В модуле

Option Explicit

Public CloseTime As Single

Function SetCloseTime()
    CloseTime = Timer
End Function

Function Delay(Seconds As Single)
    Dim StopTime As Single: StopTime = Timer + Seconds
    Do While Timer < StopTime
        DoEvents
    Loop
End Function

Function CloseProcedure()
    MsgBox "Excel is closing"
End Function

.OnTime, кажется, работает в течение одной секунды, что определяет продолжительность задержки, а тест разницы во времени имеет небольшую задержку, добавленную с дополнительной 1/10 отвторой (который я нашел необходимым). Возможно, эти временные интервалы могут потребовать небольшой доработки, но до сих пор они работали с разными сценариями при закрытии книги.

1 голос
/ 12 ноября 2019

Чтобы обойти ваш крайний случай, вам нужно обработать случай, когда рабочая книга деактивирована в течение 1 секунды после ее закрытия, но только после отображения приглашения на сохранение.

Чтобы проверить, меньше лиПрошла 1 секунда, используйте таймер высокого разрешения, чтобы сохранить время в событии Workbook_BeforeClose, а затем сравнить с ним в событии Workbook_Deactivate. Предполагая, что clsTimer является подходящим таймером высокого разрешения, ваш код теперь должен выглядеть так:

Private MyTimer As clsTimer
Private StartTime As Currency

Private Sub Workbook_BeforeClose(ByRef Cancel As Boolean)

    closing_event = True
    Set MyTimer = New clsTimer
    StartTime = MyTimer.MicroTimer
    check_time = VBA.Now + VBA.TimeSerial(Hour:=0, Minute:=0, Second:=1)
    Excel.Application.OnTime EarliestTime:=check_time, Procedure:="disable_closing_event"

End Sub

Private Sub Workbook_Deactivate()

    If closing_event Then

        If Not ThisWorkbook.Saved Then
            'The Save prompt must have been displayed, and the user clicked No or Cancel or pressed Escape

            If MyTimer.MicroTimer - StartTime < 1 Then
                'The user must have pressed Escape and Alt-Tabbed
                closing_event = False
            Else
                'Your Windows API calls here
            End If
        Else
            'The workbook was saved before the close event, so the Save prompt was not displayed
            'Your Windows API calls here
        End If
        Excel.Application.OnTime EarliestTime:=check_time, Procedure:="disable_closing_event", Schedule:=False
    End If

    Set MyTimer = Nothing

End Sub

Модуль класса для clsTimer выглядит следующим образом:

Private Declare PtrSafe Function getFrequency Lib "kernel32" _
Alias "QueryPerformanceFrequency" (cyFrequency As Currency) As Long

Private Declare PtrSafe Function getTickCount Lib "kernel32" _
Alias "QueryPerformanceCounter" (cyTickCount As Currency) As Long


Public Function MicroTimer() As Currency

    ' Returns seconds.

    Dim cyTicks1 As Currency
    Static cyFrequency As Currency

    MicroTimer = 0

    ' Get frequency.
    If cyFrequency = 0 Then getFrequency cyFrequency

    ' Get ticks.
    getTickCount cyTicks1

    ' Seconds
    If cyFrequency Then MicroTimer = cyTicks1 / cyFrequency

End Function
0 голосов
/ 13 ноября 2019

Я думаю, деактивация - лучший способ это уловить. Beforeclose может произойти раньше, чем событие Save, если документ не был сохранен. Таким образом, Excel может предложить сохранить до закрытия. Но Деактивация является последним событием перед закрытием (после сохранения). Так что это можно использовать.

0 голосов
/ 12 ноября 2019

Я не знаю, пытались ли вы это уже сделать, но одной из возможностей может быть создание класса, который обернет экземпляр Excel.Application, где все события для всех рабочих книг могут обрабатываться в одном месте. Поэтому, когда пользователь, когда вы пишете, «переходит в другую рабочую книгу (Alt + Tab)», событие может быть перехвачено, и может вызываться disable_closing_event(), чтобы отключить событие закрытия.

Добавить модуль класса с именем, например, ExcelApplication

Private WithEvents m_app As Excel.Application

Private Sub m_app_WorkbookActivate(ByVal Wb As Workbook)
    Call disable_closing_event
End Sub

Private Sub m_app_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
    closing_event = True
End Sub

Private Sub m_app_WorkbookDeactivate(ByVal Wb As Workbook)
    If closing_event Then
        ' ... do your cleaning stuff
    End If
End Sub

Private Sub Class_Initialize()
    Set m_app = Excel.Application
End Sub

Создать экземпляр ExcelApplication в классе ThisWorkbook

Private m_excelApplication As ExcelApplication

Private Sub Workbook_Open()
    Set m_excelApplication = New ExcelApplication
End Sub
0 голосов
/ 09 ноября 2019

Это похоже на работу Код в WorkBook

Public ByeBye As String

Private Sub Workbook_BeforeClose(Cancel As Boolean)
   ByeBye = "BB @ " & Now()
End Sub

Private Sub Workbook_Deactivate()
   If Left(ByeBye, 2) = "BB" Then
      ByeBye="Done"
      MsgBox "Closing"
   Else
      ByeBye="Done"
      MsgBox "DeActivating BUT NOT Closing"
   End If
End Sub

Private Sub Workbook_Open()
   ByeBye = "OP @ " & Now()
End Sub

Просто используется открытая переменная ByeBye

  • Вы должны инициализировать его в WorkBook.Open
  • Необходимо установить его в WorkBook.BeforeClose

и проверить его в WorkBook.DeActivate

В случае, если это необходимо для работы даже после сбоя VBA- и потеря значения ByeBye Я сбрасываю его в Workbook_SheetChange и WorkBook_SheetSelectionChange

Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
   ByeBye = "SC @ " & Now()
End Sub

Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
   ByeBye = "SSC @ " & Now()
End Sub

Приведенное выше дополнение действительно необходимо только в том случае, если вы собираетесь использовать строку по умолчанию "" для проверенного значения -но я использую "BB @" & Now (), так что это на самом деле не нужно

0 голосов
/ 31 октября 2019

Этот пост может быть полезен https://www.dummies.com/software/microsoft-office/excel/an-excel-macro-to-save-a-workbook-before-closing/

Я нашел код ниже из книги Excel 2016 Power Programming с VBA , автор Michael Alexander

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Dim msg As String, ans as integer
    If Me.Saved = False Then
        msg = "Do you want to save?"
        ans = MsgBox(msg, vbquestion+vbyesnocancel)
        Select Case ans
            Case vbYes: Me.Save
            Case vbCancel: Cancel = True
        End Select
    End If
    Call mySub
    Me.Saved = True
End Sub
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...