Pull to refresh

Таймер в Excel

Не помню кто из великих сказал… И не помню что. Но в процессе разработки vba-приложений время от времени возникает необходимость использовать таймер. «Из коробки» решения, к сожалению, нет.

Application.OnTime?

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

Application.OnTime + рекурсия?

Очевидно, что в конце необходимой процедуры можно вызывать её же с помощью данного метода, но не менее очевидны и недостатки этого решения — отсутствие достаточного контроля и неудобство вызова:
  • не таймер вызывает действие;
  • чтобы остановить цикл придётся объявлять глобальную переменную;
  • добавление «костыля» в тело отдельно взятой процедуры. Если в следующий раз будет нужно вызвать по таймеру другую — то и в неё придётся переносить данный «костыль».

Evaluate в помощь?

Написать свой класс и вызывать внутри необходимую процедуру с помощью Application.Evaluate. Особо упорные в стремлении создать приемлемый метод вызова таймера доходят до этого решения и упираются в стран/ш/ный баг, отловить который достаточно сложно: udf-функция переданная в переменной будет вызываться каждый раз по два(!) раза.

Потому что гладилус

Способ обойти этот баг пока найден лишь один:
ActiveSheet.Evaluate "0+" & nameOfProcedure

Решение

Для простоты вызова написан не класс, а метод. Достаточно указать интервал, имя вызываемой процедуры и, опционально, кол-во необходимых вызовов:
StartTimer 60, "CalculateTotal()", 30 'вызов CalculateTotal() каждую минуту в течении получаса.
StopTimer 'остановка таймера
модуль
Option Explicit

Private Type Timer
    interval As Long
    procedure As String
    times As Long
    enabled As Boolean
    initialized As Boolean
    ticks As Long
End Type

Private Timer As Timer

Public Sub StartTimer(ByRef interval As Long, _
                      ByRef procedure As String, _
                      Optional ByRef times As Long)
With Timer
    .interval = interval
    .procedure = procedure
    .times = times
    .enabled = True
    .initialized = False
    .ticks = 0
End With

InvokeTimer
End Sub

Public Sub StopTimer()
Timer.enabled = False
End Sub

Private Function InvokeTimer()
If Timer.ticks > Timer.times And Not Timer.times = 0 Then StopTimer
If Not Timer.enabled Then Exit Function

If Timer.initialized Then
    ActiveSheet.Evaluate "0+" & Timer.procedure
Else
    Timer.initialized = True
End If

Timer.ticks = Timer.ticks + 1

Application.OnTime Now + 1 / 86400 * Timer.interval, "InvokeTimer"
End Function
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.