countdown in vba userform triggered by a function - vba

I would like to start a countdown form from a running code. The problem is if I state
> TimerForm.Show
Then the user form is shown, however the countdown is not triggered. How can I trigger a countdown simply from a function where the countdown is shown in the user form TimerLabel, like below
TimerForm.TimerLabel.Caption = Format(TimeSerial(0, 0, nTime), "hh:mm:ss") & " seconds "
nTime would then be decreased from an initial value of 30 seconds.
The answer Below totally solves the problem. The cancel button for the Timer should be
Private Sub CancelTimer_Click()
Application.OnTime EarliestTime:=earliest, _
Procedure:=handler, _
schedule:=False
Unload Me
End
End Sub
If not the Application.OnTime continues running in the background.

Do not know if understand the question correctly, but here are some thouhts. HTH
Standard module code
Option Explicit
Public Sub test()
' One possibility with modeless user form
' In this case it is possible to start timer after
' timer form was displayed, because the form is modeless
' TimerForm.Show vbModeless
' TimerForm.StartTimer
' Other possiblity with modal user form
' In this case the StartTimer has to be called
' from inside of user form because the form is modal.
TimerForm.Show
End Sub
Public Sub TimerElapsed()
TimerForm.OnTimerElapsed
End Sub
TimerForm class module code
' More info about OnTime:
' http://www.cpearson.com/excel/ontime.aspx
Option Explicit
Private Const interval As Integer = 1 ' second
Private Const countdownInit As Integer = 30 ' seconds
Private Const handler As String = "TimerElapsed"
Private earliest As Double
Private countdown As Integer
Private Sub UserForm_Initialize()
countdown = countdownInit
StartTimer
End Sub
Private Sub Cancel_Click()
StopTimer
Unload Me
End Sub
Public Sub StartTimer()
earliest = Now + TimeSerial(0, 0, interval)
Application.OnTime EarliestTime:=earliest, _
Procedure:=handler, _
Schedule:=True
End Sub
Public Sub StopTimer()
On Error Resume Next
countdown = 0
Application.OnTime EarliestTime:=earliest, _
Procedure:=handler, _
Schedule:=False
End Sub
Public Sub OnTimerElapsed()
If countdown <= 0 Then
Me.TimerLabel.Caption = "00:00:00 seconds "
Exit Sub
End If
Dim timerInfo As String
timerInfo = Format(TimeSerial(0, 0, countdown), "hh:mm:ss")
Me.TimerLabel.Caption = timerInfo & " seconds "
countdown = countdown - interval
StartTimer ' <--- 'How can I trigger a countdown simply
' from a function where the countdown is shown
' in the user form TimerLabel ...'
' Here the OnTime is re-scheduled for next call.
End Sub

Related

VBA - Difficulty interrupting Application.OnTime

I am having difficult interrupting a looped Application.OnTime command. I have a macro I want to periodically loop every 5 seconds (CommandButton221), but then I want to be able to cut the loop off by pressing StopButton.
Private Sub CommandButton221_Click()
If StopMacro = True Then Exit Sub
'[Code that I want looping, not relevant to the question]
testup1
End Sub
Public Sub testup1()
RunWhen = Now + TimeValue("00:00:05")
Application.OnTime RunWhen, "CommandButton221_Click", , True
End Sub
Public Sub StopButton_Click()
StopMacro = True
End Sub
Any suggestions as how to achieve this would be hugely appreciated!
This should do it, a slightly different method, removing the schedule entirely when hitting the Stop button:
Public RunWhen As Double
Private Sub CommandButton221_Click()
Application.StatusBar = Now()
testup1
End Sub
Public Sub testup1()
RunWhen = Now + TimeValue("00:00:05")
Application.OnTime RunWhen, "CommandButton221_Click", , True
End Sub
Public Sub StopButton_Click()
On Error Resume Next
Application.OnTime EarliestTime:=RunWhen, Procedure:="CommandButton221_Click", Schedule:=False
End Sub
Make sure the code you have in 221 doesn't take more than 5 seconds to run!
Try using the command DoEvents at the end of testup1() to yield control back to the O.S., which should allow the stop button event to occur. It is very easy for VBA to get stuck in loops with timers and/or heavy data processing and ignore other events.

vba start stop timers?

I want to start and stop two timers in my excel worksheet. This is what i have:
When worksheet is opened, this module is triggered:
Sub SetOpenTimer()
OpenTimer = Now + TimeValue("00:00:10")
Application.OnTime OpenTimer, "StartTimerShutdownForm"
End Sub
After 10 seconds (in this example), the sub startimershutdownform is triggered:
Sub StartTimerShutdownForm()
UserForm1.Show
End Sub
This displays a userform where a message is displayed. In the initialize sub in the Userform a sub setformtimer is triggered:
Sub setFormTimer()
FormTimer = Now + TimeValue("00:00:10")
Application.OnTime FormTimer, "Shutdown"
End Sub
If this timer in setformtimer ends, the sub shutdown is triggerd
this sub closes the workbook, but in the Userform there is a button for the user to cancel the setformtimer:
Sub stopFormTimer()
Application.OnTime EarliestTime:=FormTimer, _
Procedure:="setOpenTimer", Schedule:=False
End Sub
This suppose to stop the setformtimer, but it does not work.
When this timer is stopped, it suppose to restart the timer that is triggered when the document is being opened, so it all restarts, but the stop timer code does not work.
In the module where stopFormTimer, setFormTimer and StartTimerShutdownForm is located, at the top I declare these
Dim OpenTimer As Date
Dim FormTimer As Date
What am i missing here? Why cant i stop the timer and restart the first timer?
I think the problem is you have different proc names in the two subs. They must match. I'd try:
Sub setFormTimer()
FormTimer = Now + TimeValue("00:00:10")
Application.OnTime EarliestTime:=FormTimer, Procedure:="Shutdown", Schedule:=True
End Sub
Sub stopFormTimer()
Application.OnTime EarliestTime:=FormTimer, Procedure:="Shutdown", Schedule:=False
End Sub

automatic close excel workbook with a pop up message

I am trying to create a macro that can automatically close the workbook within 5 minutes plus there will be a pop up reminder message at 4 mins 30 sec. I want the message box to be automatically closed in 10 seconds if user does not click the ok button. I am stuck at the point that the message box cannot close within 10 seconds. Most of my code are copied from the internet. Below are my codes:
In the workbook page:
Private Sub workbook_open()
Call settimer
End Sub
Private Sub workbook_beforeclose(cancel As Boolean)
Call stoptimer
End Sub
Private Sub workbook_sheetcalculate(ByVal sh As Object)
Call stoptimer
Call settimer
End Sub
Private Sub workbook_sheetselectionchange(ByVal sh As Object, _
ByVal target As Excel.Range)
Call stoptimer
Call settimer
End Sub
In the module
Dim downtime As Date
Sub settimer()
downtime = Now + TimeValue("00:01:00")
alerttime = downtime - TimeValue("00:00:50")
Application.OnTime Earliesttime:=alerttime, _
procedure:="alertuser", schedule:=True
Application.OnTime Earliesttime:=Downtime, _
procedure:="shutdown", schedule:=True
End Sub
Sub stoptimer()
On Error Resume Next
Application.OnTime Earliesttime:=downtime, _
procedure:="shutdown", schedule:=False
End Sub
Sub shutdown()
Application.DisplayAlerts = True
With ThisWorkbook
.Save = True
.Close
End With
End Sub
Sub alertuser()
Dim wsshell
Dim intText As Integer
Set wsshell = CreateObject("WScript.Shell")
intText = wsshell.Popup("log sheet will be closed in 30 seconds if there are no more inputs", 10, "reminder")
Set wsshell = Nothing
End Sub
You need to fully qualify your procedure name. It is not finding the procedure in question. You also had a typo and where missing the global variable alerttime. Try this:
Public downtime As Date
Public alerttime As Date
Private Sub workbook_open()
Call settimer
End Sub
Private Sub workbook_beforeclose(cancel As Boolean)
Call stoptimer
End Sub
Private Sub workbook_sheetcalculate(ByVal sh As Object)
Call stoptimer
Call settimer
End Sub
Private Sub workbook_sheetselectionchange(ByVal sh As Object, _
ByVal target As Excel.Range)
Call stoptimer
Call settimer
End Sub
Sub settimer()
downtime = Now + TimeValue("00:01:00")
alerttime = downtime - TimeValue("00:00:50")
'fully qualify your procedure name here and the procedure will run
Application.OnTime Earliesttime:=alerttime, _
procedure:="WorkbookName.xlsm!ThisWorkbook.alertuser", schedule:=True
'and here... also typo was here in downtime
Application.OnTime Earliesttime:=downtime, _
procedure:="WorkbookName.xlsm!ThisWorkbook.shutdown", schedule:=True
End Sub
Sub stoptimer()
On Error Resume Next
Application.OnTime Earliesttime:=downtime, _
procedure:="shutdown", schedule:=False
End Sub
Sub shutdown()
Application.DisplayAlerts = True
With ThisWorkbook
.Save = True
.Close
End With
End Sub
Sub alertuser()
Dim wsshell
Dim intText As Integer
Set wsshell = CreateObject("WScript.Shell")
intText = wsshell.Popup("log sheet will be closed in 30 seconds if there are no more inputs", 10, "reminder")
Set wsshell = Nothing
End Sub
You could use a userform (which you Insert into your project in the VBA editor) which looks something like this:
In the properties window I changed the forms name to formReminder to make it easier to refer to in other modules. Then, in the userform's code window I put:
Private Running As Boolean
Private Sub CommandButton1_Click()
Running = False
End Sub
Private Sub UserForm_Activate()
Dim start As Single
start = Timer
Running = True
Do While Running And Timer < start + 10
DoEvents
Loop
Unload Me
End Sub
Private Sub UserForm_Click()
Running = False
End Sub
When you run the line formReminder.Show anywhere else in the code (e.g. -- in place of where you create the popup) the form will display and show for 10 seconds (or less if you click anywhere on it) and then disappear.
While it displays it will look like this:
Thanks, John Coleman for your answer. It led me to a solution I've wanted for a long time. I took your code and converted it into a generic function that accepts parameters for the message and the number of seconds to wait.
Sub MsgBoxTimerTest()
' Test the Message box with a timer form
Dim vReturn As Variant
vReturn = MsgBoxTimerCall("MessageBox that Dissappears after n Seconds", "Hello World!", 3)
End Sub
' **************************************************************************
Function MsgBoxTimerCall(strCaption As String, strMessage As String, intSeconds As Integer)
' Show a messagebox for a while
' https://stackoverflow.com/questions/37281840/automatic-close-excel-workbook-with-a-pop-up-message
' 2016-06-21
TimerSeconds = intSeconds
msgBoxTimerForm.Caption = strCaption
msgBoxTimerForm.TextBox1.Value = strMessage
msgBoxTimerForm.Show
End Function
' **************************************************************************
' **************************************************************************
Insert this code in the form
' **************************************************************************
Private Running As Boolean
Private Sub CommandButton1_Click()
MsgBox "Yo!"
Running = False
End Sub
Private Sub UserForm_Activate()
Dim start As Single
start = Timer
Running = True
Do While Running And Timer < start + TimerSeconds
DoEvents
Loop
Unload Me
End Sub
Private Sub UserForm_Click()
Running = False
End Sub

Creating a Macro in Excel which executes the other Macro every 2 seconds

I have a macro called UpdateMacro which updates my Database from my Excel
I want to create a macro called RepeatMacro where it executes the UpdateMacro every 2 seconds automatically and only Start and Stop Buttons are to be provided to start and Stop execution of the RepeatMacro.
How can it be done?
Google for Application.OnTime
E.g.
Dim dtNextRunTime As Date
dtNextRunTime = Now + TimeSerial(0,0,2)
Application.OnTime dtNextRunTime, "MyProcedure", True
To clear a previously set procedure, you need to save the time at which it was scheduled )e.g. dtNextRunTime above), then use:
Application.OnTime dtNextRunTime, "MyProcedure", False
Here's a sample VB module with methods StartSchedule / StopSchedule to get you going:
Private m_dtScheduledTime As Date
Private m_lDelaySeconds As Long
Private m_bIsScheduled As Boolean
Private Sub DoWork()
m_bIsScheduled = False
' ... do your work
' Reschedule at the same frequency once completed
StartSchedule m_lDelaySeconds, "DoWork"
End Sub
Public Sub StartSchedule(ByVal DelaySeconds As Long)
StopSchedule
m_lDelaySeconds = DelaySeconds
m_dtScheduledTime = Now + TimeSerial(0, 0, m_lDelaySeconds)
Application.OnTime m_dtScheduledTime, "DoWork", True
m_bIsScheduled = True
End Sub
Public Sub StopSchedule()
If m_bIsScheduled Then
Application.OnTime m_dtScheduledTime, "DoWork", False
m_bIsScheduled = False
End If
End Sub
This will run UpdateMacro every two seconds, assuming UpdateMacro takes less than two seconds to run.
Sub RepeatMacro()
Dim lastRunTime
Do
lastRunTime = Now
Range("A1") = "Last run: " & Format(lastRunTime, "hh:nn:ss")
Call UpdateMacro
DoEvents
Application.Wait lastRunTime + TimeValue("00:00:02")
Loop
End Sub
Sub UpdateMacro()
Debug.Print "running UpdateMacro"
End Sub
EDIT To start and stop RepeatMacro from your sheet, make a start button and a stop button, and put the following code in your Sheet module. Also notice that I added a DoEvents in RepeatMacro above.
Private Sub StartButton_Click()
Call RepeatMacro
End Sub
Private Sub StopButton_Click()
MsgBox "Stopped."
End
End Sub
Now just saying End is poor practice, but you get the idea.

Timer on user form in Excel VBA

I've got some old Excel VBA code where I want to run a task at regular intervals. If I were using VB6, I would have used a timer control.
I found the Application.OnTime() method, and it works well for code that's running in an Excel worksheet, but I can't make it work in a user form. The method never gets called.
How can I make Application.OnTime() call a method in a user form, or are there other ways to schedule code to run in VBA?
I found a workaround for this. If you write a method in a module that just calls a method in your user form, then you can schedule the module method using Application.OnTime().
Kind of a kludge, but it'll do unless somebody has a better suggestion.
Here's an example:
''//Here's the code that goes in the user form
Dim nextTriggerTime As Date
Private Sub UserForm_Initialize()
ScheduleNextTrigger
End Sub
Private Sub UserForm_Terminate()
Application.OnTime nextTriggerTime, "modUserformTimer.OnTimer", Schedule:=False
End Sub
Private Sub ScheduleNextTrigger()
nextTriggerTime = Now + TimeValue("00:00:01")
Application.OnTime nextTriggerTime, "modUserformTimer.OnTimer"
End Sub
Public Sub OnTimer()
''//... Trigger whatever task you want here
''//Then schedule it to run again
ScheduleNextTrigger
End Sub
''// Now the code in the modUserformTimer module
Public Sub OnTimer()
MyUserForm.OnTimer
End Sub
I needed a visible countdown timer that could stay on top of other windows and run smoothly whether making changes to the workbook, or minimizing the Excel window. So, I adapted the #don-kirkby's creative code above for my own purposes and figured I'd share the result.
                      
The code below requires creation of a module and a userform as noted in the comments, or you can download the .xlsm at the bottom of this answer.
I used the Windows Timer API for more accurate and smooth countdown (and also customizable down to ~100 millisecond timer resolution, depending on your processor. There's even a "tick tock" sound. ⏰
Insert a new module and save it as modUserFormTimer. Add two form control command buttons to the worksheet, labelled Start Timer and Stop Timer and assigned procedures btnStartTimer_Click and btnStopTimer_Click.
Option Explicit 'modUserFormTimer
Public Const showTimerForm = True 'timer runs with/without the userform showing
Public Const playTickSound = True 'tick tock (a WAV sounds could be embedded: `https:// goo.gl/ ReuUyd`)
Public Const timerDuration = "00:00:20" 'could also Insert>Object a WAV for tick or alarm
Public Const onTimerStart_MinimizeExcel = True 'minimize Excel? (countdown remains visible)
Public Const onTimerStart_MaximizeExcel = True 'maximize Excel when timer completes?
'timer could be on top of other applications; instructions here: `https:// goo.gl/ AgmWrM`
'safe for 32 or 64 bit Office:
Private Declare PtrSafe Function SetTimer Lib "User32" (ByVal hWnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As Long
Private Declare PtrSafe Function KillTimer Lib "User32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
Public Declare PtrSafe Function Beep Lib "kernel32" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
Public schedTime As Date 'this is the "major" timer set date
Private m_TimerID As Long
Public Sub OnTimerTask()
'the procedure that runs on completion of the "major timer" (timer won't reschedule)
Unload frmTimer
''''''''''''''''''''''''''''''
MsgBox "Do Something!" ' < < < < < Do Something Here
''''''''''''''''''''''''''''''
End Sub
Public Sub btnStartTimer_Click()
schedTime = Now() + TimeValue(timerDuration)
InitTimerForm
End Sub
Public Sub btnStopTimer_Click()
'clicking the 'x' on the userform also ends the timer (disable the close button to force continue)
schedTime = 0
frmTimer.UserForm_Terminate
End Sub
Public Sub InitTimerForm()
'run this procedure to start the timer
frmTimer.OnTimer
Load frmTimer
If showTimerForm Then
If onTimerStart_MinimizeExcel Then Application.WindowState = xlMinimized
frmTimer.Show 'timer will still work if userform is hidden (could add a "hide form" option)
End If
End Sub
Public Sub StartTimer(ByVal Duration As Long)
'Begin Millisecond Timer using Windows API (called by UserForm)
If m_TimerID = 0 Then
If Duration > 0 Then
m_TimerID = SetTimer(0, 0, Duration, AddressOf TimerEvent)
If m_TimerID = 0 Then
MsgBox "Timer initialization failed!", vbCritical, "Timer"
End If
Else
MsgBox "The duration must be greater than zero.", vbCritical, "Timer"
End If
Else
MsgBox "Timer already started.", vbInformation, "Timer"
End If
End Sub
Public Sub StopTimer()
If m_TimerID <> 0 Then 'check if timer is active
KillTimer 0, m_TimerID 'it's active, so kill it
m_TimerID = 0
End If
End Sub
Private Sub TimerEvent()
'the API calls this procedure
frmTimer.OnTimer
End Sub
Next, create a userform, save it as frmTimer. Add a text box named txtCountdown. Set property ShowModal to False. Paste the following into the form's code window:
Option Explicit 'code for userform "frmTimer"
'requires a textbox named "txtCountdown" and "ShowModal" set to False.
Dim nextTriggerTime As Date
Private Sub UserForm_Initialize()
ScheduleNextTrigger
End Sub
Public Sub UserForm_Terminate()
StopTimer
If schedTime > 0 Then
schedTime = 0
End If
If onTimerStart_MaximizeExcel Then Application.WindowState = xlMaximized 'maximize excel window
Unload Me
End Sub
Private Sub ScheduleNextTrigger() 'sets the "minor" timer (for the countdown)
StartTimer (1000) 'one second
End Sub
Public Sub OnTimer()
'either update the countdown, or fire the "major" timer task
Dim secLeft As Long
If Now >= schedTime Then
OnTimerTask 'run "major" timer task
Unload Me 'close userForm (won't schedule)
Else
secLeft = CLng((schedTime - Now) * 60 * 60 * 24)
If secLeft < 60 Then 'under 1 minute (don't show mm:ss)
txtCountdown = secLeft & " sec"
Else
'update time remaining in textbox on userform
If secLeft > 60 * 60 Then
txtCountdown = Format(secLeft / 60 / 60 / 24, "hh:mm:ss")
Else 'between 59 and 1 minutes remain:
txtCountdown = Right(Format(secLeft / 60 / 60 / 24, "hh:mm:ss"), 5)
End If
End If
If playTickSound Then Beep 16000, 65 'tick sound
End If
End Sub
Download the demo .xksm. here. There are numerous ways this can be customized or adapted to specific needs. I'm going to use it to calculated and display real time statistics from a popular Q&A site in the corner of my screen...
Note that, since it contains VBA macro's, the file could may set off your virus scanner (as with any other non-local file with VBA). If you're concerned, don't download, and instead build it yourself with the information provided.)
How about moving all the code to a 'Timer' module.
Dim nextTriggerTime As Date
Dim timerActive As Boolean
Public Sub StartTimer()
If timerActive = False Then
timerActive = True
Call ScheduleNextTrigger
End If
End Sub
Public Sub StopTimer()
If timerActive = True Then
timerActive = False
Application.OnTime nextTriggerTime, "Timer.OnTimer", Schedule:=False
End If
End Sub
Private Sub ScheduleNextTrigger()
If timerActive = True Then
nextTriggerTime = Now + TimeValue("00:00:01")
Application.OnTime nextTriggerTime, "Timer.OnTimer"
End If
End Sub
Public Sub OnTimer()
Call MainForm.OnTimer
Call ScheduleNextTrigger
End Sub
Now you can call from the mainform:
call Timer.StartTimer
call Timer.StopTimer
To prevent errors, add:
Private Sub UserForm_Terminate()
Call Timer.StopTimer
End Sub
Wich will trigger:
Public Sub OnTimer()
Debug.Print "Tick"
End Sub
Thanks to user1575005 !!
Used the code in a Module to setup a Timer() process:
Dim nextTriggerTime As Date
Dim timerActive As Boolean
Public Sub StartTimer()
Debug.Print Time() & ": Start"
If timerActive = False Then
timerActive = True
Call ScheduleNextTrigger
End If
End Sub
Public Sub StopTimer()
If timerActive = True Then
timerActive = False
Application.OnTime nextTriggerTime, "OnTimer", Schedule:=False
End If
Debug.Print Time() & ": End"
End Sub
Private Sub ScheduleNextTrigger()
If timerActive = True Then
nextTriggerTime = Now + TimeValue("00:00:10")
Application.OnTime nextTriggerTime, "OnTimer"
End If
End Sub
Public Sub OnTimer()
Call bus_OnTimer
Call ScheduleNextTrigger
End Sub
Public Sub bus_OnTimer()
Debug.Print Time() & ": Tick"
Call doWhateverUwant
End Sub
Private Sub doWhateverUwant()
End Sub