how to use Application.OnTime to delay a loop - vba

SO I'm writing a code which makes references to bloomberg cell formulas... as a result the loop often skips faster than data can load. I think this can be solved relatively easily if I can somehow delay the iteration of the loop ... which would give cells time to populate. This is what I have written so far, and I'm not sure how to write the last line really.
x = 1
If x < TixCollection.count Then
' runs report if ADR Close is active
If Sheets("Input").Range("L2").value = "x" Then
Call build_singleEquity(x)
'Set pat = New pattern
Application.OnTime Now + TimeValue("00:00:10"), "pattern_recogADR"
If Sheets("Input").Range("L5").value = "x" Then
Dim sht_name As String
sht_name = TixCollection(x).ADR & "_ADRclose"
Call Sheet_SaveAs(path_ADRclose, sht_name, "SingleEquityHistoryHedge")
End If
End If
'runs report if ORD Close is active
If Sheets("Input").Range("L9").value = "x" Then
Call build_ordCloseTrade(x)
If Sheets("Input").Range("L13").value = "x" Then
Dim sht_name1 As String
sht_name1 = TixCollection(x).ADR & "_ORDclose"
Call Sheet_SaveAs(path_ORDclose, sht_name1, "OrdCloseTrade")
End If
End If
Application.OnTime Now + TimeValue("00:00:15"), x = x + 1 'want to write something like this but syntax is wrong

Application.OnTime essentially schedules an event to run at/after a particular time. It allows you to schedule an event to run even after your code has executed and cleared the stack. This is why it's an Application level method.
The OnTime method doesn't pause or delay anything, it's simply a scheduler. So the rest of your code will continue to execute, and to whatever extent that code relies on the results of the task that is waiting for the OnTime, you'll run in to errors.
In theory, I think you could probably make this approach work in a roundabout way for your purposes, but I think you'd probably be better served to use the WinAPI Sleep function. This also gives you greater granularity (you can specify to the millisecond).
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
You can invoke this from any subroutine or function, by calling:
Sleep 5000 'pause for 5000ms, (5 seconds)
UPDATE
I notice your code doesn't actually contain a Loop structure. I think I understand what you're trying to do. Here's a stab at it:
Do While x < TixCollection.count Then `or consider: Do While x <= TixCollection.Count
' runs report if ADR Close is active
If Sheets("Input").Range("L2").value = "x" Then
Call build_singleEquity(x)
'Set pat = New pattern
Sleep 10000
Call pattern_recogADR
If Sheets("Input").Range("L5").value = "x" Then
Dim sht_name As String
sht_name = TixCollection(x).ADR & "_ADRclose"
Call Sheet_SaveAs(path_ADRclose, sht_name, "SingleEquityHistoryHedge")
End If
End If
'runs report if ORD Close is active
If Sheets("Input").Range("L9").value = "x" Then
Call build_ordCloseTrade(x)
If Sheets("Input").Range("L13").value = "x" Then
Dim sht_name1 As String
sht_name1 = TixCollection(x).ADR & "_ORDclose"
Call Sheet_SaveAs(path_ORDclose, sht_name1, "OrdCloseTrade")
End If
End If
x = x+1
Sleep 15000 'Wait for 15 seconds
Loop
It is hard to tell if both of the Sleep (your previous OnTime calls) are really needed, since I'm not sure which one (or if both) was introducing the error condition.
You will need to make sure that you put the code for the Sleep function in the vba project.
Update
Assuming the sleep function, or Application.Wait method does not work for you, one other thing you could try is a simple Do/While Loop. Although I am not able to replicate your condition, this seems like possibly the most reliable.
Dim newTime As Date
newTime = Now + TimeValue("00:00:10")
Do While Not Now >= newTime
DoEvents
Loop
A final option would be to disable and manually force calculation, like below. My understanding is that the application is busy and will not execute code while a calculation event is occurring. However, with this in mind I'm not sure if any of these approaches will work for you, because although you indicate it's waiting on an Excel worksheet calculation, I don't think that is possible, the worksheet event takes precedence over running code, so I'm thinking that something is still happening on the client side which you might not be able to trap reliably unless they provide some sort of method through the API (something like .Busy which returns a boolean, etc.).
Dim appCalc as Long: appCalc = Application.Calculation
Application.Calculation = appCalc '# disable automatic calculation
Call build_singleEquity(x)
Application.Calculate 'Force calculation
Application.Calculation = xlCalculationAutomatic '# return to normal/previous property
Call pattern_recogAD

Related

What is causing the delay between recordset.update and the form/report getting the information?

Short version
I'm entering information in a database and fetching it shortly after, but for some reason, when I enter the information, it isn't immediately entered, so that when I try to fetch it, I get old results. Why does this happen? I thought the operations were synchronous.
Long version
I have a split Access database. At the moment the backend is on my own hard drive to speed up testing, eventually this backend will land on a server. Back when it was a combined frontend/backend database and before I had done a major code refactor (tbh, it was quite the clusterfornication before that), and now this is happening in a number of different scenarios, but pretty much every time I enter information and try to fetch it right after that. Why this happens is a mystery to me, since everything I was reading told me there is no multi-threading in VBA and that everything is synchronous if not specified otherwise, and I haven't enabled any asynchronous options.
Two Examples:
I add a record to the database then refresh the form that contains those new records. I'm not going to post the full code (unless it is deemed necessary), since I've modularized the code a lot. But essentially it boils down to this: the user clicks a button which executes this:
Private Sub Anhang_hinzufügen_Click()
If IsNull(Me.Parent.ID) Then
MsgBox "Bitte erst Felder ausfüllen, und anschließend Anhänge hinzufügen", vbInformation
Else
AnhängeAuswählen Me.Parent.Name, Me.Parent.ID
Me.Form.Requery
End If
End Sub
As part of the AnhängeAuswählen method, the method AddRecord is called:
Function AddRecord(TableName As String, fields() As String, values) As Long
Dim Table As DAO.Recordset
Set Table = LUKSVDB.OpenRecordset(TableName)
Table.AddNew
For i = LBound(fields) To UBound(fields)
If TypeName(Table.fields(fields(i)).Value) = "Recordset2" Then
Dim rs2 As DAO.Recordset2
Set rs2 = Table.fields(fields(i)).Value
If IsArray(values(i)) Then
For j = LBound(values(i)) To UBound(values(i))
rs2.AddNew
rs2!Value = values(i)(j)
rs2.Update
Next j
Else
rs2.AddNew
rs2!Value = values(i)
rs2.Update
End If
Else
Table.fields(fields(i)) = values(i)
End If
Next i
AddRecord = Table!ID
Table.Update
Table.Close
End Function
The record is created, that's not the problem. But when it executes Me.Form.Requery, the new record doesn't appear in the form. Only when I execute Me.Form.Requery a fraction of a second later does the record appear.
I add a record to the database using a form, update some information in the recordset with VBA, then requery the subreport with the records. The record appears immediately, but the details I added programmatically only appear when I execute Me.Parent.Requery a couple of seconds later.
The first form is a data entry form, so that as soon as the data is saved, it's blank in order to create a new record. The previous should then appear in the form. The button to create the new record looks like this:
Private Sub Anmerkung_Hinzufügen_Click()
currentID = Me.ID
mSaved = True
If Me.Dirty Then Me.Dirty = False
UpdateRecord "Anmerkungen", currentID, StringArray("Person", "Datum"), Array(User, Now)
Me.Parent.Requery
End Sub
The UpdateRecord is similar to the AddRecord method:
Function UpdateRecord(TableName As String, ByVal ID As Integer, fields() As String, values)
Dim Table As DAO.Recordset
Set Table = SeekPK(TableName, ID, True)
Table.Edit
For i = LBound(fields) To UBound(fields)
If TypeName(Table.fields(fields(i)).Value) = "Recordset2" Then
Dim subtable As DAO.Recordset2
Set subtable = Table.fields(fields(i)).Value
If IsArray(values(i)) Then
On Error Resume Next
Dim t
t = LBound(values(i))
If Developer Then On Error GoTo -1 Else On Error GoTo Fehler
If Err.Number = 0 Then
For j = LBound(values(i)) To UBound(values(i))
subtable.AddNew
subtable!Value = values(i)(j)
subtable.Update
Next j
End If
Else
subtable.AddNew
subtable!Value = values(i)
subtable.Update
End If
Else
Table.fields(fields(i)) = values(i)
End If
Next i
Table.Update
Table.Close
End Function
Does anyone know why this happens, and how I can prevent it? I could do a bit of a workaround with timers on the forms, so that it refreshes the form a couple of seconds later, but that seems like a kludgy workaround to me, especially considering I don't know how long it specifically takes, and the times could change drastically once the backend is on the server.
Additional information, in case it's necessary:
In the code I've posted I've removed some additional code for error handling and performance logging, but it doesn't have any impact on what's happening otherwise.
When the database is opened, a global variable LUKSVDB As DAO.Database is initialized:
Function ConnectDatabase(Backend As Integer)
Select Case Backend
Case 0: DatenOrt = 'redacted, folder in which the production/beta database is located on the server
Case 1: DatenOrt = 'redacted, folder in which I have a personal testing database on the server
Case 2: DatenOrt = 'redacted, folder in which I have the testing database on my own computer
End Select
Set LUKSVDB = OpenDatabase(DatenOrt & "\LUKS-Verwaltung_be.accdb", False, False, ";pwd=PASSWORD")
End Function
For testing purposes, ConnectDatabase is launched with a value of 2. However, if it's a problem on my own SSD, where latency is just about 0, then I can only assume it will be a problem on the server as well, where the latency is definitely not 0.

Jump out of code if a button is pressed

Is there some way to have a 'listener' of sorts to listen for a button click in the middle of code? There are certain scenarios where I won't have to wait for the code to complete before I can exit the functions but I cannot seem to find a way to see if the button was clicked other than throwing a ton of if checks throughout the function calls which doesn't seem very efficient to me.
This code runs on the thread, different from your main application
private function LongRunningFunction()
Din canceled as boolean = false ' to make sure it is canceled while in the loop
' this is very long loop
For i as integer = 0 to 100000
If _cancelExecution Then
canceled = true
Exit For
End If
' your code runs here
.........
Next
If canceled Then
' Wrap up this thread, clean up stuff, etc
End If
.......................
End Function
On the main thread, when button is clicked _cancelExecution is set to true
The BackGroundWorker has this mechanism built-in already

Detect when exe is started vb.net

Dose anybody know how I can make my VB.net application wait until a process is detected as running?
I can find example of how to detect once an exe has finished running but none that detect when an exe is started?
You can use the System.Management.ManagementEventWatcher to wait for certain WMI events to occur. You need to give it a query type and condition to have it watch for the next creation of your process, then get it to do something when that occurs.
For example, if you want :
Dim watcher As ManagementEventWatcher
Public Sub Main()
Dim monitoredProcess = "Notepad.exe"
Dim query As WqlEventQuery = New WqlEventQuery("__InstanceCreationEvent", new TimeSpan(0, 0, 1), "TargetInstance isa ""Win32_Process"" And TargetInstance.Name = """ & monitoredProcess & """")
watcher = New ManagementEventWatcher()
watcher.Query = query
'This starts watching asynchronously, triggering EventArrived events every time a new event comes in.
'You can do synchronous watching via the WaitForNextEvent() method
watcher.Start()
End Sub
Private Sub Watcher_EventArrived(sender As Object, e As EventArrivedEventArgs) Handles watcher.EventArrived
'Do stuff with the startup event
End Sub
Eventually you'll need to stop the watcher, which is you can do by closing the app, or calling watcher.Stop(). This has been written as brain compiler, so if there's any issues let me know.
You could simply wait and check every once in a while whether the process exists. Use Thread.Sleep to avoid busy waiting.
However, this has the possibility that you miss the process if it starts and exists during your wait time.
You can use the below condition
return Process.GetProcesses().Any(Function(p) p.Name.Contains(myProcessName))
Dim p() As Process
Private Sub CheckIfRunning()
p = Process.GetProcessesByName("processName")
If p.Count > 0 Then
' Process is running
Else
' Process is not running
End If
End Sub
OR SIMPLY
System.Diagnostics.Process.GetProcessesByName("processName")

SheetCalculate - How does this sub routine run?

I have this module here that has is Workbook subroutine. I can't for the life of me understand how the GenerateLimitSummary is ever able to run? Can someone please articulate the process flow here?
Private LimitBool As Boolean
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
If LimitBool Then Exit Sub
' use conditional formatting to highlight limit breaches
ApplyConditionalFormatting
' regenerate the summary limits sheet
LimitBool = True
GenerateLimitSummary
LimitBool = False
End Sub
The author uses LimitBool to prevent a infinite loop/a stack overflow:
Initially, LimitBool is False, therefore the remainder of Workbook_SheetCalculateis executed
Now, LimitBool is set to True (after it was confirmed it's not True)
GenerateLimitSummary is executed. If this routine now for some reasons forces the workbook to recalculate, Workbook_SheetCalculate will be triggered again. However, as LimitBool is now True*, the second call to this procedure is now Exited after the first check. If it would not have this check, it would again call GenerateLimitSummary, which would then trigger the recalc, etc...
After the GenerateLimitSummaryran, LimitBool is set back to False, therefore, it can ran again
(*) - it has a Module-wide scope, i.e. it keeps it value across the different calls, while a procedure-wide scope (=Dimmed in the sub) would create a new variable for each call

While loop causes the app to go slow? Any idea why?

I have a simple code that looks up a text file, reads the line of text, splits the string by semi-colons and then posts the results.
After it has done this, I have created a really simple while loop to waste 10 seconds before going for it again.... here is the code:
Private Sub checkTemps()
While Abort = False
Try
fileReader = New StreamReader(directory.Text & "currentTemp.dat")
rawData = fileReader.ReadLine()
fileReader.Close()
Dim dataArray() As String
dataArray = rawData.Split(";")
updateOutput("1", dataArray(0), dataArray(1))
updateOutput("2", dataArray(2), dataArray(3))
updateOutput("3", dataArray(4), dataArray(5))
updateOutput("4", dataArray(6), dataArray(7))
stpWatch.Start()
While stpWatch.Elapsed.Seconds < 10 And Abort = False
pollInterval(stpWatch.ElapsedMilliseconds)
End While
stpWatch.Stop()
stpWatch.Reset()
Catch ex As Exception
msgbox("oops!")
End Try
End While
closeOnAbort()
End Sub
But when it gets to the "time-wasting" loop - it seems to slow the whole application down? And I can't work out why!
So a couple of questions... is there a better way to do all this? and second - can anyone spot a problem?
All the other commands seem to run fine - there isn't much else to this app. I have another program that updates the dat file with the values, this is simply a client side app to output the temperatures.
Any help would be appreciated.
Andrew
More info:
I should explain what the pollInterval sub does!
Private Delegate Sub pollIntervalDelegate(ByVal value As Integer)
Private Sub pollInterval(ByVal value As Integer)
If Me.InvokeRequired Then
Dim upbd As New pollIntervalDelegate(AddressOf pollInterval)
Me.Invoke(upbd, New Object() {value})
Else
ProgressBar1.Value = value
End If
End Sub
Your loop is a very tight loop continually calling pollInterval. This will tie up the application until the loop condition is met.
You should use the Sleep method to pause this thread for the required amount of time.
If you want to show the progress (as per your update) you could put the Sleep into the loop and sleep for 1 second (or half a second?) at a time:
While stpWatch.Elapsed.Seconds < 10 And Abort = False
Sleep(1000) <-- NOT 100% sure of the syntax here,
but the time is specified in milliseconds
pollInterval(stpWatch.ElapsedMilliseconds)
End While
You should go with
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10).TotalMilliseconds);