How to enter seconds in a Userform Timer? - excel

I have a program that uses a VBA countdown timer.
I can only enter minutes. How can I enter seconds?
In Module1 I enter the time
Public Const AllowedTime As Double = 1
and the code looks like this
Private Sub CommandButton1_Click()
Dim T, E, M As Double, S As Double
T = Timer
Do
E = CDbl(Time) * 24 * 60 * 60 - T
M = AllowedTime - 1 - Int(E / 60)
S = 59 - Round((E / 60 - Int(E / 60)) * 60, 0)
With tBx1
.Value = Format(CStr(M), "00") & ":" & Format(CStr(S), "00")
End With
DoEvents
Loop Until (Timer - T) / 60 >= AllowedTime
End Sub
Private Sub poker_Initialize()
Dim M As Double, S As Double
M = Int(AllowedTime)
S = (AllowedTime - Int(AllowedTime)) * 60
With tBx1
.Value = Format(CStr(M), "00") & ":" & Format(CStr(S), "00")
End With
End Sub
Here is a simple working example
https://app.box.com/s/211uo88dk02x6il8hqj19wyiv1rli2sj

I suggest that you use Date variables for your time calculations because it is much easier.
You will also need a module-level variable if you want the Pause button to work. This code lets you show minutes & seconds. Your AllowedTime variable should be minutes with a decimal part for seconds, or change it to the number of seconds and change the lines that are commented out.
Dim userClickedPause As Boolean ' Gets set to True by the Pause button
Private Sub CommandButton1_Click()
Dim stopTime As Date
userClickedPause = False
' If AllowedTime is the number of minutes with a decimal part:
stopTime = DateAdd("s", Int(AllowedTime * 60), 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 tBx1
.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

Related

Optimize code for multiple timers on 1 sheet

This is what my sheet looks like:
(I got the code from online somewhere & just been adjust what I know)
I Currently have 10 rows with working buttons, but it's already at 500+ lines of code and I still need 60more. I'm worried the file will become too large and start crashing.
Should I just keep changing the "Range(F#)" every time I make a new button/row?
Also, is it possible to keep more than 1 timer going at a time? Currently when I click stop on any of the rows it will stop whatever timer is active.
Public StopIt As Boolean
Public ResetIt As Boolean
Public LastTime
Private Sub cust10reset_Click()
Range("F10").Value = Format(0, "00") & ":" & Format(0, "00") & ":" & Format(0, "00") & "." & Format(0, "00")
LastTime = 0
ResetIt = True
End Sub
Private Sub cust10start_Click()
Dim StartTime, FinishTime, TotalTime, PauseTime
StopIt = False
ResetIt = False
If Range("F10") = 0 Then
StartTime = Timer
PauseTime = 0
LastTime = 0
Else
StartTime = 0
PauseTime = Timer
End If
StartIt:
DoEvents
If StopIt = True Then
LastTime = TotalTime
Exit Sub
Else
FinishTime = Timer
TotalTime = FinishTime - StartTime + LastTime - PauseTime
TTime = TotalTime * 100
HM = TTime Mod 100
TTime = TTime \ 100
hh = TTime \ 3600
TTime = TTime Mod 3600
MM = TTime \ 60
SS = TTime Mod 60
Range("F10").Value = Format(hh, "00") & ":" & Format(MM, "00") & ":" & Format(SS, "00") & "." & Format(HM, "00")
If ResetIt = True Then
Range("F10") = Format(0, "00") & ":" & Format(0, "00") & ":" & Format(0, "00") & "." & Format(0, "00")
LastTime = 0
PauseTime = 0
End
End If
GoTo StartIt
End If
End Sub
Private Sub cust10stop_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
StopIt = True
End Sub
I tried making a dedicated formula tab and just make macros going my timer buttons but I couldn't get that to work.
I tried making a togglebutton and linking it to the cell then just make a code that references the linkedcell to know where to put the timer, but that wasn't working. It just kept coming back true/false.
I guess I just want to know if it's ok to have 4000+ lines on 1 sheet with 210 buttons lol.
Or just an easier way.
Here's one approach using hyperlinks in place of buttons:
The hyperlinks you create need to have a destination, but in this case we want "do nothing" links - their only purpose is to trigger the sheet's FollowHyperlink event
This post
excel hyperlink to nothing
has suggestion for approaches to a "do nothing" hyperlink. Entering #rc for the address seems to work well - as explained by lori_m in their comment -
The # signifies a reference within a document and any formula that
returns a reference can follow in either A1 or r1c1 notation. Here rc
means this cell in r1c1notation.
Set up some links using "Insert >> Hyperlink", using "#rc" as the link target (entered next to "Address").
Don't use the HYPERLINK() formula, because those types of links don't trigger the FollowHyperlink event.
For example (3 timers running):
Finally this code goes in the worksheet code module:
Option Explicit
Dim timers As Object 'cell addresses as keys and start times as values
Dim nextTime 'next run time
'This is called when youclickon a hyperlink
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
Dim txt As String, cLnk As Range, cTimer As Range, addr As String
Dim currVal
If timers Is Nothing Then Set timers = CreateObject("scripting.dictionary")
Set cLnk = Target.Range 'cell with clicked link
Set cTimer = cLnk.EntireRow.Columns("B") 'cell with elapsed time
addr = cTimer.Address(False, False) 'address of cell with elapsed time
txt = Target.TextToDisplay 'Start/Stop/Reset
Select Case txt 'what action to take depends on the link's text
Case "Stop"
If timers.Exists(addr) Then timers.Remove addr
Target.TextToDisplay = "Start" 'toggle link text
cLnk.Interior.Color = vbGreen 'toggle cell color
Case "Start"
currVal = cTimer.Value 'already some elapsed value?
timers(addr) = IIf(Len(currVal) > 0, Now - currVal, Now)
Target.TextToDisplay = "Stop"
cLnk.Interior.Color = vbRed
Case "Reset"
If timers.Exists(addr) Then 'timer is running?
timers(addr) = Now 'just reset the start time
Else
cTimer.Value = 0 'clear the elapsed time
End If
End Select
UpdateTimers
End Sub
'called using OnTime, or from the event handler
Sub UpdateTimers()
Dim addr As String, k, macro
macro = Me.CodeName & ".UpdateTimers"
On Error Resume Next 'cancel any running timer
Application.OnTime EarliestTime:=nextTime, Procedure:=macro, Schedule:=False
On Error GoTo 0
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
nextTime = Now + TimeSerial(0, 0, 1) 'schedule next run
Application.OnTime nextTime, macro
End Sub
What you could consider is to work with a Class module and a dictionary.
The Timer() command in XL merely generates a TimeStamp value that you can store for later use. You could do that in a dictionary with a particular class.
Create a Class module and name it cTimer add below code
Option Explicit
Private pTimer As Single
Public Sub StartTimer()
pTimer = Timer()
End Sub
Property Get Elapsed() As Single
Elapsed = Timer() - pTimer
End Property
Now, mind you, the portion of using the class may not strictly be required as you could simply add a dictionary entry for the address and Timer() value.
like so:
dict.Add Key, Timer()
But working with a class object allows you to create more functionality for each of the cTimer objects.
Now, to keep track of all the timers you can set add a new cTimer object to the dictionary based on the cell address of the button (this may need some fine tuning to ensure all your buttons eventually generate the same reference key)
But that is the most important portion of it, the reference key.
In a code module, add the below, this will look for an existing entry in the dictionary and if it exists display the elapsed time otherwise a new cTimer object will be added to the dictionary with the address as the reference key.
Create a Module and add the following:
Global dict As Object 'this line should be all the way at the top of the module code!
Sub TestTimer()
Dim rngButton As Range
Dim mm As cTimer
If dict Is Nothing Then
Set dict = CreateObject("Scripting.Dictionary")
End If
Caller = Application.Caller
Set rngButton = ActiveSheet.Buttons(Caller).TopLeftCell
Key = rngButton.Address
Set tmr = New cTimer
tmr.StartTimer
If Not dict.Exists(Key) Then
dict.Add Key, tmr
Else
Set tmr = dict(Key)
Debug.Print tmr.Elapsed
End If
End Sub
This may obviously need some tweaking to suit your particular need, but this could well be the solution you aim for. As you can simply have all the buttons refer to the same Method (or Macro)
You should add some logic for removing times and for resetting them etc. but the concept works.
see also: https://learn.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/dictionary-object

Excel VBA, Time values via time picker

I have created a userform where the user has to put in some time values, these time values are for example how long a product has to be processed for. I have a code that is almost what I would like it to do, only when it reaches 24 hours, it resets to 0. but the input requires to be for 24+ hours (for example: 36:59:59 (where the max value of mm & ss = 59, the hh max value should be 99).
could anyone assist me on how I can change this?
Private mtmPosition1 As tmPosition1
Private Const msFMTTIME1 As String = "[$-409]hh:mm:ss"
Private Const miRIGHTARROW1 As Integer = 39
Private Const miLEFTARROW1 As Integer = 37
Private Const mdHOUR1 As Double = 1 / 24
Private Const mdMINUTE1 As Double = 1 / 24 / 60
Private Const mdSECOND1 As Double = 1 / 24 / 60 / 60
Private Enum tmPosition1
tmPositionHour1
tmPositionMinute1
tmPositionSecond1
End Enum
Private Sub sbTime1_SpinDown()
If Me.IsHour1 Then
Me.tbxTimePicker1.Text = Format(1 + TimeValue(Me.tbxTimePicker1.Text) - mdHOUR1, msFMTTIME1)
SelectHour1
ElseIf Me.IsMinute1 Then
Me.tbxTimePicker1.Text = Format(1 + TimeValue(Me.tbxTimePicker1.Text) - mdMINUTE1, msFMTTIME1)
SelectMinute1
ElseIf Me.IsSecond1 Then
Me.tbxTimePicker1.Text = Format(1 + TimeValue(Me.tbxTimePicker1.Text) - mdSECOND1, msFMTTIME1)
SelectSecond1
End If
End Sub
Private Sub sbTime1_SpinUp()
If Me.IsHour1 Then
Me.tbxTimePicker1.Text = Format(1 + TimeValue(Me.tbxTimePicker1.Text) + mdHOUR1, msFMTTIME1)
SelectHour1
ElseIf Me.IsMinute1 Then
Me.tbxTimePicker1.Text = Format(1 + TimeValue(Me.tbxTimePicker1.Text) + mdMINUTE1, msFMTTIME1)
SelectMinute1
ElseIf Me.IsSecond1 Then
Me.tbxTimePicker1.Text = Format(1 + TimeValue(Me.tbxTimePicker1.Text) + mdSECOND1, msFMTTIME1)
SelectSecond1
End If
End Sub
Private Sub tbxTimePicker1_Enter()
With Me.tbxTimePicker1
.SelStart = 0
.SelLength = 2
End With
mtmPosition1 = tmPositionHour1
End Sub
Private Sub tbxTimePicker1_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = miRIGHTARROW1 Then
If Me.IsHour1 Then
SelectMinute1
ElseIf Me.IsMinute1 Then
SelectSecond1
End If
ElseIf KeyCode = miLEFTARROW1 Then
If Me.IsSecond1 Then
SelectMinute1
Else
SelectHour1
End If
Else
If Me.IsHour1 Then
SelectHour1
ElseIf Me.IsMinute1 Then
SelectMinute1
ElseIf Me.IsSecond1 Then
SelectSecond1
End If
End If
End Sub
Private Sub tbxTimePicker1_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If Me.tbxTimePicker1.SelStart < 3 Then
SelectHour1
ElseIf Me.tbxTimePicker1.SelStart < 6 Then
SelectMinute1
ElseIf Me.tbxTimePicker1.SelStart < 9 Then
SelectSecond1
End If
End Sub
Public Property Get IsHour1() As Boolean
IsHour1 = mtmPosition1 = tmPositionHour1
End Property
Public Property Get IsMinute1() As Boolean
IsMinute1 = mtmPosition1 = tmPositionMinute1
End Property
Public Property Get IsSecond1() As Boolean
IsSecond1 = mtmPosition1 = tmPositionSecond1
End Property
Private Sub SelectMinute1()
With Me.tbxTimePicker1
.SetFocus
.SelStart = 3
.SelLength = 2
End With
mtmPosition1 = tmPositionMinute1
End Sub
Private Sub SelectHour1()
With Me.tbxTimePicker1
.SetFocus
.SelStart = 0
.SelLength = 2
End With
mtmPosition1 = tmPositionHour1
End Sub
Private Sub SelectSecond1()
With Me.tbxTimePicker1
.SetFocus
.SelStart = 6
.SelLength = 2
End With
mtmPosition1 = tmPositionSecond1
End Sub
Without trying to modify your code, I suggest to do as follows:
Let the user enter, with SpinDown and SpinUp controls the "process duration" as three numbers (integers), 0-99 (hours), 0-59 (minutes) and 0-59 (seconds). Do not treat these as time values, just simple numbers.
If applicable, let the user enter "process start time", use separate SpinDown/SpinUp controls as true time values.
If applicable, to show "process end time" use simple text boxes to show start time + duration as calculated end time (day, hour, minute, second)

Run VBA macro to start at every 45th second

I want to start the vba macro to start to run every 45th second, If it got delayed then it has to start again at either at that minute 45th second or next minute 45th second and so on.. I wrote following 2 codes, but its not working. please guide
If Format(Now(), "ss") > 45 And Format(Now(), "ss") < 60 Then
runtime = Now()
Else
x = Format(Now(), "ss")
y = 45 - x
runtime = Now() + TimeValue("00:00:" & y)
End If
Application.OnTime runtime, "testtimer"
or (in the below code getting type mismatch here)
runtime = Now() + TimeValue("00:00:45")
nextime = runtime + TimeValue("00:00:45")
Application.OnTime runtime, "testtimer",nextime
Working with the application.onTime event is always a bit tricky. The following code should do what you are after.
Sub macro_to_run()
Debug.Print "at macro_to_run", Now
End Sub
Sub timed_procedure()
' read the position of the second hand from the syetem clock
Dim secondHand As Byte
secondHand = Second(Now)
' schedule this proc to run again either on the 45th second of this minute (if that is in the future)
' or the 45th second of the next minute
If secondHand < 45 Then
Application.OnTime DateAdd("s", 45 + Minute(Now) * 60 + Hour(Now) * 3600, Date), "timed_procedure"
Else
Application.OnTime DateAdd("s", 60 + 45 + Minute(Now) * 60 + Hour(Now) * 3600, Date), "timed_procedure"
End If
'execute the macro only if the current second is in the allowed window
If secondHand >= 45 And secondHand <= 50 Then
macro_to_run
End If
End Sub

Is this unstable VBA Timer behavior real or am I doing something wrong

I have seen some strange behavior for VBA Timer function which suggests it can have sudden changes in output of up to 250 milliseconds, and I would like to understand this since I use it often in my VBA code. Is this even possible? Pls note that this is my first post to StackOverflow and I am not a professional programmer.
My research of timers says TIMER returns the number of fractional seconds since midnight, updated every few milliseconds depending on your PC environment. So for a period of a few milliseconds it returns the same value, and then jumps to the next value when it is reset to current seconds past midnight. I created a test code using QueryPerformanceFrequency and QueryPerformanceCounter (QPFC) to compare time intervals measured by TIMER and QPFC to better understand this behavious.
My code sets a start time using TIMER and QPFC and runs a loop to look at the difference in elapsed milliseconds from the start time. Much to my surprise I see the occasional huge jumps in TIMER time. The first image shows multiple jumps, max about 300mSec, when my Gigabyte Aero is running on battery but I see similar behavior when plugged in, and on my Surface Pro 4.
Image of output from Code:
[1]: https://i.stack.imgur.com/kDDuQ.png
The output shows that for my machine often there is 7.8 mSec update period for Timer as you can see in column A. I have seen many different multiples of 7.8 (mostly 7.8/N values, mostly I see 3.9mSec). But suddenly there can be a big jump in difference in elapsed time between the two timers that persists until the next jump in timer differences. Sometimes it takes several hours of looping to see the jump, but often I see it in less than a minute. I have run the same code on another multicore PC and see the same feature. Spikes in the difference in timers I can understand (PC gets busy elsewhere between timer calls) but not these steps. It runs until there is a preselected delta between timers, or end of the allow run time if there are no large steps.
The test code is at the end of this question, it needs a workbook open with a worksheet called “Timer”, and will populate the sheet with the headings, data, analysis, graph shown above to facilitate review. I “read” the TIMER before and after the QPFC timer to help diagnose the issue. Obviously if the PC decides that other activity is higher priority than this test code between the two TIMER queries, the difference between the two can take a big jump for one loop, but then should go back to the usual range of deltas between the two measured elapsed times. I.e. a spike in timer difference, not a permanent timer difference. The timing information suggests it is TIMER that is jumping not QPFC.
I added a check on how long the two timer calls take, TIMER is about 60 nSec, QPFC is about 900 nSec on my machine. I turned off time resets in Date and Time settings (not in VBA but in Windows). I see the jumps even if I use Task Manager to set the Excel affinity to one CPU.
Is my test code OK? Am I interpreting the data correctly? How can “time since midnight” change like this?
Option Explicit
Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LongLong) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LongLong) As Long
Private ll_Counter As LongLong
Private dFrequency As Double
'just put all variables where they can be used throughout module, not good practice but makes this test code easy to write
Dim dTimer1 As Double, dTimer2 As Double, dMaxDelta As Double, dLL_TimerDelta As Double, dTotalTime As Double
Dim dTimerStart As Double, dLL_TimerStart As Double, dTimer1Delta As Double, dTimer2Delta As Double, dMaxMinutes As Double
Dim iRow As Long, idTimerNothing As Long, iNothing As Long
Dim iLoops As Long, iReUse As Long
Sub Test_LongLongTimer() 'needs an open workbook with sheet names "Timer"
Application.ScreenUpdating = False
Application.Calculation = xlManual
Worksheets("Timer").Select
SetUpWorksheet
Call SetFrequency 'set the frequency for QPFC timer
dTotalTime = 0#
dMaxDelta = 0#
dTimerStart = Timer
dLL_TimerStart = LL_Timer
dMaxMinutes = 5#
Do While dMaxDelta < 10 And dTotalTime < (dMaxMinutes * 60# * 1000#) 'stops the loop when the TIMER to QPFC timer difference exceeds dMaxDelta or time is longer than dMaxMinutes
iRow = 2
iLoops = 10000
For iReUse = 2 To iLoops + 1 'allow for header row in top row when saving data, saves every 100th calc
'read timers
dTimer1Delta = (Timer - dTimerStart) * 1000# 'convert to milliseconds
dLL_TimerDelta = (LL_Timer - dLL_TimerStart) * 1000# 'convert to milliseconds
dTimer2Delta = (Timer - dTimerStart) * 1000# 'convert to milliseconds
SaveTimers 'save timers and analysis functions
For iNothing = 1 To 1 'add some extra delay before next comparisons to vary total time, change value as desired
DoEvents
Next iNothing
Worksheets("Timer").Range("A" & iRow & ":K" & iRow).Calculate
If Abs(Worksheets("Timer").Range("I" & iRow).Value) > dMaxDelta Then dMaxDelta = Abs(Worksheets("Timer").Range("I" & iRow).Value)
dTotalTime = dTimer2Delta
iRow = iRow + 1
Next iReUse
Application.Calculate
iRow = 2
Loop
'check time for LL_Timer function call, my system gives about 900 nanoseconds
dTimerStart = Timer
iLoops = 1000000
For iReUse = 1 To iLoops
dLL_TimerStart = LL_Timer
Next iReUse
dTimerStart = ((Timer - dTimerStart) / iLoops) * 1000000000# 'convert to NanoSeconds
Worksheets("Timer").Range("P1").Value2 = "MY TIMER Avrg NanoSeconds per LL_Timer call"
Worksheets("Timer").Range("P2").Value2 = dTimerStart
'check time for VBA timer function call, my system gives about 65 nanoseconds
dTimerStart = Timer
iLoops = 1000000
For iReUse = 1 To iLoops
dLL_TimerStart = Timer
Next iReUse
dTimerStart = ((Timer - dTimerStart) / iLoops) * 1000000000# 'convert to NanoSeconds
Worksheets("Timer").Range("P3").Value2 = "VBA TIMER Avrg NanoSeconds per Timer call"
Worksheets("Timer").Range("P4").Value2 = dTimerStart
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
Worksheets("Timer").Range("p:p").ColumnWidth = 50
With Worksheets("Timer").Range("A:P")
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = True
.EntireColumn.AutoFit
.NumberFormat = "#,##0.0_);[Red](#,##0.0)"
End With
Call AddChart
End Sub
Function LL_Timer() 'Seconds
QueryPerformanceCounter ll_Counter
LL_Timer = CDbl(ll_Counter) / dFrequency
End Function
Private Sub SetFrequency()
Dim PerfFrequency As LongLong
QueryPerformanceFrequency PerfFrequency
dFrequency = CDbl(PerfFrequency)
End Sub
Sub AddChart()
Dim ChtObj As ChartObject
For Each ChtObj In Worksheets("Timer").ChartObjects
ChtObj.Delete
Next ChtObj
Range("H3").Select
Range(Selection, Selection.End(xlDown)).Select
ActiveSheet.Shapes.AddChart2(332, xlLineMarkers).Select
'ActiveChart.SetSourceData Source:=Range("Timer!$C$2:$C$1001")
ActiveChart.Parent.Name = "TimerDelta"
With ActiveChart.Parent
.Width = 15 * 72 '500 ' resize 72 pnts per inch
.Height = 3 * 72 '500 ' resize 72 pnts per inch
.Top = 105 ' reposition
.Left = 125 'reposition
End With
Dim ax As Axis
With ActiveChart.Axes(xlValue)
.TickLabels.Font.Size = 16
.TickLabels.Font.Bold = True
.Crosses = xlMaximum
End With
With ActiveChart.Axes(xlCategory)
.TickLabels.Font.Size = 16
.TickLabels.Font.Bold = True
End With
Range("C2").Select
Range(Selection, Selection.End(xlDown)).Select
Range(Selection, Selection.End(xlUp)).Select 'funny stuff re bad display, fix by moving around
Range("C25").Select
End Sub
Sub SetUpWorksheet()
Worksheets("Timer").Range("A2:K1000000").ClearContents
Worksheets("Timer").Range("A1").Value2 = "dTimer1 (mSec)"
Worksheets("Timer").Range("B1").Value2 = "dMyTimer (mSEC)"
Worksheets("Timer").Range("C1").Value2 = "dTimer2 (mSEC)"
Worksheets("Timer").Range("D1").Value2 = "Delta, Timer1 to previous"
Worksheets("Timer").Range("E1").Value2 = "Delta, dMyTimer to previous, mSec"
Worksheets("Timer").Range("F1").Value2 = "Delta, Timer2 to previous"
Worksheets("Timer").Range("G1").Value2 = "Delta, Timer2 to dMyTimer"
Worksheets("Timer").Range("H1").Value2 = "Delta, dMyTimer to Timer1"
Worksheets("Timer").Range("I1").Value2 = "Delta of LL-T1 delta to previous delta" 'delta of column H
Worksheets("Timer").Range("J1").Value2 = "QueryPerformanceCounter"
Worksheets("Timer").Range("K1").Value2 = "QueryPerformanceCounter Delta To Previous"
End Sub
Sub SaveTimers()
Worksheets("Timer").Range("A" & iRow).Value = dTimer1Delta
Worksheets("Timer").Range("B" & iRow).Value = dLL_TimerDelta
Worksheets("Timer").Range("C" & iRow).Value2 = dTimer2Delta
Worksheets("Timer").Range("J" & iRow).Formula = ll_Counter
If iRow > 2 Then
Worksheets("Timer").Range("D" & iRow).Value = "=(A" & iRow & "-A" & (iRow - 1) & ")"
Worksheets("Timer").Range("E" & iRow).Value = "=(B" & iRow & "-B" & (iRow - 1) & ")"
Worksheets("Timer").Range("F" & iRow).Value = "=(C" & iRow & "-C" & (iRow - 1) & ")"
Worksheets("Timer").Range("G" & iRow).Value = "=(C" & iRow & "-B" & iRow & ")"
Worksheets("Timer").Range("H" & iRow).Value = "=(B" & iRow & "-A" & iRow & ")"
Worksheets("Timer").Range("I" & iRow).Value = "=(H" & iRow & "-H" & (iRow - 1) & ")"
Worksheets("Timer").Range("K" & iRow).Value = "=(J" & iRow & "-J" & (iRow - 1) & ")"
End If
End Sub

Application.Wait Breaks my Code

Original
I have the following code that is coloring a cell to demonstrate the use of milisecond wait time. However, when i = 500 the code breaks. The error I get is Code Execution has been Interrupted and from 500 to 1000 I have to keep clicking continue. I've tried to wrap my code in an Application.DisplayAlerts = False and True but it still gets interrupted and won't finish. I estimate this code will take approximately 6 minutes more or less as i approaches 1000. I'm at a loss as to what could cause this. I've gone through every setting I can think of and it won't continue past 500 without breaking. ms was calculated from 1/(1000*24*60*60).
Excel 2007
Sub Kaleidoscope()
Dim r, g, b, i As Integer, ms As Double
ms = 0.0000000115741
For i = 1 To 1000
r = WorksheetFunction.RandBetween(1, 255)
g = WorksheetFunction.RandBetween(1, 255)
b = WorksheetFunction.RandBetween(1, 255)
Range("A1").Interior.Color = RGB(r, g, b)
Application.Wait (Now + (ms * i))
Next i
End Sub
Thank you in advance!
Update
The link provided by #MarcoMarc (stackoverflow.com/a/5823507/5175942) solved the initial breaking problem of my question. However, it still does not appear to be incrementing correctly. It goes as if it isn't waiting until i = 500 then appears to be stalling 1 second every time. Is this the limit you were speaking of and ultimately it is not possible to wait for 1 ms? No change in the original code was needed to prevent the breaking.
Final Thoughts
#JohnMuggins gives a great tweak to my original code and provides additional tools to see the calculations behinds the scenes. Ultimatley though, he also had to call winAPI like #MacroMarc in order to pause the code for less than 1 second. Through research on additional websites and through Stack Overflow, it appears not possible to pause the program for less than 1 second using VBA alone. It either runs at normal speed or when it gets to 500 ms it rounds up to 1 second and delays the code for 1 second instead of 500 ms. My final code for demonstration is below with #JohnMuggins tweaks.
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub Kaleidascope()
Dim StartTime As Double
Dim EndTime As Double
Dim ms As Double
Dim i, r, g, b As Integer
Dim count As Long
StartTime = Timer
For i = 1 To 500
ms = i
r = WorksheetFunction.RandBetween(1, 255)
g = WorksheetFunction.RandBetween(1, 255)
b = WorksheetFunction.RandBetween(1, 255)
Range("A1").Interior.Color = RGB(r, g, b)
Sleep ms
Range("B1").Value = "Time: " & Format(Timer - StartTime, "####.###")
Range("C1").Value = "ms = " & Format(ms, "####.####")
Range("D1").Value = i & " of 500"
Next i
EndTime = Timer - StartTime
Debug.Print Format(EndTime, "####.##")
End Sub
You could use the Sleep function from winAPI.
At the top of the module:
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Then in your code:
Sleep i ' where i is now in milliseconds
Note that Sleep delays all VBA code.
0m3r was correct. The problem is with the application.wait function. Try the following do events routine instead.
Sub Kaleidoscope()
Dim r, g, b, i As Integer, ms As Double
ms = 0.0000000115741
For i = 1 To 1000
r = WorksheetFunction.RandBetween(1, 255)
g = WorksheetFunction.RandBetween(1, 255)
b = WorksheetFunction.RandBetween(1, 255)
Range("A1").Interior.Color = RGB(r, g, b)
Range("A1").Value = i
For j = Now To (Now + (ms * i))
DoEvents
Next j
Next i
End Sub
This is the only way I could reach your objective of run-time of 2 minutes and 9 seconds.
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
' run time on my computer 2:09:07
' runs from 6 ms to 751.5 ms
Sub ewhgfsd()
Dim StartTime As Double
Dim EndTime As Double
StartTime = Timer
For i = 1 To 500
ms = ms + (i * 0.006)
r = WorksheetFunction.RandBetween(1, 255)
g = WorksheetFunction.RandBetween(1, 255)
b = WorksheetFunction.RandBetween(1, 255)
Range("A1").Interior.Color = RGB(r, g, b)
Sleep ms
Range("B1").Value = "Time: " & Format(Timer - StartTime, "####.##")
Range("C1").Value = "ms = " & Format(ms, "####.#####")
Range("D1").Value = i & " of 500"
Next i
EndTime = Timer - StartTime
Debug.Print Format(EndTime, "####.##")
End Sub

Resources