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
Related
I have a worksheet which I use to track progress recording audiobooks (I'm a narrator). On each sheet, I have a timer that I use to track how many hours I record each day/session.
If the timer is running at midnight, it sets the timer to 20:00:00 and keeps going. I often am recording at midnight, so this is a major issue if I don't remember to stop the timer before midnight and restart it after.
Here is a link to the whole Excel file. You can find the timer in the TEMPLATE worksheet. The Macros for the timer are within Module1.
http://recording-tracker.narratedby.me
Here is all the code:
Option Explicit
Dim NextTick As Date, t As Date, PreviousTimerValue As Date, strSheetName As String, lRowTime As Long, lRowDate As Date, CurrentTimerValue As Date, StartTimerValue As Date
Sub StartTime()
strSheetName = ActiveSheet.Name
StartTimerValue = PreviousTimerValue
CurrentTimerValue = PreviousTimerValue
t = Time
Call ExcelstrSheetName
End Sub
Private Sub ExcelstrSheetName()
strSheetName = ActiveSheet.Name
ThisWorkbook.Worksheets(strSheetName).Range("Q1").Value = Format(Time - t + PreviousTimerValue, "hh:mm:ss")
NextTick = Now + TimeValue("00:00:01")
Application.OnTime NextTick, "ExcelstrSheetName"
End Sub
Sub StopClock()
strSheetName = ActiveSheet.Name
On Error Resume Next
Application.OnTime earliesttime:=NextTick, procedure:="ExcelstrSheetName", schedule:=False
CurrentTimerValue = ThisWorkbook.Worksheets(strSheetName).Range("Q1").Value
End Sub
Sub Reset()
strSheetName = ActiveSheet.Name
CurrentTimerValue = ThisWorkbook.Worksheets(strSheetName).Range("Q1").Value
On Error Resume Next
Application.OnTime earliesttime:=NextTick, procedure:="ExcelstrSheetName", schedule:=False
ThisWorkbook.Worksheets(strSheetName).Range("Q1").Value = 0
End Sub
The cell Q1 is what holds the timer value at any given time.
I have removed other Subs and portions of these Subs which are irrelevant to my question. This is the basic stopwatch function.
Any help is much appreciated!
I am developing a VBA code that will run on a range of cells in a worksheet. Every time a cell within that rnage is selected, I want the code to execute. I am doing this using:
Sub Worksheet_SelectionChange(ByVal Target As Range)
However, the code is quite slow and there are times I would like to navigate within that range using the arrow keys. To do so, I thought to use the Timer function that will only run the code if I am on a cell for more than 0.5 seconds.
The code I have tried is as follows:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'set timer
Dim PauseTime, Start, Finish, TotalTime
PauseTime = 0.6 ' Set duration.
Start = Timer ' Set start time.
Do While Timer < Start + PauseTime
DoEvents ' Yield to other processes.
Loop
Finish = Timer ' Set end time.
TotalTime = Finish - Start ' Calculate total time.
If Abs(TotalTime) < 0.1 Then GoTo ws_exit
'continuation of code......
ws_exit:
Application.EnableEvents = True
End Sub
The Main issue I am having with the code is that it is recursive and returns to the first timer that was set on the first cell I pressed. I wanted to know if there is a way to delay the SelectionChange code from running until the user is set on a cell for X amount of time.
Thanks,
Yon
A class module solves this. Add a class module, call it cTimer. Paste this code:
Option Explicit
Public Target As Range
Private StartTime As Double
Public WaitTime As Double
Public Sub HandlePrevious()
If Timer - StartTime > WaitTime Then
'Handle things now using Target
Debug.Print Target.Address, "Taking action"
Else
Debug.Print Target.Address, "Moving on"
End If
End Sub
Private Sub Class_Initialize()
StartTime = Timer
End Sub
Use this in your worksheet module:
Option Explicit
Dim mcTimer As cTimer
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not mcTimer Is Nothing Then
mcTimer.HandlePrevious
End If
Set mcTimer = Nothing
Set mcTimer = New cTimer
mcTimer.WaitTime = 0.5
Set mcTimer.Target = Target
End Sub
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
I want to show a message 20 seconds after opening the Excel workbook. Code is:
//ThisWorkbook
Private Sub Workbook_Open()
SetTimer
End Sub
//Module1
Public Sub SetTimer()
Application.OnTime Now + TimeValue("00:00:20"), "ShowMsg"
End Sub
Public Sub ShowMsg()
MsgBox ("my message")
End Sub
As you see, code is very simple and it works when user don't update sheet or when they leave updated/focused cell. However, if cursor remains at cell the message will never be shown. It seams that control doesn't return to VBA code while a cell has focus or is updating. Any idea for this issue? Thanks
Here's a workaround:
Sub main()
Dim start As Single
start = Timer
Do
DoEvents
Loop Until Timer > (start + 20) '20 seconds
MsgBox "hello"
End Sub
Edit. Code for further question:
In a module called Module1, enter the following code:
Public start As Single
Sub main2()
start = Timer
Do
DoEvents
Loop Until Timer > (start + 20) '20 seconds
MsgBox "hello"
End Sub
In your ThisWorkbook object (double click on ThisWorkbook from the list of objects in the Project Explorer) enter the following code:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Module1.start = Module1.start + 5
End Sub
Every time any cell in any worksheet in the workbook is changed, another five seconds is added to the timer.
I'm using the Application.Ontime event to pull a time field from a cell, and schedule a subroutine to run at that time. My Application.Ontime event runs on the Workbook_BeforeSave event. As such, if a user (changes the desired time + saves the workbook) multiple times, multiple Application.Ontime events are created. Theoretically I could keep track of each event with a unique time variable.. but is there a way to check/parse/cancel pending events?
Private Sub Workbook_BeforeSave
SendTime = Sheets("Email").Range("B9")
Application.OnTime SendTime, "SendEmail"
End Sub
Private Sub Workbook_BeforeClose
Application.OnTime SendTime, "SendEmail", , False
End Sub
So if I:
change B9 to 12:01, Save the workbook
change B9 to 12:03, Save the workbook
change B9 to 12:05, Save the workbook
change B9 to 12:07, Save the workbook
etc
I end up with multiple events firing. I only want ONE event to fire (the most recently scheduled one)
How can I cancel ALL pending events (or enumerate them at least) on the Workbook_BeforeClose event?
I don't think you can iterate through all pending events or cancel them all in one shabang. I'd suggest setting a module level or global boolean indicating whether or not to fire your event. So you'd end up with something like this:
Dim m_AllowSendMailEvent As Boolean
Sub SendMail()
If Not m_AllowSendMailEvent Then Exit Sub
'fire event here
End Sub
Edit:
Add this to the TOP of the sheet module which contains the range which contains the date/time value you're after:
' Most recently scheduled OnTime event. (Module level variable.)
Dim PendingEventDate As Date
' Indicates whether an event has been set. (Module level variable.)
Dim EventSet As Boolean
Private Sub Worksheet_Change(ByVal Target As Range)
Dim SendTimeRange As Range
' Change to your range.
Set SendTimeRange = Me.Range("B9")
' If the range that was changed is the same as that which holds
' your date/time field, schedule an OnTime event.
If Target = SendTimeRange Then
' If an event has previously been set AND that time has not yet been
' reached, cancel it. (OnTime will fail if the EarliestTime parameter has
' already elapsed.)
If EventSet And Now > PendingEventDate Then
' Cancel the event.
Application.OnTime PendingEventDate, "SendEmail", , False
End If
' Store the new scheduled OnTime event.
PendingEventDate = SendTimeRange.Value
' Set the new event.
Application.OnTime PendingEventDate, "SendEmail"
' Indicate that an event has been set.
EventSet = True
End If
End Sub
And this to a standard module:
Sub SendEmail()
'add your proc here
End Sub
Each time you call Application.Ontime save the time the event is set to run (you could save it on a sheet or in a module scoped dynamic array)
Each time your event fires, remove the corresponding saved time
To cancel all pending event iterate through the remaining saved times calling Application.Ontime with schedule = false
I think I may have a solution that works, based on some of the advice already given.
In short, we create a global array and each time the user hits save the SendTime is written to the array. This serves to keep track of all our scheduled times.
When the workbook is closed, we loop through the array and delete all scheduled times.
I tested this and it seemed to work on Excel 2003. Let me know how you get on.
Dim scheduleArray() As String //Set as global array to hold times
Private Sub Workbook_BeforeSave
SendTime = Sheets("Email").Range("B9")
AddToScheduleArray SendTime
Application.OnTime SendTime, "SendEmail"
End Sub
Private Sub Workbook_BeforeClose
On Error Resume Next
Dim iArr As Integer, startTime As String
For iArr = 0 To UBound(scheduleArray) - 1 //Loop through array and delete any existing scheduled actions
startTime = scheduleArray(iArr)
Application.OnTime TimeValue(startTime), "SendEmail", , False
Next iArr
End Sub
Sub AddToScheduleArray(startTime As String)
Dim arrLength As Integer
If Len(Join(scheduleArray)) < 1 Then
arrLength = 0
Else
arrLength = UBound(scheduleArray)
End If
ReDim Preserve scheduleArray(arrLength + 1) //Resize array
scheduleArray(arrLength) = startTime //Add start time
End Sub
or you can just create some cell (like abacus), for example:
if I use application.ontime:
if range("V1") < 1 then
Application.OnTime dTime, "MyMacro"
range("V1")=range("V1") + 1
end if
if I want to stop counting...
Application.OnTime dTime, "MyMacro", , False
range("V1")=range("V1") - 1