word vba: webbrowser not navigating for ReadyState to check - vba

I am trying to extract links from webpages, but it seems webbrowser does not navigate, so I get infinite loop at webbrowser1.readstate <> readystate_complete...
HOWEVER, if make a breakpoint at webbrowser1.readstate <> readystate_complete, the webbrowser navigates successfully in the userform, and code works....
Any ideas? Thanks
Do Until n = num
WebBrowser1.Navigate URL
Do While WebBrowser1.readyState <> READYSTATE_COMPLETE
Loop
If WebBrowser1.readyState = READYSTATE_COMPLETE Then
'code
end if
n = n +1
loop

The while loop you use in your question and in your answer is a busy waiting tight loop, consuming CPU cycles in vain while waiting for something to happen. It works (sort of) for InternetExplorer object, because the latter runs in its own separate process. It doesn't work for in-process WebBrowser control, because your loop doesn't pump Windows messages, which is required for navigation to work. If you want to stick with the loop approach, consider placing Sleep 250 and DoEvents calls inside your loop to mitigate busy waiting and pump messages. This is still not recommended, instead you should consider re-factoring your code to use WebBrowser_DocumentComplete event.

Related

Infinite Loop crashes VBA/Powershell and Win11

How can I create a Powershell or VBA script to run a command, wait and loop the command and wait infinitely until I close the respective window? I have tried the below in Powershell and VBA but they make the respective Powershell or VBA windows unresponsive and a bit later the whole system becomes unresponsive and I have to reboot.
Sub proc()
Do while $true
Application.Windows("workbook1.xls").Activate
Application.Wait(Now + TimeValue("0:10:00"))
Loop
End Sub
UPDATE:
I think it's obvious what I am trying to do: activate the window of a particular workbook every few seconds indefinitely.
I have tried DoEvents (both below the Application.Windows and the Application.Wait but it does not solve anything. Also I saw the below which says to avoid DoEvents:
https://www.fmsinc.com/microsoftaccess/modules/examples/AvoidDoEvents.asp
I tried Application.OnTime which made things much better but eventually got unresponsiveness too

DoEvents is used yet application is not responsive

In my application, I call a process to update software - which is stored within its own class. Even thou I have been writing Application.DoEvents() in few places for some reason the label in the update form is not updating and the form itself is inactive.
Namespace software
Public Class updater
Public Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Boolean
Application.DoEvents()
Thread.Sleep(1000)
frmUpdate.lblResult.Text = "Update is about to begin"
Thread.Sleep(1000)
frmUpdate.lblResult.Text = "Downloading data"
Thread.Sleep(1000)
Application.DoEvents()
frmUpdate.lblResult.Text = "About to start the writing process"
Application.DoEvents()
frmUpdate.lblResult.Text = "Software was updated, please restart your device."
End Function
End Class
End Namespace
I can't figure out why you were calling DoEvents in those specific locations, since none of them will have any visible effect where they are. The first one happens before any labels are changed, so allowing the form to refresh there is pointless. The others are at the very end, after all the long-running work is already done (the three sleeps). So, while they will allow the form to refresh before execution leaves the method, it will very very shortly be leaving the method anyway, so there's no point in doing it there either. The only place where it would even be applicable to call DoEvents would be between two long running things. For instance, if you did this, you'd notice a difference:
Public Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Boolean
Thread.Sleep(1000)
frmUpdate.lblResult.Text = "Update is about to begin"
Application.DoEvents()
Thread.Sleep(1000)
frmUpdate.lblResult.Text = "Downloading data"
Application.DoEvents()
Thread.Sleep(1000)
frmUpdate.lblResult.Text = "About to start the writing process"
frmUpdate.lblResult.Text = "Software was updated, please restart your device."
End Function
You need to understand, in .NET WinForms (as well as in WPF), the UI is running on a single thread. What I mean by that is, if one of your event handlers contains code that takes a long time to complete, the UI will be frozen for the entire time that event handler is executing. The UI refreshing is completely blocked until the last event handler finishes doing whatever it was doing. DoEvents is somewhat of a hack way of getting around that (and a dangerous hack, at that). Each time you call DoEvents, it returns control back to the form to handle whatever else it has queued up to do (such as repainting the screen and handling user input) and then execution is returned to the original event handler so it can continue where it left off. That means, each time you call DoEvents, it allows the form to repaint at that point, but the event handler still blocks the UI in between all of the DoEvents.
As others have already implied, using DoEvents is highly discouraged. Not only is it less effective, it can cause all sorts of unexpected behavior. In pre-.NET versions of VB (e.g. VB6), DoEvents was often the only option, but in .NET, multi-threading is relatively easy. There may be occasions where DoEvents is legitimately still useful, but they should be very few and far between and only implemented with great care and caution.
There are two recommended ways of implementing multi-threading in WinForm applications. The original method, which still works well, is to use a BackgroundWorker component (you'll find it in the WinForm designer tool-box). The BackgroundWorker raises an event on a different thread so that you can do all your long-running work in that event handler, without it blocking the UI. Then, when it's all done, it raises another event, back on the UI thread so that you can update the UI after the work is complete.
The newer method, which is much cleaner and easier to read, but a little more sophisticated, is to use the Async and Await keywords to make all your long-running methods, and your event handler that calls them, asynchronous.

VB Form Filling

I may be missing a very basic thing, but i just dont understand why this isnt working.
I am looking to fill out some web forms based on some user input.
I have already written this in vbscript where its working flawlessly. Now i want to import it to VB.
My 2 main issues atm.
Before, i would have a piece of code:
For Each wnd In CreateObject("Shell.Application").Windows
If InStr(1, wnd.FullName, "iexplore.exe", vbTextCompare) > 0 Then
Set IE = wnd
Exit For
End If
Next
For deciding what Internet Explore window to use. However, if i use this form in VB, i miss out on pretty much any type of event handling. I cant use IsBusy, i cant use DocumentCompleted or anything.
So instead i have to declare IE as a browser.
Dim IE As New WebBrowser
Which seems fine.. Except.. Now i cant navigate anywhere, no browsers are created, theres nothing happening. So when i run my code:
IE.Navigate("www.awebwithusernameandpasswordform.org")
IE.Visible = True
Helem = IE.Document.GetElementById("username")
Helem.Value = "xxxx"
Helem = IE.Document.GetElementById("password")
Helem.Value = "xxxx"
Helem = IE.Document.Forms("signupForm")
Helem.Submit()
It just fails at the element "Username" because it never gets to navigate to the website.
It like it just ignores the IE.Navigate. I have tryed running a new process with process.start() but that doesnt work either, it still isnt opening a webpage for me or anything.
Secondly, i am having major issues having vb checking when a document is completely loaded.
In VBS i used
Do while ie.readystate <> 4 or ie.busy or ie.document.readystate <> "complete"
wscript.sleep 500
Loop
Do until ie.document.readystate = "complete"
wscript.sleep 500
Loop
And that works like a charm.
In VB, ie.document.readystate is not recognised, and i cant seem to get DocumentCompleted event handling to work. I have attempted several thing from this website, but either they just make everything hang, or it just doesnt work for various reasons. I have attempted the 2 solutions from How to wait until WebBrowser is completely loaded in VB.NET? but they both just make the script hang.
Any tips would be greatly appreciated..
What is happening is that your code runs way faster than the browser can navigate, meaning that you try to get elements before it has loaded any, as Bjørn-Roger Kringsjå tried to say.
What you need to do is to wait between .Navigate and .GetElementById :
IE.Navigate("www.awebwithusernameandpasswordform.org")
'WAIT BEFORE COMPLETION
'Continue
Helem = IE.Document.GetElementById("username")
Helem.Value = "xxxx"
However, this is really tricky to do in-line and is not recommended. It is much better to use the DocumentCompleted event handler for the browser control to run code after a page is loaded. Hope this clears it up enough.

VB.NET DOEVENTS

I have a VB6 app, which does a lot of processing in the form_load. A call to DoEvents ensures that the form loads before the processing is complete. However,this does not appear to work in VB.NET i.e. in the following code, the loop finishes befoRe the form is loaded, even though I have called DOEVENTS:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
For i = 0 To 100000
Application.DoEvents()
Dim Test As String = "Test"
Next
End Sub
What have I missed?
Your statement:
A call to DoEvents ensures that the form loads before the processing is complete
To the best of my knowledge, this is NOT true. The form "paints" before processing your load event - the processing (that is loading) is still not complete. All that is ensured by a call to DoEvents is OTHER messages get a chance to be handled when you are in the middle of a long processing. MSDN's help of DoEvent() describes it as:
Processes all Windows messages currently in the message queue.
Also, it specifically states:
Unlike Visual Basic 6.0, the DoEvents method does not call the Thread.Sleep method.
I believe it might be risky for you to handle your requirement in the Load event. Just a search for "Application.DoEvents in load" in google talks about bad experiences for many. I suggest you can explore handling your requirement in Shown event.
DoEvents in VB.NET should be actively avoided.
Have a look at DoEvents in .NET and Stop DoEvents and DoEvents is Evil to see why.
It is a common misconception that this does the same as the VB6 DoEvents. In almost all cases what you want to achieve can be done in some other way in .NET without the need to call DoEvents.
Your best bet is probably a BackgroundWorker object have a look at this example to get you started.
A point to note though is that you can't update controls on your form from a background worker without using a delegate - but that is another question...
You should use a BackgroundWorker component in order to do lots of background work without freezing up the user interface in VB.Net. Look at this MSDN tutorial How to run an operation in the background

VB 2010 - Stop a loop with a button

I am using VB 2010 , I have a loop that takes something like 5 minutes until it's finish.
I want to allow the user the option to stop it in the middle or whenever they want.
There's a button which starts the loop , and after I click it the loop starts running and the button is sort of "stuck".
I saw something called "background work" in the VB Toolbox, can it be a solution to my problem?
Start your loop in separate thread and set a flag which directs the action of the loop . Keep polling for this flag to see whether user wants to stop the thread inside the loop on thread . See BackgroundWorker
I think background worker would work, but a very simple solution if to create a boolean variable (visible in scope to your stop button and the logic loop) that controls stopping inside your loop. Your stop button would set the variable to true and the next time that code is hit, it would stop. you may need an application.doevents inside your loop at the end to allow the button's event to fire. This is not an ideal way, but is certainly simple.