I want to add wait time of 1 second in my Application.
What I found online?
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 1
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
Here is my code
Sub workwithdelay()
Dim i As Integer
Dim done As Integer
Dim newHour As Integer
Dim newMinute As Integer
Dim newSecond As Integer
Dim waitTime As String
Dim ws As Worksheet
Set ws = ActiveSheet
frame = Cells(1, "A").Value
For i = 1 To done
newHour = Hour(Now)
newMinute = Minute(Now)
newSecond = second(Now) + 1
waitTime = newHour: newMinute: newSecond
(ALL of work Code)
Application.Wait waitTime
Next i
End Sub
The code takes 0.30 seconds to 0.45 seconds to complete one cycle in for loop(not including wait time) depending on the processing of data. I want to add a definite 1 second to complete one for loop cycle no matter what the processing time of the loop is.
You have the right idea, but I think what you want is the TimeValue function (documented here).
You can use it like this:
Dim StartTime as Date
Dim EndTime as Date
StartTime = Now
EndTime = StartTime + TimeValue("00:00:01")
' Do Stuff
Application.Wait EndTime
This will cause the application to wait until one second after the start of the Do Stuff section, which I think is what you are asking for.
Hope this helps :)
You need to use different technique with do...loop + timer.
Sub WaitOneSecond()
Dim Start as single
start = timer
do while start + 1 > timer
doevents
loop
end sub
and in your original sub call WaitOnSecond where necessary:
Sub workwithdelay()
(...)
For i = 1 To done
(...)
call WaitOnSecond
Next i
End Sub
Some information to above:
timer counts seconds and milliseconds from midnights of today
be careful with my code run just before midnight- it is not correct when new day starts
application.wait doesn't recognise milliseconds therefore you are not able to get exact 1 second waiting time
Related
I'm trying to make a function to delay an email alert sent by excel. Basically my strategy is to have the email alert be triggered when two different cells are 1 at the same time. To delay it, after the initial email the plan is to have a function that sets one of the values to 0 so it wont send a ton of emails successively. I'm not terribly familiar with VBA, but Microsoft's website said this is the syntax to delay. However, this function only returns the 1 and not the 0. Is there a way to fix this so that it returns a value of 0 if called, waits a specified amount of time, and then returns the 1 again before ending. Or would there be a better way to accomplish what I'm trying to do?
Thanks
Public Function Delay_Alert() As Integer
Delay_Alert = 0
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 10
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
Delay_Alert = 1
End Function
The literal answer to your question is as follows. The code doesn't work as intended because the value you set only returns after the function finishes. So it is functionally equivalent to
Public Function Delay_Alert() As Integer
Dim ret as Integer
ret = 0
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 10
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
ret = 1
return ret
End Function
As you can see ret is only returned after it has been set the second time, nothing outside the function ever knows its been set the first time.
The broader question is more difficult to answer without more information. Crude solutions would be to put the delay in whatever function sends the email, or update the cells from the function as Avro suggests in the comments.
Public Function Delay_Alert() As Integer
Cells(2,1).Value = 0
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 10
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
Cells(2,1).Value = 1
End Function
But if you're having to put artificial measures in to stop your code becoming a spambot then the real answer is to fix that problem. If your happy it's not spamming then just send the emails as needed, I'm sure the operating system and servers can cope.
Edit:-
In response to the comment I suggest you add a Hysteresis to your trigger
https://en.wikipedia.org/wiki/Hysteresis#Control_systems
Sounds an interesting project!
I am trying to clear the data from cell A5 to A10 if the count in A3 is not equal to 6. I have written an "If statement" but it gives me out of stack error. How do I overcome this error
I have tried an "if statement" but it gives me error.
Dim Count As Integer
Dim BundleDup As Integer
Dim duplicateall As Integer
Dim SAPError As Integer
Private Sub Worksheet_Change(ByVal Target As Range)
Count = Range("A3").Value
BundleDup = Range("B3").Value
duplicateall = Range("C3").Value
SAPError = Range("D3").Value
If Target.Address = "$A$10" And Count = 6 And BundleDup = 0 And duplicateall = 0 And SAPError = 0 Then
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 3
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
Call MoveData
End If
If Count <> 6 Then
Call ClearData
End If
End Sub
The code works fine if I am not using this
If Count <> 6 Then
Call ClearData
End If
But once I use this and enter value in A5 to A10, it will clear the data but it will get stuck and give me the error.
The ClearData module includes the following code:
Sub ClearData()
'
' ClearData Macro
'
'
Range("A5:A10").Select
Range("A10").Activate
Selection.ClearContents
Range("A5").Select
End Sub
This is how this situation should be handled:
Public ClearingData As Boolean 'initializes natively to "False"
Private Sub Worksheet_Change(ByVal Target As Range)
If ClearingData Then Exit Sub 'stops the recursive loop
ClearingData = True
Count = Range("A3").Value
BundleDup = Range("B3").Value
duplicateall = Range("C3").Value
SAPError = Range("D3").Value
If Target.Address = "$A$10" And Count = 6 And BundleDup = 0 And duplicateall = 0 And SAPError = 0 Then
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 3
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
Call MoveData
End If
If Count <> 6 Then
Call ClearData 'Here's where the recursive loop gets created.
End If
ClearingData = False
End Sub
This prevents a recursive loop from starting and still leaves Events enabled. The code handles the specific situation completely within the function, avoiding unexpected results which could effect other functions.
If the "MoveData" function changed cell selection and you had a "Worksheet_SelectionChange" event being called, disabling events would prevent that event from being called. By using the above logic, the event would still be called. If you wanted to prevent the "Worksheet_SelectionChange" from being called from this function, you simply include the line "If ClearingData Then Exit Sub" at the start of the "Worksheet_SelectionChange" event.
Sorry, I used the term "Global" instead of "Public" in my comments above.
Just to add a bit around Rory's comment - your problem here is that you've created an infinite loop in your code.
From a very high level here is what's happening:
1. You change a value on your worksheet
2. The Worksheet_Change() code is called
3. This code then changes something on the same worksheet
4. This change causes the Worksheet_Change() code to run again
5. This code then changes something on the same worksheet
6. This change causes the Worksheet_Change() code to run again
7. This code then changes something on the same worksheet
8. This change causes the Worksheet_Change() code to run again
9.This code then changes something on the same worksheet
(you get the picture...)
To get around this you need to disable any further events from being called in your code:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False '// stop further events from firing
Count = Range("A3").Value
BundleDup = Range("B3").Value
duplicateall = Range("C3").Value
SAPError = Range("D3").Value
If Target.Address = "$A$10" And Count = 6 And BundleDup = 0 And duplicateall = 0 And SAPError = 0 Then
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 3
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
Call MoveData
End If
If Count <> 6 Then
Call ClearData
End If
Application.EnableEvents = True '// re-enable events
End Sub
Finally, if you're going to change application level settings in your code - I would strongly recommend you write a proper error handler into your code to revert any such settings in the event of a run-time error.
I have code where I am accessing an xml feed for live currency data. I need to capture the price every 3 minutes, and it has to be pretty spot on, otherwise it will throw off the other calculations.
I set up a timer to run the code every 3 minutes, or whatever interval I choose.
I started noticing slippage, which was a second or two at first but has turned into 10 seconds in places. Any thoughts?
Sub xmlData()
Dim aSwitch As String: aSwitch = Sheet2.[Switch].Value
Dim aSymbol As String: aSymbol = Sheet2.[Symbol].Value
'check [switch] status
If aSwitch = "OFF" Then
MsgBox "Switch is OFF!", vbCritical, "Program Status"
Exit Sub
End If
'MsgBox "Program is ON!", vbCritical, "Program Status"
'refresh xml data
Dim iMap As XmlMap
Set iMap = ActiveWorkbook.XmlMaps(1)
iMap.DataBinding.LoadSettings "http://****.com/webservice/v1/symbols/" & aSymbol & "/quote"
iMap.DataBinding.Refresh
'dim inputs
Dim aStart As String: aStart = Sheet2.Range("c3").Text
Dim aInterval As String: aInterval = Sheet2.Range("d3").Text
Dim aStatus As String: aStatus = Sheet2.[Status].Value
'oth
Dim aSecurity As String: aSecurity = Sheet2.[Security].Value
Dim aPrice As String: aPrice = Sheet2.[Price].Value
Dim aDatetime As String: aDatetime = Sheet2.[DateTime].Value
'separate adatetime
Dim aDate As String: aDate = Mid(aDatetime, 1, 10)
Dim aTime As String: aTime = Mid(aDatetime, 12, 10)
'Time actual
Dim aTimeNow As String: aTimeNow = Format(Now(), "HH:mm:ss")
'copy xml data to table
Dim aRow As Long
aRow = Sheet2.Cells.Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Row + 1
Sheet2.Cells(aRow, 1).Value = aDate
Sheet2.Cells(aRow, 2).Value = aTime
Sheet2.Cells(aRow, 3).Value = aSecurity
Sheet2.Cells(aRow, 4).Value = aPrice
Sheet2.Cells(aRow, 5).Value = aTimeNow
'start timer for reload
Application.OnTime Now + TimeValue(aInterval), "xmlData"
End Sub
EDIT 160627
Is it possible the XML is not fetching right away?
Calculate the scheduled time in absolute terms, rather than as a differential from Now. (Now will always be some amount of time after the last scheduled time, so will always creep)
Sub xmlData()
Static ScheduleTime As Variant ' Statis retains value between executions
Dim aInterval As String
' other code
' ...
If ScheduleTime = 0 Then
' initialise on first execution
ScheduleTime = Now + TimeValue(aInterval)
Else
' schedule based on last scheduled time
' this assumes execution time is always less than interval
ScheduleTime = ScheduleTime + TimeValue(aInterval)
End If
Application.OnTime Now + TimeValue(aInterval), "xmlData"
End Sub
I'm trying to write a script to pull doctor reviews from vitals.com and put them into an excel sheet.
It worked well when I just pulled the review, but when I added for it to pull the date as well, it will print the first review and date, then loads for a while, and then crashes. I'm new to all of this so I'm hoping there are some glaring mistakes I am not seeing. I just can't seem to find a way to fix it. Any help would be greatly appreciated.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim DocCounter As Integer
DocCounter = 2
Dim Go As String
Go = "Go"
If IsEmpty(Cells(1, 4)) And Cells(1, 3).Value = Go Then
If IsEmpty(Cells(DocCounter, 1).Value) Then GoTo EmptySheet
Do
Dim Reviews As String
Reviews = "/reviews"
Dim IE As MSXML2.XMLHTTP60
Set IE = New MSXML2.XMLHTTP60
Application.Wait (Now + TimeValue("0:00:01"))
IE.Open "get", "http://vitals.com/doctors/" & Cells(DocCounter, 1).Value & Reviews, True
IE.send
While IE.readyState <> 4
DoEvents
Wend
Application.Wait (Now + TimeValue("0:00:01"))
Dim HTMLDoc As MSHTML.HTMLDocument
Dim HTMLBody As MSHTML.HTMLBody
Set HTMLDoc = New MSHTML.HTMLDocument
Set HTMLBody = HTMLDoc.body
HTMLBody.innerHTML = IE.responseText
Dim ReviewCounterString As String
Dim ReviewCounter As Integer
ReviewCounterString = HTMLDoc.getElementsByName("overall_total_reviews")(0).getElementsByTagName("h3")(0).innerText
ReviewCounter = CInt(ReviewCounterString)
'Pull info from website loop'
Dim RC As Integer
RC = 2
Dim sDD As String
Dim WebCounter As Integer
WebCounter = 0
Do
sDD = HTMLDoc.getElementsByClassName("date c_date dtreviewed")(WebCounter).innerText & "-" & HTMLDoc.getElementsByClassName("description")(WebCounter).innerText
Cells(DocCounter, RC).Value = sDD
WebCounter = WebCounter + 1
RC = RC + 1
Application.Wait (Now + TimeValue("0:00:01"))
Loop Until WebCounter = ReviewCounter
Application.Wait (Now + TimeValue("0:00:01"))
DocCounter = DocCounter + 1
If IsEmpty(Cells(DocCounter, 1).Value) Then GoTo Finished
Loop
Finished:
MsgBox ("Complete")
End Sub
EmptySheet:
MsgBox ("The Excel Sheet is Empty. Please add Doctors.")
End Sub
End If
End Sub
When you do Cells(DocCounter, RC).Value = sDD the Worksheet.Change event gets triggered again and the macro starts over again, until the call stack is full (I think).
Add
Application.EnableEvents = False
at the start of the macro and
Application.EnableEvents = True
at the end. That way the event will not be triggered during the macro.
Edit: You should probably also think about if it's really necessary to run the macro every time anything is changed anywhere on the sheet. You could check Target (the range that was changed) first to see if the change makes it necessary to reload the data.
I created a status bar that works off of application.wait. In a sense, that worked quite well. However, I want to be able to update the status bar while working on other worksheets. I am unable to find a suitable method. Below is what I have thus far.
Private i As Integer
Private newHour
Private newMinute
Private newSecond
Private waitTime
Private Sub UserForm_Activate()
For i = 1 To 10
UserForm1.Label1.Width = UserForm1.Label1.Width + 24
Application.Calculate
If Application.CalculationState = xlDone Then
newHour = Hour(Now())
newMinute = Minute(Now())
newSecond = Second(Now()) + 1
waitTime = TimeSerial(newHour, newMinute, newSecond)
Application.Wait waitTime
End If
Next
UserForm1.Hide
End Sub
NOTE: For some odd reason the only way I could get the label to constantly update was to use Application.Calculate. Otherwise, it would wait the full 10 seconds, and then the status bar would reach maximum extension before hiding the userform.
Use this delay function instead of Application.Wait
Private Sub delay(seconds As Long)
Dim endTime As Date
endTime = DateAdd("s", seconds, Now())
Do While Now() < endTime
DoEvents
Loop
End Sub