How can I implement a stopwatch with VBA? - excel

I'm trying to set up a basic stopwatch for tracking elapsed time. Button1 should start the clock at 0, and Button2 should stop the watch and record how many minutes and seconds have elapsed since the timer started. I have the buttons lodged on a worksheet and just need a way to calculate elapsed time. What would be the best approach?
Here is the code I have been working with:
Private Sub CommandButton1_Click()
startTime = Timer
End Sub
Private Sub CommandButton2_Click()
Dim finalTime As Single
finalTime = Timer - startTime
MsgBox Format(finalTime, "0.00")
End Sub
The problem with this code is that it displays the time elapsed only after I click Button2. I need a running timer on my Excel sheet.

Public Started As Boolean
Dim myTime As Date
Sub StartTimer()
If Not Started Then
myTime = Time
Started = True
Else
If MsgBox("Do you really want to restart?", vbYesNo) = vbYes Then
myTime = Time
End If
End If
End Sub
Sub StopTimer()
If Not Started Then
MsgBox "The timer is stopped."
Else
MsgBox Format(Time - myTime, "hh:mm:ss")
Started = False
End If
End Sub
youtube
link

If you need any accuracy, Excel is probably not the place to do this. But if close enough is close enough, then here's how I would do it.
In a standard module:
Public gbStop As Boolean
Public gdtStart As Date
Sub UpdateTime()
Sheet1.Range("rngTimer").Value = Now - gdtStart
DoEvents
If Not gbStop Then
Application.OnTime Now + TimeSerial(0, 0, 1), "UpdateTime"
End If
End Sub
In Sheet1's class module
Private Sub CommandButton1_Click()
gbStop = False
gdtStart = Now
Application.OnTime Now + TimeSerial(0, 0, 1), "UpdateTime"
End Sub
Private Sub CommandButton2_Click()
gbStop = True
End Sub
When you click Button1, gbStop is set to false, the start time is recorded in a public variable, and the UpdateTime procedure is scheduled to run 1 second later.
One second later, UpdateTime runs and the value of the cell named rngTimer is changed to show the elapsed time between Now and the start recorded in Button1_Click. If gbStop is still False, UpdateTime schedules itself to run again in another second.
Eventually, you press Button2 which sets the public variable gbStop to True. The next time Updatetime runs, it doesn't schedule itself to run again.

Related

Problem Running multiple timers in MS excel using VBA

I am trying to run multiple timers in different cells in MS excel. I want to stop the timer only on the selected active cell.
The problem is when I am starting the timer on another cell. The previous timer on a previously chosen cell automatically stops.
The program I have now starts the timer upon double clicking.
I am trying to run different timers on the different cells and I want to stop the timer on the selected cells only using macros.
Here is what I have done so far:
The functions have been assigned to macros.
For a clearer explanation, let's say I start the timer in Cell A1 by double clicking(as coded in my worksheet). Now, if I start the timer in Cell A2, then the timer in A1 stops, and the timer in A2 runs. I want to run both the timers and stop the timer on the cell I select only while the other should still run.
I am scratching my head as to how I should modify my code to achieve this.
Option Explicit
Dim Tick As Date, t As Date
Global myCell As Range
Sub stopwatch()
t = Time
Call StartTimer
End Sub
Sub StartTimer()
Tick = Time + TimeValue("00:00:01")
myCell.Value = Format(Tick - t - TimeValue("00:00:01"), "hh:mm:ss")
Application.OnTime Tick, "StartTimer"
End Sub
Sub StopTimer()
On Error Resume Next
Application.OnTime EarliestTime:=Tick, Procedure:="StartTimer", Schedule:=False
End Sub
In my Worksheet , I have this code to start the timer on double click:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Set myCell = Target
StartTimer
Cancel = True
End Sub
Try this - using a Dictionary to track which cells have timers running.
Double-click a cell to start a timer there: double-click again to stop.
This code is all in the Sheet3 module (codename)
Option Explicit
Dim timers As Object 'cell addresses as keys and start times as values
Dim nextTime 'next run time
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
ToggleTimer Target
Cancel = True
End Sub
Sub ToggleTimer(Optional c As Range = Nothing)
Dim addr As String, k, macro
If timers Is Nothing Then Set timers = CreateObject("scripting.dictionary")
macro = Me.CodeName & ".ToggleTimer"
On Error Resume Next 'cancel any running timer
Application.OnTime EarliestTime:=nextTime, Procedure:=macro, Schedule:=False
On Error GoTo 0
If Not c Is Nothing Then '? called from a cell double-click ?
addr = c.Address(False, False)
If timers.exists(addr) Then
timers.Remove addr ' timer was running - remove it
Else
timers(addr) = Now ' timer was not running - add it
End If
End If
If timers.Count = 0 Then Exit Sub 'no more timers
For Each k In timers 'update timer(s)
Me.Range(k).Value = Format(Now - timers(k), "hh:mm:ss")
Next k
'schedule next run
nextTime = Now + TimeSerial(0, 0, 1)
Application.OnTime nextTime, Me.CodeName & ".ToggleTimer"
Debug.Print Me.CodeName
End Sub

Run a macro automatically but not in ThisWorkbook

I have a code to set a timer of 5 minutes on my userform. It works when I press the command button.
How do I make it run automatically at the start ? I tried in ThisWorkbook but it didn't worked.
here's the code :
In a module:
Public Const AllowedTime As Double = 1
In the Userform:
Private Sub CommandButton1_Click()
Dim userClickedPause As Boolean ' Gets set to True by the Pause button
Dim stopTime As Date
userClickedPause = False
' If AllowedTime is the number of minutes with a decimal part:
stopTime = DateAdd("s", Int(AllowedTime * 600), Now) ' add seconds to current time
' If AllowedTime is the number of seconds:
'stopTime = DateAdd("s", AllowedTime, Now) ' add seconds to current time
Do
With UserForm1.TextBox1
.Value = Format(stopTime - Now, "Nn:Ss")
End With
DoEvents
If userClickedPause = True Then
Exit Do
End If
Loop Until Now >= stopTime
End Sub
Private Sub CommandButton2_Click()
userClickedPause = True
End Sub
I would recommend you to re-structure your code. Since you want to call your StartTimer sub from both a UserForm and the Workbook_Open event, put it into a module.
Then you simply call the sub from your Commandbutton1_Click event and the Workbook_Open event.
'Put this code inside ThisWorkbook
Private Sub Workbook_Open()
'This sub into ThisWorkbook
Application.Run "SetTimer"
End Sub
'This sub goes into a module
Private Sub SetTimer()
'Here goes your code that sets the timer
'(move it from your UserForm)
'.......................................
'.......................................
End Sub
'The code inside your UserForm
Private Sub CommandButton1_Click()
Application.Run "SetTimer"
End Sub

How do you open a workbook whilst a macro is running in another workbook?

I am experiencing an issue where I created a VBA timer that counts down from a specified time to zero. This macro runs for a while, as such, when I try to open another workbook nothing happens, it is like the macro blocks the other workbook from opening?
My timer sub
Private Sub Timer15_main(play As Boolean)
Dim UserInput As String
If play Then
UserInput = TextBox1.Value 'this is what the user inputs and how long the timer should run
Else
timer_15_pause_button = False
UserInput = "00:15:00" 'this is what the user inputs and how long the timer should run
End If
'validate userinput und ensure hh:mm:ss format
Select Case Len(UserInput) - Len(Replace$(UserInput, ":", ""))
Case 2 'input format is hh:mm:ss
Case 1 'input format is mm:ss
UserInput = "00:" & UserInput
Case 0 'input format is ss
UserInput = "00:00:" & UserInput
Case Else
MsgBox "invalid input"
Exit Sub
End Select
'we need to convert the string UserInput into a double and
'convert it into seconds (Timer uses seconds!)
Dim SecondsToRun As Long
SecondsToRun = CDbl(TimeValue(UserInput)) * 24 * 60 * 60
TextBox4.Value = Format$((SecondsToRun / 24 / 60 / 60) + Time(), "hh:mm:ss")
Dim TimerStart As Double
TimerStart = Timer 'remember when timer starts
Do
If SecondsToRun - (Timer - TimerStart) < 10 Then
TextBox1.BackColor = RGB(255, 0, 0)
End If
TextBox1.Value = Format$((SecondsToRun - (Timer - TimerStart)) / 24 / 60 / 60, "hh:mm:ss")
'count backwards from 01:15 format as hh:mm:ss
DoEvents
If timer_15_pause_button = True Then
Exit Sub
End If
Loop While TimerStart + SecondsToRun > Timer 'run until SecondsToRun are over
TextBox1.BackColor = RGB(255, 255, 255)
'TextBox4.Value = ""
End Sub
Assuming the form is displayed like this:
UserForm1.Show
DoSomething
Then the form is modal, which means the DoSomething call will not run until the form is closed. While a modal form is displayed, it controls the message loop, and the host application is unavailable all the while: the only thing the user can interact with, is the form.
If the form is displayed like this:
UserForm1.Show vbModeless
DoSomething
Then the form is displayed, and the DoSomething call runs immediately; user can still interact with the host application, and code in the form can run asynchronously.
But a loop like this:
Do
' do stuff
DoEvents
Loop While {condition}
Is bad design: without the DoEvents, the loop would be hijacking the message loop completely, modal or modeless wouldn't make a difference, and the host application would likely go "(not responding)" until the loop finishes. With the DoEvents, that's a busy-loop constantly telling Windows "hey you got anything to run? go ahead then!" - what you want to do, is register a procedure that will be invoked once per second to update the timer label on the form.
That procedure needs to be in a separate, standard module - ideally the same module that's showing the form, and ideally working off the same instance of that form.
Option Explicit
Private theForm As UserForm1
Public Sub ShowTheForm()
If theForm Is Nothing Then Set theForm = New UserForm1
theForm.Show vbModeless
End Sub
Public Sub OnTimerTick()
If theForm Is Nothing Then Exit Sub
theForm.HandleTimerTick
End Sub
Now the form needs to expose a HandleTimerTick procedure, and schedule the OnTimerTick macro. So you might have a CommandButton control that, on click, begins the scheduling loop:
Dim TimerStart As Double
Private Sub CommandButton1_Click()
' validate inputs...
TimerStart = Timer
Application.OnTime Now + TimeValue("00:00:01"), "OnTimerTick"
End Sub
Public Sub HandleTimerTick()
'timer has ticked, we're at least 1 second later.
Dim secondsElapsed As Double
secondsElapsed = Timer - TimerStart
'update the textbox accordingly...
TextBox1.Text = Format$(secondsToRun - secondsElapsed, "hh:mm:ss")
'now determine if we need to schedule another tick:
If Int(secondsToRun - secondsElapsed) > 0 Then
Application.OnTime Now + TimeValue("00:00:01"), "OnTimerTick"
End If
End Sub
Notice there's no explicit loop anymore, no DoEvents: just a scheduled macro that tells the form "hey there, tick!" and the form responds by updating itself and, if needed, re-scheduling another tick.

Countdown can't be cancelled

I have built a small 5 minute Countdown in a UserForm.
The countdown should count down. But if the UserForm is closed, the macro should stop and the timer should be reset.
In the UserForm's Terminateevent I have stored the call of my own abort function. This terminates the UserForm, but the timer continues to run in the background and the corresponding macro is always brought to the foreground.
How can the code be modified to stop the timer when the UserForm is closed?
Module 1
Dim Count As Date
Sub callBreak()
Break.Show
End Sub
Sub Time()
Count = Now + TimeValue("00:00:01")
Application.OnTime Count, "minus"
End Sub
Sub minus()
Dim y As Date
y = Break.Label1.Caption
y = y - TimeSerial(0, 0, 1)
Break.Label1.Caption = y
If y <= 0 Then
Break.Label1.Caption = "Arbeit, Arbeit!"
Exit Sub
End If
Call Time
End Sub
Sub abord()
End
End Sub
Userform:
Sub UserForm_Initialize()
Call Coffeebreak.Time
End Sub
Sub UserForm_Terminate()
Call Coffeebreak.abord
End Sub
You can cancel a previously scheduled procedure by setting the optional Schedule argument of Application.OnTime to False. Try this as your Abord() function:
Sub Abord()
Application.OnTime EarliestTime:=Count, Procedure:="minus", Schedule:=False
End Sub
One solution:
in your module, define a global boolean variable, eg isCancelled. Set this variable to true in abort and check it in minus to prevent that the timer is called again.
Dim Count As Date
Dim isCancelled as Boolean
Sub Time()
isCancelled = False
Count = Now + TimeValue("00:00:01")
Application.OnTime Count, "minus"
End Sub
Sub minus()
if isCancelled Then Exit Sub
(...)
End Sub
Sub abort()
isCancelled = True
End Sub

Can't get Pause Button to resume time properly - Excel VBA

I am coding a Stopwatch Timer in Excel, and I am trying to add a pause button that will stop the timer, also resume when clicked the 2nd time. I have it almost working, but when it resumes it does not continue the stopwatch at the same time, but rather includes all of the elapsed time. The way I have it working is that when you click the pause button it adds the value of "Paused" to a cell and sets the application to "False". If the button is clicked and the cell already says "Paused" it will empty the cell value and call the StartTimer sub and start the timer again.
Example of problem: If I press the button at 1min and 3secs, wait 3mins and resume the timer, it starts counting again at 4min and 3secs.
Example of goal: If I press the button at 1min and 3secs, wait 3mins and resume the timer, it should start counting again at 1min and 4secs.
Here is my current code that i've been working on:
Dim NextTick As Date, t As Date
Sub StartStopWatch()
t = Time
Call StartTimer
End Sub
Sub StartTimer()
NextTick = Time + TimeValue("00:00:01")
Range("M1").Value = Format(NextTick - t - TimeValue("00:00:01"), "hh:mm:ss")
Application.OnTime NextTick, "StartTimer"
End Sub
Sub StopTimer()
On Error Resume Next
Application.OnTime EarliestTime:=NextTick, Procedure:="StartTimer", Schedule:=False
End Sub
Sub ResetTimer()
Range("M1").ClearContents
Range("N1").ClearContents
Range("L2").ClearContents
End Sub
Sub PauseButton()
If Range("L2").Value = "" Then
Range("L2").Value = "Paused"
Application.OnTime EarliestTime:=NextTick, Procedure:="StartTimer", Schedule:=False
Else
Range("L2").ClearContents
Call StartTimer
End If
End Sub
Your code does not reset the start time when paused. But resetting the start time would lose previous elapsed periods. One approach to this problem is to record the number of elapsed seconds each time the clock stops. Subsequence start/stops are added to the running elapsed counter. There is a reset method so you can begin again from zero.
Private StartTime As Date ' Stores timer start time.
Private StopTime As Date ' Stores timer end time.
Private ElapsedTime As Integer ' Store number of seconds between start and end time.
Private TimerRunning As Boolean ' True when the timer is running.
' Resets ElapsedTime and running status.
Public Sub ResetTimer()
ElapsedTime = 0
If TimerRunning Then TimerRunning = False
End Sub
' Starts the timer.
Public Sub StartTimer()
' Record start time.
StartTime = Time
TimerRunning = True
End Sub
' Stops the timer.
' Displays elapsed seconds.
Public Sub StopTimer()
' Record end time.
StopTime = Time
TimerRunning = False
' Calculate numebr of seconds clock was running for.
' Add total to any previous runs.
ElapsedTime = ElapsedTime + DateDiff("S", StartTime, StopTime)
MsgBox "Clock ran for " & ElapsedTime & " seconds", vbInformation, "Clock Stopped"
End Sub
' Pause the timer.
Public Sub PauseTimer()
' Check current status.
If TimerRunning Then
' Stop the clock, capture the end time
' but do not reset the timer.
StopTimer
Else
' Restart the timer.
StartTimer
End If
End Sub
Users only really need access to the pause and reset functions. You could make the start and stop functions internal private methods.
I've stored the elapsed time in an integer. Date and time variables should be used to record when an event occurred, rather than the duration. Why? Because a time variable supports values from 00:00:00 -> 23:59:59 (times of the day). This leaves no room to record events that lasted longer than a day. You can always format the int when displaying.
I havn't understood how exactly your code works and I tried a couple of things but its not working. Basically what I was trying to to was to take the time from the cell M1 itself the next time it starts and then continue from there itself. Look at this code, maybe it will help.
Dim NextTick As Date, t As Date, continue_time As Date
Sub StartStopWatch()
If Range("L2").Value = "" Then
t = Time
Range("L2").ClearContents
Else
t = TimeValue(Range("M1").Text) - Time
End If
't = Time
Call StartTimer
End Sub
Sub StartTimer()
'If Range("L2").Value = "" Then
' continue_time = Time
'Else
' continue_time = TimeValue(Range("M1").Text)
'End If
NextTick = Time + TimeValue("00:00:01")
Range("M1").Value = Format(NextTick - t - TimeValue("00:00:01"), "hh:mm:ss")
Application.OnTime NextTick, "StartTimer"
End Sub
Sub StopTimer()
On Error Resume Next
Application.OnTime EarliestTime:=NextTick, Procedure:="StartTimer", Schedule:=False
End Sub
Sub ResetTimer()
Range("M1").ClearContents
Range("N1").ClearContents
Range("L2").ClearContents
End Sub
Sub PauseButton()
If Range("L2").Value = "" Then
Range("L2").Value = "Paused"
Application.OnTime EarliestTime:=NextTick, Procedure:="StartTimer", Schedule:=False
Else
Call StartStopWatch
End If
End Sub
I actually just figured it out using a code that counts integers and then changed the formatting to mm:ss.
Solution: http://www.ozgrid.com/forum/showthread.php?t=153966

Resources