Excel/PowerQuery/VBA, Анализ данных

Макрос VBA в фоновом режиме

Время прочтения: 4 мин.
  • в каких случаях важно запускать макрос в «фоновом» режиме и почему необходимо реагировать на действия пользователя;
  • пример реализации работы «фонового» приложения (проверено на версиях c 2007);
  • актуализация программного кода в приведенном примере к версиям 2013-2016.

Зачем скрывать приложение с экрана?

Приложение с выполняющимся макросом имеет смысл переводить в «фоновый» режим, если предполагается, что пользователь продолжит работать за компьютером, при этом без необходимости контролировать работу макроса: скрытое с экрана приложение не вызовет раздражение пользователя «непрорисованной» экранной областью, не потребует ресурсов для такой прорисовки, не будет подвержено воздействию пользователя на формирующуюся книгу (например, активацией объектов книги, использованием буфера памяти приложения).

Как реализовать?

Объектная модель Excel позволяет работать с несколькими книгами (объектами Workbook) в одном приложении (в объекте Application). А значит, приложение, скрытое с экрана для выполнения макроса, может быть вызвано операционной системой для попытки открыть в нем указанный пользователем файл. В результате, попытка открытия файла либо будет безрезультатной, либо пользователю придется ожидать окончания выполнения скрытого макроса для возможности приступить к работе с файлом.

  1. Чтобы скрытое приложение могло реагировать должным образом на загрузку пользовательского файла, необходимо указать порядок действий в событиях приложения (события объекта Application) – реализую их в отдельном классе.
ExAppClass (class module)
'Переменная уровня приложения
Private WithEvents ExApp As Excel.Application


'Указывает объект для управления
Public Function SetExApp(ByVal NewExApp As Excel.Application) As Excel.Application
    Set SetExApp = Nothing
    
    If (NewExApp Is Nothing) Then
        Exit Function
    End If
    
    If (Not ExApp Is Nothing) Then
        Call ToNothingApp
    End If
    
    Set ExApp = NewExApp
    Set SetExApp = ExApp
End Function

'Возвращает ссылку на управляемый объект
Public Function GetExApp() As Excel.Application
    Set GetExApp = ExApp
End Function


'Заново открывает пользовательскую книгу в новом приложении
Private Sub ExApp_WorkbookOpen(ByVal Wb As Workbook)
    Dim TransApp As Excel.Application
    Dim TransWbName As String
    
    'Запоминаю
    TransWbName = Wb.FullName
    Wb.Close False
    Set Wb = Nothing
    
    'Открываю заново
    Set TransApp = New Excel.Application
    TransApp.Visible = True
    TransApp.Workbooks.Open TransWbName
    Set TransApp = Nothing
End Sub


'Заново создает пустую книгу в отдельном приложении
Private Sub ExApp_NewWorkbook(ByVal Wb As Workbook)
    'Реализация аналогична процедуре ExApp_WorkbookOpen()
End Sub


'Заново открывает пользовательскую книгу в новом приложении с режимом просмотра
'(реализация процедуры требуется при наличие данного события в версии Excel)
Private Sub ExApp_ProtectedViewWindowOpen(ByVal Pvw As ProtectedViewWindow)
    Dim TransApp As Excel.Application
    Dim TransWbName As String
    
    'Запоминаю
    TransWbName = Pvw.Workbook.FullName
    Pvw.Workbook.Close False
    Pvw.Close
    Set Pvw = Nothing
    
    'Открываю заново
    Set TransApp = New Excel.Application
    TransApp.Visible = True
    TransApp.ProtectedViewWindows.Open TransWbName
    Set TransApp = Nothing
End Sub


'Уничтожает ссылку на приложение
Public Sub ToNothingApp()
    If (Not ExApp Is Nothing) Then
        ExApp.Visible = True
        Set ExApp = Nothing
    End If
End Sub


'Уничтожает объект, управляющий приложением
Private Sub Class_Terminate()
    Call ToNothingApp
End Sub

Стоит отметить, что в подобных задачах часто реализуются возможности для управления «своим» или «чужим» запущенным приложением Excel, определяется количество допустимых для обработки книг, список допустимых к открытию книг.

  1. Для скрытия приложения с экрана и запуска в нем требуемого функционала, добавляю программный модуль.
ExecModule (module)
'"Захватывает" приложение и запускает искомый макрос
Public Sub Exec()
    Dim clsProgApp As New ExAppClass
    
    With clsProgApp
        'Начинаю управлять «своим» приложением
        Call .SetExApp(NewExApp:=ThisWorkbook.Application)
        'Скрываю приложение
        .GetExApp.WindowState = xlMinimized
        .GetExApp.Visible = False
        'Запускаю макрос с искомым функционалом
        Call ResultRun(.GetExApp)
        'Отображаю приложение с искомым результатом
        .GetExApp.Visible = True
        .GetExApp.WindowState = xlNormal
        
        'Перестаю управлять объектом Application
        Call .ToNothingApp
    End With
    
    Set clsProgApp = Nothing
End Sub


Private Function ResultRun(ByVal ManagerExApp As Excel.Application) As Boolean
    'Здесь реализуется какой-либо искомый функционал...
    
    'Продолжительный по времени функционал
    'должен периодически вызывать функцию DoEvents
    'для реагирования процесса на действия пользователя
End Function

Что актуализировать?

Приложение Excel, начиная с версии 2013, позволяет пользователю работать с книгами в отдельных окнах (реализуя Single Document Interface). При этом, книги фактически, как и в прежних версиях, могут быть загружены в коллекцию одного приложения. Потому приведенный пример программного кода в целом остается работоспособным за исключением обнаруженных на практике «специфического поведения» приложения.

При попытке закрыть вызванный пользователем файл, происходит обращение к скрытому приложению, в котором он в данный момент загружен. Важно временно отобразить скрытое приложение, перед закрытием файла. Иначе в памяти остаются «висеть» ссылки на объекты закрываемого файла. Приведу пример исправления события.

ExAppClass (class module)
'(Для интерфейса SDI) заново открывает пользовательскую книгу в новом приложении
Private Sub ExApp_WorkbookOpen(ByVal Wb As Workbook)
    Dim TransApp As Excel.Application
    Dim TransWbName As String
    
    Dim AppVersion As Integer
    Dim SDIversion As Integer: SDIversion = 15
    
    With ExApp
        AppVersion = Fix(Replace(.Version, ".", ","))
        If (AppVersion >= SDIversion) Then
            'Временно отображаю скрытое приложение
            .Visible = True
        End If
        
        'Запоминаю
        TransWbName = Wb.FullName
        Wb.Close False
        Set Wb = Nothing
        
        If (AppVersion >= SDIversion) Then
            'Скрываю приложение
            .Visible = False
        End If
    End With
    
    'Открываю заново
    Set TransApp = New Excel.Application
    TransApp.Visible = True
    TransApp.Workbooks.Open TransWbName
    Set TransApp = Nothing
End Sub

Советуем почитать