I don't know enough about VB.Net yet to use the richer HttpWebRequest class, so I figured I'd use the simpler WebClient class to download web pages asynchronously (to avoid freezing the UI).
However, how can the asynchronous event handler actually return the web page to the calling routine?
Imports System.Net
Public Class Form1
Private Shared Sub DownloadStringCallback2(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs)
If e.Cancelled = False AndAlso e.Error Is Nothing Then
Dim textString As String = CStr(e.Result)
'HERE : How to return textString to the calling routine?
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim client As WebClient = New WebClient()
AddHandler client.DownloadStringCompleted, AddressOf DownloadStringCallback2
Dim uri As Uri = New Uri("http://www.google.com")
client.DownloadStringAsync(uri)
'HERE : how to get web page back from callback function?
End Sub
End Class
Thank you.
Edit: I added a global, shared variable and a While/DoEvents/EndWhile, but there's got to be a cleaner way to do this :-/
Public Class Form1
Shared page As String
Public Shared Sub AlertStringDownloaded(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs)
' If the string request went as planned and wasn't cancelled:
If e.Cancelled = False AndAlso e.Error Is Nothing Then
page = CStr(e.Result)
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim wc As New WebClient
AddHandler wc.DownloadStringCompleted, AddressOf AlertStringDownloaded
page = Nothing
wc.DownloadStringAsync(New Uri("http://www.google.com"))
'Better way to wait until page has been filled?
While page Is Nothing
Application.DoEvents()
End While
RichTextBox1.Text = page
End Sub
End Class
You can set the RichTextBox1.Text directly in the completed handler if you make the handler function an instance method instead of shared.
Public Class Form1
Private Sub AlertStringDownloaded(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs)
' If the string request went as planned and wasn't cancelled:
If e.Cancelled = False AndAlso e.Error Is Nothing Then
RichTextBox1.Text = CStr(e.Result)
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim wc As New WebClient
AddHandler wc.DownloadStringCompleted, AddressOf AlertStringDownloaded
page = Nothing
wc.DownloadStringAsync(New Uri("http://www.google.com"))
End Sub
End Class
You haven't found a clean way. Close your form while the download is taking place and behold the kaboom you'll get. You'll have to at least set the form's Enabled property to False.
Look at the BackgroundWorker class to do this cleanly.
Related
I'm creating a program dashboard and one feature is that the user is automatically logged into a website site using stored credentials in the program (no need to open chrome or FF).
In the program, await task delay is working, I see the username and password fields populate before submission (click), but when it tries to submit, the browser, built into the form, acts like the page is empty and no credentials have been entered? I should mention that I can see the username and password entered in the form, but the page acts like nothing has been entered. What am I doing wrong here?
Side note: The button on the site we're connecting to doesn't have an element ID, only a type is shown...hence the workaround for Invokemember("Click")
Any help is appreciated.
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Label3.Visible = False
End Sub
Private Function login_thesite() As Task
WebBrowser1.Document.GetElementById("username").SetAttribute("value", "Username")
WebBrowser1.Document.GetElementById("Password").SetAttribute("value", "Password")
Dim allelements As HtmlElementCollection = WebBrowser1.Document.All
For Each webpageelement As HtmlElement In allelements
If webpageelement.GetAttribute("type") = "submit" Then
webpageelement.InvokeMember("click")
End If
Next
End Function
Private Property pageready As Boolean = False
#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
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If CheckBox1.Checked = True Then
login_thesite()
WaitForPageLoad()
End If
End Sub
Private Sub CheckBox1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CheckBox1.CheckedChanged
TextBox1.Text = ""
TextBox2.Text = ""
Label3.Visible = True
WebBrowser1.Navigate("https://thesite.com/#/login")
WaitForPageLoad()
End Sub
End Class
You don't need any async procedure here. The WebBrowser.DocumentCompleted event is already invoked asynchronously. DoEvents() is equally useless if not disruptive.
You just need to subscribe to the DocumentCompleted event and call the Navigate method to let the WebBrowser load the remote Html resource.
When the HtmlDocument is finally loaded, the WebBrowser will signal its completion setting its state to WebBrowserReadyState.Complete.
About the Html Input Element and Forms:
here, the code is supposing that there's only one Form in that HtmlDocument.
It may be so, but it may be not. A Html Document can have more than one Form and each Frame can have its own Document. IFrames will have one each for sure.
Read the notes in this answer (C# code, but you just need the notes) for more information on how to handle multiple Frames/IFrames
Button1 will wire up the DocumentCompleted event and call Navigate().
When the Document is completed, the code in the event handler will run and perform the LogIn procedure.
The event handler is then removed, since it has complelted its task and you still need to use the WebBrowser for other purposes.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Button1.Enabled = False
WebSiteLogIn()
End Sub
Private Sub WebSiteLogIn()
AddHandler WebBrowser1.DocumentCompleted, AddressOf PageWaiter
WebBrowser1.Navigate("https://thesite.com/#/login")
End Sub
Private Sub PageWaiter(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
If WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then
WebBrowser1.Document.GetElementById("username").SetAttribute("value", "Username")
WebBrowser1.Document.GetElementById("Password").SetAttribute("value", "Password")
Dim allInputElements = WebBrowser1.Document.Body.All.
Cast(Of HtmlElement).Where(Function(h) h.TagName.Equals("INPUT")).ToList()
For Each element As HtmlElement In allInputElements
If element.GetAttribute("type").ToUpper().Equals("SUBMIT") Then
element.InvokeMember("click")
End If
Next
RemoveHandler WebBrowser1.DocumentCompleted, AddressOf PageWaiter
Button1.Enabled = True
End If
End Sub
I have a function that gets User ID from USB badge reader, used to log in an application.
when I run the app, the log in window does not appear until I swipe the tag.
I need to know if it`s possible to load the windows, then to start running the function that gets the data from the USB.
Thanks :)
Private Sub SerialPort1_DataReceived()
'Threading.Thread.SpinWait(1000)
OpenPort()
If SerialPort1.IsOpen() Then
byteEnd = SerialPort1.NewLine.ToCharArray
'read entire string until .Newline
readBuffer = SerialPort1.ReadLine()
readBuffer = readBuffer.Remove(0, 1)
readBuffer = readBuffer.Remove(8, 1)
WWIDTextBox.AppendText(readBuffer)
End If
End Sub
Private Sub Form1_Activated(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Activated
SerialPort1_DataReceived()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'SerialPort1_DataReceived()
End Sub
The problem is that you are calling the ReadLine method, which is a blocking (synchronous) method. In other words, when you call it, the method does not return the value until it has the value to return. Because of that, it stops execution on the current thread until a complete line is read (when the badge is swiped). Since you are on the UI thread when you call it, it will lock up the UI until the badge is swiped.
Instead of calling your SerialPort1_DataReceived method from the UI thread, you can do the work from a different thread. The easiest way to do that is to drag a BackgroundWorker component onto your form in the designer. Then you can add code like this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
OpenPort()
If SerialPort1.IsOpen() Then
byteEnd = SerialPort1.NewLine.ToCharArray
Dim readBuffer As String = SerialPort1.ReadLine()
readBuffer = readBuffer.Remove(0, 1)
readBuffer = readBuffer.Remove(8, 1)
e.Result = readBuffer
End If
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
WWIDTextBox.AppendText(CStr(e.Result))
End Sub
Working on VS2013, I came across the same issue, I needed to to a datagridview refresh (colors in the gridrows). This worked for me.
Sub MyForm_VisibleChanged(sender As Object, e As EventArgs) Handles Me.VisibleChanged
If Me.Visible Then
'do action...
End If
End Sub
Try Form Activated Event
Private Sub Form1_Activated(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Activated
'Call your function here
End Sub
It call the function After the Form Loads...
Private Sub loadCombo()
Dim sqlconn As New OleDb.OleDbConnection
Dim connString As String
connString = ""
Dim access As String
access = "select slno from atable"
Dim DataTab As New DataTable
Dim DataAdap As New OleDbDataAdapter(access, connString)
DataAdap.Fill(DataTab)
ComboBox1.DataSource = DataTab
ComboBox1.DisplayMember = "slno"
End Sub
I am having a little trouble understanding how I should be utilizing the Dispatcher to help me solve my problem of accessing a text box from a different thread. What I am trying to achieve is getting the thread to append to a chat box once it receives data form the server.
Public Class ChatScreen
Public client As Client
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
client = Application.Current.Properties("Client")
Me.Title = "ChitChat - " & client.Name
txtMessage.Focus()
Dim serverHandler As New ServerHandler(client.clientSocket, client.networkStream, txtChat)
End Sub
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSend.Click
client.SendMessage(txtMessage.Text)
End Sub
Private Sub Window_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles MyBase.KeyDown
If e.Key = Key.Enter Then
client.SendMessage(txtMessage.Text)
End If
End Sub
Public Sub AppendToChat(ByVal message As String)
txtChat.AppendText(">> " & message)
End Sub
Public Class ServerHandler
Dim clientSocket As TcpClient
Public networkStream As NetworkStream
Dim thread As Thread
Public Sub New(ByVal clientSocket As TcpClient, ByVal networkStream As NetworkStream)
Me.clientSocket = clientSocket
Me.networkStream = networkStream
thread = New Thread(AddressOf ListenForServer)
thread.Start()
End Sub
Public Sub ListenForServer()
Dim bytesFrom(10024) As Byte
Dim message As String
While True
networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
message = System.Text.Encoding.ASCII.GetString(bytesFrom)
message = message.Substring(0, message.IndexOf("$"))
'AppendToChat <--- This is where I would like to append the message to the text box
End While
End Sub
End Class
End Class
You can use SynchronizationContext to do this,
Store UI tread context in a variable like this
Private syncContext As SynchronizationContext
Private Sub frmClient_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
syncContext = AsyncOperationManager.SynchronizationContext
End Sub
Now create a procedure to execute on main UI thread like this
Private Sub AddTextBox()
‘Do whatever you want you are in UI thread here
End Sub
From you background thread post request on UI thread like this
syncContext.Post(New SendOrPostCallback(AddressOf AddTextBox), Nothing)
you can even pass arguments also
Private Sub AddTextBox(ByVal argument As Object)
‘Do whatever you want you are in UI thread here
End Sub
.....
syncContext.Post(New SendOrPostCallback(AddressOf AddTextBox), objToPass)
A few weeks ago I wrote a wrapper for the ServiceController control to enhance and streamline the base ServiceController. One of the changes I made was to add a monitoring component using the System.Threading.Timer object. On any change of status, an event is raised to the parent class. The actual monitoring works fine, but when the event is handled in the main form, my program abruptly ends - no exceptions, no warning, it just quits. Here's a skeleton version of the control:
Public Class EnhancedServiceController
Inherits ServiceController
Public Event Stopped(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event Started(ByVal sender As Object, ByVal e As System.EventArgs)
Private _LastStatus As System.ServiceProcess.ServiceControllerStatus
Private serviceCheckTimer as System.Threading.Timer
Private serviceCheckTimerDelegate as System.Threading.TimerCallback
...
Private Sub StartMonitor()
MyBase.Refresh()
_LastStatus = MyBase.Status
serviceCheckTimerDelegate = New System.Threading.TimerCallback(AddressOf CheckStatus)
serviceCheckTimer = New System.Threading.Timer(serviceCheckTimerDelegate, Nothing, 0, 60*1000)
End Sub
Private Sub CheckStatus()
MyBase.Refresh()
Dim s As Integer = MyBase.Status
Select Case s
Case ServiceControllerStatus.Stopped
If Not s = _LastStatus Then
RaiseEvent Stopped(Me, New System.EventArgs)
End If
Case ServiceControllerStatus.Running
If Not s = _LastStatus Then
RaiseEvent Started(Me, New System.EventArgs)
End If
End Select
_LastStatus = s
End Sub
End Class
And the form:
Public Class Form1
Private Sub ServiceStarted(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Started
Me.TextBox1.Text = "STARTED"
End Sub
Private Sub ServiceStopped(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Stopped
Me.TextBox1.Text = "STOPPED"
End Sub
End Class
If I had to guess, I'd say that there's some sort of thread problem, but I'm not sure how to handle that in the form. Any ideas?
IF it is a threading issue then you are probably trying to update the UI from a non-UI thread.
So something like this should solve that...
Private Delegate Sub UpdateTextBoxDelegate(byval tText as String)
Private Sub UpdateTextBox(byval tText As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), tText)
Exit Sub
End If
TextBox1.Text = tText
End Sub
Private Sub ServiceStarted(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Started
UpdateTextBox ("STARTED")
End Sub
Private Sub ServiceStopped(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Stopped
UpdateTextBox("STOPPED")
End Sub
Imports System.Net
Public Class DownloadStuff
Dim downloader As New WebClient()
Private Sub Progress_Validated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Progress.Validated
AddHandler downloader.DownloadProgressChanged, AddressOf DownloadChangedHandler
Dim uri As New Uri("http://www.example.com/example.txt")
downloader.DownloadFileAsync(uri, "C:\example.txt")
End Sub
Private Sub DownloadChangedHandler(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
Progress.Maximum = CInt(e.TotalBytesToReceive)
Progress.Value = CInt(e.BytesReceived)
Application.DoEvents()
End Sub
End Class
This is my code, but the DownloadProgressChanged event is NEVER fired. (I am using an example URL here, but it is the same basic stuff)
What am I doing wrong? Progress is a ProgressBar.
This is in VB.net.
On MSDN, they mentioned something about overriding GetWebRequest, but I have no idea how to do that or what to do.
UPDATE: Still no progress, I simply cannot figure out how to make the handler fire.
Try this:
Sub Main()
Dim client As WebClient = New WebClient()
AddHandler client.DownloadProgressChanged, AddressOf DownloadProgressCallback
client.DownloadFileAsync(New Uri("..."), "data.txt")
End Sub
Private Sub DownloadProgressCallback( _
ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
Console.WriteLine(e.ProgressPercentage)
End Sub
Everytime I see AddHandler and Handles I feel dizzy.