VBA - Updating Progress Bar while running SQL Query - vba

I have a progress bar that updates based on the line:
Call ProgressBar(X)
Where X indicates the percentage that the bar displays as 'complete'.
I've roughly calculated various time intervals throughout the code and placed the line several places throughout. It's at the point where it runs quite smoothly for the majority of the code but only half of the bar, with the problem being a forced jump from 10% to 60%.
I'm using an ADODB connection to run a SQL query in the code (I can't take it out of the code because I'm passing variables through it). The jump from 10 to 60 is either side of the line where I'm executing the query
Set rs = conn.Execute(QryND)
where rs is defined as ADODB.Recordset and conn as ADODB.Connection.
I guess ideally what I'm after would be to know if it's possible to say:
Call ProgressBar(10)
'code to the effect of: "in x seconds, execute the next line but in
'the meantime continue with the code
Call ProgressBar(20)
'code to the effect of: "in 2x seconds, execute the line but in the
'meantime continue with the code
Call ProgressBar(30)
Set rs = conn.Execute(QryND)
Or something to that effect.
Alternatively a means of running the query in the background and continuing the code up to a point. Eg
Call ProgressBar(10)
'instruct to run in backrgound:
Set rs = conn.Execute(QryND)
Call ProgressBar(10)
'wait x seconds
Call ProgressBar(20)
'wait x seconds
.
.
.
'Stop running query in background (in case it hasn't finished)
Do either of these sound possible?

Running background queries is not recommended as they can run your code into errors. What you can actually do is leave the StatusBar the f* alone and check it with while loops (or at regular intervals) , i.e. does it fetch the connection, run or is it finished.
What you can do is create an ActiveX object to display whatever you wanted to tell, even up to fancy load bars and multiline feedback.
You cannot really have asynchronous processes in a single-threaded application unless you call outside scripts to execute them.

Related

VBA execution flow interrupted unexpectedly

Context of the problem
I'm developing a new feature for an HMI using Factory Talk View Studio 7.00.00 (CPR 9 SR 6) and VBA 6.5.
I have two displays: ma1_header and ma2_header and, as many of you know, in Factory Talk View Studio (I'm going to call it as FTV for brevity from now on) each display has a dedicated DisplayCode.
A Display code can be seen as the VBA code behind an Excel file that stays open as long as its excel file. From this point of view, the VBA code bounded to a display in FTV has the same behaviour of a VBA project in excel, so it's closed when the graphic display it's closed by the user or from code.
Another important point in order to understand the problem is that when a generic display in FTV called A is opened in Replace mode with at least one pixel that overlaps another display B, the display B is closed with its VBA code. Take in mind that ma1_header and ma2_header are always opened in Replace mode.
Problem description
That said, now I'm going to describe the problem that I found.
The VBA code bounded to ma1_header and ma2_header is mostly the same (the differences are pointed out in the following schema) and it performs some init actions on display start and after those it runs a procedure called ScheduleCheck. This procedure updates some UI components and it evaluates some conditions in order to determine if it's time to show ma2_header (ma1_header if the code it's executed behind ma2_header), then it recalls itself.
The command that opens a display is not executed directly from VBA, its executed asynchronously outside VBA. In fact VBA for some actions like: "Show a display", "Set tags values", etc. can uses a library that enables it to tell to a FTV service (that also can be used with a command line tool) to perform a list of these actions (1 command or more transmitted at time).
When a user starts a FTV client the ma1_header is shown first with some others displays which compose the user interface.
In ma1_header, when the opening conditions for ma2_header are satisfied and it's opened, the problem comes out. Let's proceed now with a step by step description of the VBA state, in order to be as clear as possible on the problem:
In ma1_header the opening conditions are satisfied and the asynchronous command that shows ma2_header is excuted. Note that for the moment ma1_header vba is still open.
When ma2_header start opening the code it's executed without any problem till the wait procedure. The wait procedure is written as follow:
Public Declare Function GetTickCount Lib "kernel32" () As Long
Public Sub wait(lMillSec As Long)
Dim lT2 As Long
Dim lT1 As Long
On Error GoTo errHandle
Const strMethod = "wait"
MsgBox "wait - 1"
lT1 = GetTickCount
lT2 = lT1
While lT2 - lT1 < lMillSec
lT2 = GetTickCount
DoEvents
Wend
errHandle:
If Err.Number Then
LogDiagnosticsMessage "VBA: Display " & Me.Name & " in Method " & strMethod
Err.Clear
End If
End Sub
The execution of the wait procedure proceed without any problems until the DoEvents command on the 18th rows. When DoEvents it's executed the ma1_header has the time to close itself definitively (even its vba code) then the vba flow in ma2_header seems to stops here without any error. Due to this the ScheduleCheck can't recall itself.
Ugly solution 1
Currently I found what I call an ugly solution that let code works.
When ma1_header is open, the ma2_header opening will force the close of ma1_header that , as I explained before, is completed only when DoEvents is executed.
I tried to transmit this new list of commands to the FTW tool when the ma2_header need to be shown:
##New##
Abort MA2_HEADER;
Pause 1;
Display MA1_HEADER /TRRU;
##Old##
Display MA1_HEADER /TRRU;
With the new approach I close the ma2_header, then I wait (in the FTV tool, not in VBA) for 1 second with the command "Pause 1;" then I open ma1_header when I'm reasonable sure that ma1_heder is closed thanks to the pause command.
In this way the ma2_heder periodically executes the procedure ScheduleCheck without strange execution interruption.
I don't know why this solves my problem, so I'd like to understand why it works and which is the cause of this problem in order to find a better solution.
Ugly solution 2
I found another ugly solution, but as before I'm not satisfied because I would like to know way my current code has this problem (for me solving a problem without knowing what is the cause it's a defeat as a programmer).
I've created the following new tag on the Tag server of FTV
I've created the following new event on FTV:
I've added a string display containing the new tag Tick in ma1_header and in ma2_header
Now, in VBA, I can use the Change event of this string display in order to execute the same code contained in ScheduleCheck (obviously without the wait with DoEvents loop) each time this string display changes (each second).
Any clarification on the problem or a better solution would be really appreciated.

VBA Macro running too fast

It's weird that I'm finding ways to slow down my macro. Apart from Doevents and other time delay techniques, which are basically a workaround, is there a way through which we can get around the asynchronous execution. As in, I want the VBA code to behave like this:
start executing line 1>finish executing line 1>move to line 2;
Forgive if I'm wrong but currently it seems to follow:
Start executing line 1>without caring whether line 1 finished or not start executing line2
If you are calling external programs (vbs, exe) then the vba isn't getting any feedback on the process at all. It calls the programs and moves on to the next line of code (the vba doesn't know if/when the external programs finishes).
One way to slow this process down would be to put a application.wait or application.sleep between the calls, but that is also a workaround. Please post your actual code and perhaps we can troubleshoot further.
if the code is about refreshing data, use Refresh method with backgroundquery=False in a For Loop instead of RefreshAll.
For Each con In Me.Connections
con.ODBCConnection.BackgroundQuery = False
con.Refresh
Next

Limit The Time A Routine Can Run - VBNET

I'm trying to fill a dataset, but I want to limit the amount of time the system has to fill this dataset to 30 seconds.
I have tried (as suggested elsewhere on SO):
Dim T As Date = Date.Now
da.Fill(ds, "DATASET")
Do
If (Date.Now - T).TotalSeconds >= 30 Then
Main.VIEW_Title.Text = "Error In Connection..."
Exit Sub
End If
Exit Do
Loop
But the system just hangs anyway during the da.Fill(ds, "DATASET") section and doesn't ever exectute the "Error In Connection" message. It doesn't matter if I put the line inside the DO either, because it stops there. What I need is for it to execute the fill command, and then if it doesn't complete in 30 seconds, to allow me to handle that error.
Thanks
Unfortunately, that's not so easy.
What you can do is:
Start a background thread and do the fill operation in that thread.
In the main thread, wait for 100ms, check if the new thread completed, repeat at most 300 times.
Now the real problem is: What do you do if the main thread determines that the background thread has not finished after 30 seconds? DataAdapter.Fill does not provide a method to gracefully interrupt the operation, so you'll have to let it continue in the background.
This also means that you must use a separate data connection in the background thread, since the ADO.NET classes are not thread-safe.
I'd rather suggest a different approach: A data selection operation taking more than 30 seconds implies that either:
you select too much data (there's really no use showing 1 million rows to the user) or
your query needs optimization (such as a well-placed index).
Thus, I suggest to fix the root cause instead of using complicated workarounds to cover-up the symptom.

Code Execution Sequence

I am a PLC programmer who is currently using a variant of VB to control a motor.
I want to call a function that will execute moves and not return to the main code until the move has been completed. Currently here is what I have:
Program 'Main Program
While 1
If move_req = 1
Function MoveMotor
End If
Wend
End Program
Function MoveMotor
MoveABS 10 ' Move to encoder position 10mm
move_complete = 1
While move_req = 1
'Do Nothing
Wend
End Function
For some reason this code isn't working and the move command is being sent over and over again. Could this be because the main program continues to run when the function is running? Is that how VB works? I am used to thinking of code sequence in terms of PLC's where they scan through everything repeatedly at a certain frequency.
Whenever the move is complete, there must be a way for it to be detected by the program. It looks like you want move_req to be set to zero when this happens, but I cannot see what would cause that. How does the machine signal the program that it's finished moving?
A second point is that when you have a loop that waits while it checks for variable change, it can cause a CPU spike. You can put a pause in the loop with some thing like System.Threading.Thread.Sleep(100) where 100 is milliseconds to pause.

.NET Equivalent for VB6 DoEvents + Sleep

I have a program that I'm updating from VB6 to VB.NET that is used for making optical measurements. The hardware runs Win XP or Win 2000 Embedded. In the original code, there is a section that is triggered after a measurement is started that uses DoEvents and Sleep:
While instDefItf.InstrumentState = Busy
Sleep 500
DoEvents
Wend
I've tried replacing the middle two lines in my code with a simple Threading.Thread.Sleep(500) (I realize that this is probably poor practice, but not having a ton of experience with VB, I was trying to preserve the VB6 program as closely as possible). This makes the code work correctly - on the second try. On the first try, the measurements that are supposed to be taken simply aren't, but no errors are thrown and the subsequent code executes correctly.
Based on this, I have two primary questions. First, is there something I could simply replace the VB6 code with to get the desired functionality? Second, is there a better way to periodically query the instrument state that doesn't make use of the Sleep function?
EDIT:
The function I referred to above is a helper function (I think that's the right term) that checks to see if the instrument has completed its measurement. The measurement routine is contained within a DLL, and the code that executes it is given below.
If cbMeasLength.Value = vbUnchecked Then
Call I12001Ref.StartMtj1IlAcquisition2(CLng(lChannel), ConnectorA, m_moduleCollection.ReflectanceModule(lRm).SerialNumber)
End If
' Wait the measurement completion
If cbMeasLength.Value = vbUnchecked Then
If WaitForOperationCompleted = False Then
Exit Sub
End If
End If