reread content of webbrowser window/wait for user input - vb.net

In my program I want to give 2 input-fields within a webbrowser-window a value and then wait for the user to click a login button. After this, I want the program to break out of the loop when the screen contents "Welcome!" (which it contains after logging in).
But now the problem: When the values for username and password are set, the do-loop starts instantly. So even if I click the login-button, the loop still contains the content of the webpage before the button was clicked and it will loop forever.
How can I solve this? I thought about two ways so far:
Reread the html code after button clicked so the "Welcome!" will be inside the code and then the loop will break, or
Wait for the user to press the login-button.
But in both ways I have no real idea how to solve it.
WebBrowserWindow.WebBrowser1.Navigate("[...]")
Do
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then
If WebBrowserWindow.WebBrowser1.Document.Body.InnerHtml.ToString.Contains("Login") Then Exit Do
End If
Application.DoEvents()
Loop
If WebBrowserWindow.WebBrowser1.Document.Body.InnerHtml.ToString.Contains("Login") Then
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("username").SetAttribute("value", sUser)
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("password").SetAttribute("value", sPass)
Application.DoEvents()
Dim DocumentCompletedHandler As WebBrowserDocumentCompletedEventHandler = Sub(dcsender As Object, dcargs As WebBrowserDocumentCompletedEventArgs)
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then
RemoveHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, DocumentCompletedHandler
'Put the code that should be executed when the user has logged in here.
MsgBox("it works")
End If
End Sub
AddHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, DocumentCompletedHandler
End If
WebBrowserWindow.Close()
Me.Close()
I noticed the problem when I tried to put the html code in a message box. It just contained the 'old' code.
Thanks in advance

The golden rule of WinForms is: NEVER, EVER use Application.DoEvents() to keep your UI responsive! If you need to use it then you are almost always doing something wrong (see: Keeping your UI Responsive and the Dangers of Application.DoEvents).
Heavy operations should never be done on the UI thread, but in a background thread. There are multiple ways of taking the work of the UI, for instance Tasks, Threads or using the BackgroundWorker.
HOWEVER in this case, you don't even need a loop. The WebBrowser has got a DocumentCompleted event that is raised every time a page (or an iframe inside the page) has loaded completely. Use that to know when to execute your code.
Having that said, here's how you'd migrate it to DocumentCompleted:
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("username").SetAttribute("value", sUser)
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("password").SetAttribute("value", sPass)
Dim DocumentCompletedHandler As WebBrowserDocumentCompletedEventHandler = _
Sub(dcsender As Object, dcargs As WebBrowserDocumentCompletedEventArgs)
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then
RemoveHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, DocumentCompletedHandler
'Put the code that should be executed when the user has logged in here.
End If
End Sub
AddHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, DocumentCompletedHandler
'Any code put here won't wait for the user to log in, it wil be executed pretty much immediately.
Here's a little test project: http://www.mydoomsite.com/sourcecodes/WebBrowser_WaitForUserInteraction.zip
Ultimately, your entire code can be changed to:
WebBrowserWindow.WebBrowser1.Navigate("[...]")
Dim FirstDocumentCompletedHandler As WebBrowserDocumentCompletedEventHandler = _
Sub()
'Check if:
' - The web browser has finished loading.
' - WebBrowser1.Document is not Null.
' - WebBrowser1.Document.Body is not Null.
' - WebBrowser1.Document.Body.InnerHtml is not Null.
' - WebBrowser1.Document.Body.InnerHtml contains "Login".
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete AndAlso _
WebBrowserWindow.WebBrowser1.Document IsNot Nothing AndAlso _
WebBrowserWindow.WebBrowser1.Document.Body IsNot Nothing AndAlso _
WebBrowserWindow.WebBrowser1.Document.Body.InnerHtml IsNot Nothing AndAlso _
WebBrowserWindow.WebBrowser1.Document.Body.InnerHtml.Contains("Login") Then
'The code put in here will execute when the page loads the first time, and the above conditions are met.
'We are at the login page. Enter the credentials.
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("username").SetAttribute("value", sUser)
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("password").SetAttribute("value", sPass)
Dim SecondDocumentCompletedHandler As WebBrowserDocumentCompletedEventHandler = _
Sub(dcsender As Object, dcargs As WebBrowserDocumentCompletedEventArgs)
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then
RemoveHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, SecondDocumentCompletedHandler
'The code put in here will be executed after the user has pressed "Login".
MsgBox("it works")
WebBrowserWindow.Close()
Me.Close()
End If
End Sub
AddHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, SecondDocumentCompletedHandler 'Add the second DocumentCompleted event handler.
RemoveHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, FirstDocumentCompletedHandler 'Remove the first DocumentCompleted event handler.
End If
End Sub
AddHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, FirstDocumentCompletedHandler
'Again, any code put here will execute almost immediately, thus NOT waiting for the page to load.
Alternatively
...if you think it's too messy having lambdas everywhere, you can fall back to using regular methods:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
WebBrowserWindow.WebBrowser1.Navigate("[...]")
AddHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, AddressOf WebBrowserWindow_FirstDocumentCompleted
End Sub
Private Sub WebBrowserWindow_FirstDocumentCompleted(sender As System.Object, e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
'Check if:
' - The web browser has finished loading.
' - WebBrowser1.Document is not Null.
' - WebBrowser1.Document.Body is not Null.
' - WebBrowser1.Document.Body.InnerHtml is not Null.
' - WebBrowser1.Document.Body.InnerHtml contains "Login".
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete AndAlso _
WebBrowserWindow.WebBrowser1.Document IsNot Nothing AndAlso _
WebBrowserWindow.WebBrowser1.Document.Body IsNot Nothing AndAlso _
WebBrowserWindow.WebBrowser1.Document.Body.InnerHtml IsNot Nothing AndAlso _
WebBrowserWindow.WebBrowser1.Document.Body.InnerHtml.Contains("Login") Then
'We are at the login page. Enter the credentials.
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("username").SetAttribute("value", sUser)
WebBrowserWindow.WebBrowser1.Document.Window.Frames(2).Document.GetElementById("password").SetAttribute("value", sPass)
AddHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, AddressOf WebBrowserWindow_SecondDocumentCompleted 'Add the second DocumentCompleted event handler.
RemoveHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, AddressOf WebBrowserWindow_FirstDocumentCompleted 'Remove the first DocumentCompleted event handler.
End If
End Sub
Private Sub WebBrowserWindow_SecondDocumentCompleted(sender As System.Object, e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
If WebBrowserWindow.WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then
RemoveHandler WebBrowserWindow.WebBrowser1.DocumentCompleted, AddressOf WebBrowserWindow_SecondDocumentCompleted
'Put the code that should be executed when the user has logged in here.
MsgBox("it works")
WebBrowserWindow.Close()
Me.Close()
End If
End Sub

Related

Running a BackGroundWorker more than once

I wonder if you can help?, please bear in mid I’m an absolute novice.
I trying to update a program I made a few years ago for booking in serial numbers into a CRM application.
Currently it runs the following command for each of the 100 textboxes and has worked a treat booking in more than 81000 serial numbers.
If TextBox1.Text.Length > 1 Then
Clipboard.SetText(TextBox1.Text)
RetBat1 = Shell("C:\Windows\BookIN.exe", , True)
Endif
The new version of the app I’ve added a listbox1 with the serial numbers in, I’m then running the below For Each loop.
The For Each loop copies each Item into the clipboard and the BookIN.exe tabs to the right location in the CRM and pastes the information, then clicks a button in the CRM for a new Line and then runs again. This works fine, but I want to add a stop button or a stop checkbox.
For Each items In ListBox1.Items
Clipboard.SetText(items)
RetBat1 = Shell("C:\Windows\BookIN.exe", , True)
Next
I have tried adding the Retbat1 to a backgroundworker, which checks if Checkbox1.checked then exit the for each loop.
The first serial number works, but when the backgroundworker runs more than once I get the following error.
If CheckBox1.Checked = True Then
BackgroundWorker1.CancelAsync()
Else
Dim RetBat1 As String
RetBat1 = Shell("C:\Windows\BookIN.exe", , True)
End If
System.InvalidOperationException: 'This BackgroundWorker is currently busy and cannot run multiple tasks concurrently.'
Sorry if this makes not sense, thanks James
The way it would go is that you would run the BackgroundWorker and have your loop in the DoWork event handler and check whether a cancellation has been requested within that loop. As you've described it, you would then handle the CheckedChanged even of your CheckBox and request the cancellation when the event is raised. I would not use a CheckBox though, because that implies that you can uncheck it to uncancel the work. I would suggest using a Button and handling its Click event, e.g.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'We must get the data from the UI here and pass it into the background thread.
Dim items = ListBox1.Items.Cast(Of String)().ToArray()
'Start the background work and pass in the data.
BackgroundWorker1.RunWorkerAsync(items)
'Enable the Cancel button.
Button2.Enabled = True
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'Disable the Cancel button so it cannot used again.
Button2.Enabled = False
'Request that background processing be cancelled.
BackgroundWorker1.CancelAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Get the data that was passed in.
Dim items = DirectCast(e.Argument, String())
'Process each item.
For Each item In items
'Check whether processing has been cancelled.
If BackgroundWorker1.CancellationPending Then
'Cancel processing.
e.Cancel = True
Exit For
End If
'Process the current item here.
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If e.Cancelled Then
MessageBox.Show("The background operation was cancelled")
Else
'Disable the Cancel button.
Button2.Enabled = False
MessageBox.Show("The background operation completed successfully")
End If
End Sub
The Cancel Button should be disabled by default and notice that this code ensures that it is only enabled when background processing is in progress. Note that the other Button probably ought to be disabled while the processing is in progress too. If you don't do that, at least check IsBusy on the BackgroundWorker. The visual feedback to the user is better though.

webbrowser auto navigation event

I am using webbrowser to navigate to a website then automating the login. Everything works perfectly up until the point of the comment "Navigating Event" After entering one credential it will login and navigate to another website. After the event none of the code will work as it is not picking up the new site. I am using a waitforpageload() function to let me know when it is completed loading however when I check the url it is still pointing to the original site. Any ideas why it would be doing this and how to possibly get around it?
Private Property pageready As Boolean = False
webBrowser1.Navigate("https://www.lamedicaid.com/sprovweb1/provider_login/provider_login.asp")
waitforpageload()
Dim allelements As HtmlElementCollection = webBrowser1.Document.All
For Each webpageelement As HtmlElement In allelements
'NPI #
If webpageelement.GetAttribute("name") = "Provider_Id" Then
webpageelement.SetAttribute("value", "xxxxxx")
End If
'Clicking enter to input NPI
If webpageelement.GetAttribute("name") = "submit1" Then
webpageelement.InvokeMember("focus")
webpageelement.InvokeMember("click")
waitforpageload()
End If
'Navigation event happens here
'Entering username
If webpageelement.GetAttribute("name") = "Login_Id" Then
webpageelement.SetAttribute("value", "xxxxxxx")
End If
'Entering Password
If webpageelement.GetAttribute("name") = "Password" Then
webpageelement.SetAttribute("value", "xxxxxxxxx")
End If
'logging in
If webpageelement.GetAttribute("name") = "submit_button" Then
webpageelement.InvokeMember("focus")
webpageelement.InvokeMember("click")
waitforpageload()
End If
#Region "Page Loading Functions"
Private Sub waitforpageload()
AddHandler webBrowser1.DocumentCompleted, New WebBrowserDocumentCompletedEventHandler(AddressOf PageWaiter)
While Not pageready
Application.DoEvents()
End While
pageready = False
End Sub
Private Sub PageWaiter(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
If webBrowser1.ReadyState = WebBrowserReadyState.Complete Then
pageready = True
RemoveHandler webBrowser1.DocumentCompleted, New WebBrowserDocumentCompletedEventHandler(AddressOf PageWaiter)
End If
End Sub
#End Region
Please, for the love of god, get rid of that waitforpageload() function! Using Application.DoEvents() is BAD PRACTICE and in a loop like that, will utilize 100% of your CPU!
The person who originally wrote that function (it's from another Stack Overflow post) clearly didn't know what he/she was doing at the time. Using Application.DoEvents() creates more problems than it solves, and should NEVER be used in anyone's code (it exists mostly because it is used by internal methods).
Refer to: Keeping your UI Responsive and the Dangers of Application.DoEvents for more info.
The WebBrowser has a dedicated DocumentCompleted event that is raised every time a page (or part of a page, such as an iframe) has been completely loaded.
To make sure that the page really is fully loaded, subscribe to the DocumentCompleted event and check if the ReadyState property is equal to WebBrowserReadyState.Complete.
To be able to run code more "dynamically" when the DocumentCompleted event is raised you can utilize lambda expressions as a way of creating inline methods.
In your case they can be used like this:
'Second step (these must always be in descending order since the first step must be able to reference the second, and so on).
Dim credentialHandler As WebBrowserDocumentCompletedEventHandler = _
Sub(wsender As Object, we As WebBrowserDocumentCompletedEventArgs)
'If the WebBrowser HASN'T finished loading, do not continue.
If webBrowser1.ReadyState <> WebBrowserReadyState.Complete Then Return
'Remove the event handler to avoid this code being called twice.
RemoveHandler webBrowser1.DocumentCompleted, credentialHandler
'Entering username
If webpageelement.GetAttribute("name") = "Login_Id" Then
webpageelement.SetAttribute("value", "xxxxxxx")
End If
'Entering Password
If webpageelement.GetAttribute("name") = "Password" Then
webpageelement.SetAttribute("value", "xxxxxxxxx")
End If
'logging in
If webpageelement.GetAttribute("name") = "submit_button" Then
webpageelement.InvokeMember("focus")
webpageelement.InvokeMember("click")
End If
End Sub
'First step.
Dim loginHandler As WebBrowserDocumentCompletedEventHandler = _
Sub(wsender As Object, we As WebBrowserDocumentCompletedEventArgs)
'If the WebBrowser hasn't finished loading, do not continue.
If webBrowser1.ReadyState <> WebBrowserReadyState.Complete Then Return
'Remove the event handler to avoid this code being called twice.
RemoveHandler webBrowser1.DocumentCompleted, loginHandler
Dim allelements As HtmlElementCollection = webBrowser1.Document.All
For Each webpageelement As HtmlElement In allelements
'NPI #
If webpageelement.GetAttribute("name") = "Provider_Id" Then
webpageelement.SetAttribute("value", "xxxxxx")
'-- Why would you even wait in here?? There's no reason for you to wait after only changing an attribute since nothing is loaded from the internet.
End If
'Clicking enter to input NPI
If webpageelement.GetAttribute("name") = "submit1" Then
'Adding the event handler performing our next step.
AddHandler webBrowser1.DocumentCompleted, credentialHandler
webpageelement.InvokeMember("focus")
webpageelement.InvokeMember("click")
End If
Next
End Sub
'Add the event handler performing our first step.
AddHandler webBrowser1.DocumentCompleted, loginHandler
webBrowser1.Navigate("https://www.lamedicaid.com/sprovweb1/provider_login/provider_login.asp")
Now every time you need to wait for the document/website to be fully loaded, just declare a new lambda and add it as an event handler to DocumentCompleted:
Dim thirdStepHandler As WebBrowserDocumentCompletedEventHandler = _
Sub(wsender As Object, we As WebBrowserDocumentCompletedEventArgs)
'If the WebBrowser hasn't finished loading, do not continue.
If webBrowser1.ReadyState <> WebBrowserReadyState.Complete Then Return
'Remove the event handler to avoid this code being called twice.
RemoveHandler webBrowser1.DocumentCompleted, thirdStepHandler
'Your goes code here...
End Sub
'To wait until performing the next step (be sure to do this BEFORE navigating):
AddHandler webBrowser1.DocumentCompleted, thirdStepHandler

How can I use the .Show() method in such a way the user can't click other forms already opened in background?

Now i'm using a'jobdone' flag and the following behaviour (that it looks quite horrible to me...):
Dim NewLoginForm As New LoginClass
LoginClass.jobdone = False
NewLoginForm.Show()
While (LoginClass.jobdone = False)
Application.DoEvents()
End While
NewLoginForm.Close()
If I try to use ShowDialog() the behaviour is better but it's a problem to manage all the opening and closing windows (if the forms are many) and I notice that all the background already opened forms close themselves either if one ShowDialog() form is closed...
Thanks
Ran a quick test and this worked perfectly:
Public Sub ForceOpen(ByRef frm As Form)
frm.Show()
For Each otherForm As Form In Application.OpenForms
If otherForm Is frm Then
AddHandler frm.FormClosing, AddressOf BlockFormClosing
Else
otherForm.Enabled = False
End If
Next
End Sub
Public Sub BlockFormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs)
e.Cancel = True
End Sub
Public Sub EnableOpenForms()
For Each frm As Form In Application.OpenForms
frm.Enabled = True
RemoveHandler frm.FormClosing, AddressOf BlockFormClosing
Next
End Sub
The calling form will open the stay-open form with ForceOpen(FormStayOpen). When the stay-open form's conditions for allowing the user to close it have been satisfied, have it call EnableOpenForms().

vb.net Detect if a link is clicked in Webbrowser control

I have a Webbrowser control on a standard windows form in VB.NET 2005. I just want to detect when someone clicks a link inside the Webbrowser control it just tells me what they clicked on, or where its trying to go, then cancel the process.
I tried putting..
MsgBox(e.Url)
e.Cancel = True
Inside of the WebBrowser1_Navigating EVENT, but that does nothing. Can anyone help?
This was the problem:
MsgBox(e.Url)
Try this:
MsgBox(e.Url.ToString())
Private Sub WebBrowser1_Navigating(ByVal sender As Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles WebBrowser1.Navigating
If MsgBox("You are trying to go to:" & vbCr & e.Url.ToString() & vbCr & "Cancel Navigate?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
e.Cancel = True
End If
End Sub
you could try something like adding a handler for each link:
For Each htmlEle As HtmlElement In Webbrowser1.document.Links
addhandler htmlElec.click, addressof YourSub
Next
private sub YourSub()
'do what you want here
end sub

Confusing System.NullReferenceException error on a defined variable - VB

Alright so I am new here so I apologize in advance if I post incorrectly or am a little vague. My problem is that I run into a NullReferenceException when I try to run my code but while debugging and hovering my mouse over the problematic variable, I do indeed see the value of the variable.
Here is the VB code that I am working with:
Private Sub Login_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles login.Click
status.Text = "Connecting...."
WebBrowser2.Navigate("http://*****.com/?op=login")
WebBrowser2.Document.GetElementById("loginUsername").InnerText = username.Text
WebBrowser2.Document.GetElementById("loginPassword").InnerText = password.Text
WebBrowser2.Document.GetElementById("loginSubmit").InvokeMember("click")
End Sub
Here is the snapshot of what is going on:
------------ EDIT : SOLUTION -------------------
WebBrowser2.Url = New Uri("http://*****.com/?op=login")
WaitForPageLoad() ' <---------- ADDED NEW FUNCTION TO WAIT FOR PAGE LOAD
WebBrowser2.Document.GetElementById("loginUsername").InnerText = username.Text
WebBrowser2.Document.GetElementById("loginPassword").InnerText = password.Text
WebBrowser2.Document.GetElementById("loginSubmit").InvokeMember("click")
status.Text = "Completed"
So I created a new function (credits go to BGM in How to wait until WebBrowser is completely loaded in VB.NET?) called WaitForPageLoad() which essentially loops through a check for the page to be ready and then once it is, is kills the handler so the login is successful and the page does not loop. Here is the WaitForPageLoad():
Private Property pageready As Boolean = False
Private Sub WaitForPageLoad()
AddHandler WebBrowser2.DocumentCompleted, New WebBrowserDocumentCompletedEventHandler(AddressOf PageWaiter)
While Not pageready
Application.DoEvents()
End While
pageready = False
End Sub
Private Sub PageWaiter(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
If WebBrowser2.ReadyState = WebBrowserReadyState.Complete Then
pageready = True
RemoveHandler WebBrowser2.DocumentCompleted, New WebBrowserDocumentCompletedEventHandler(AddressOf PageWaiter)
End If
End Sub
WebBrowser2.Navigate takes some time to load the document, but is asynchronous. That means that the next code gets executed before the document finishes loading.
Consequently, in the next line, GetElementById cannot yet find the target element and returns Nothing. To prevent this, you cannot execute code after calling Navigate – instead, you need to create an event handler for the event that is fired once the document finished loading, and execute the code there. – This is the DocumentCompleted event.
On that line in particular...
Document could be null
The result of GetElementById("loginUsername") could be null.
Why do you think that username is null?
I bet that WebBrowser2.Document.GetElementById("loginUsername") returns null.
The other possibility is Document to be null.