Using Async Sub or Functions to update UI - vb.net

I'm learning on how to use Async and Await. I wrote this code to fetch a website and write it on a RichTextBox in every one second. The problem is, if there's a network slowdown, the fetching of the page slows down too and the UI freezes briefly. This is the code:
Imports System.Net
Imports System.IO
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Timer1.Start()
End Sub
Private Async Function FetchData(uri As String) As Task(Of String)
Using wb As New WebClient
Dim stream As New StreamReader(wb.OpenRead(uri))
Return Await stream.ReadToEndAsync
End Using
End Function
Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
RichTextBox1.Text = Await FetchData("http://someurl")
End Sub
End Class

Related

How can I interact with parallel page loaded from WebView2?

I have written some VB.Net code using WebView2 control to try to download a PDF file from a specific magazine.
My VB.Net code is following
Imports Microsoft.Web.WebView2.Core
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Call InitializeAsync()
End Sub
Async Sub InitializeAsync()
Await wv.EnsureCoreWebView2Async()
wv.CoreWebView2.Navigate("https://journal.cinetelerevue.sudinfo.be")
End Sub
Private Sub wv_NavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs) Handles wv.NavigationCompleted
Threading.Thread.Sleep(1000)
Call ClickOnPdfButton()
Threading.Thread.Sleep(1000)
End Sub
Async Sub ClickOnPdfButton()
Dim sButtonCmd = "document.getElementById('readPdfBtn').click();"
Dim task = Await wv.ExecuteScriptAsync(sButtonCmd)
End Sub
End Class
The first Navigate() method display correctly requested URL.
The Javascript document.getElementById('readPdfBtn').click(); method works also correclty. It open a NEW window because Javascript code linked to click() method do following action
var e = window.open("","pdf_view");
When program has run, I obtain following result
I have painted a red circle around PDF button in first Window.
My problem is that I need to continue to click on another PDF button contained in new Window to initiate PDF download.
How can I access it using wv WebView2 variable ?
In tasks manager, I can see that new Windows is attached to Extract-PDF-From-Web application that is the name of my VB.Net application.
To solve this issue, I have added an event handler in wv.NavigationCompleted event in which I have changed e.NewWindow property.
I have also try to set URI but without success.
The full VB.Net solution that works using Visual Studio 2022 is following
Imports Microsoft.Web.WebView2.Core
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Call InitializeAsync()
End Sub
Async Sub InitializeAsync()
Await wv.EnsureCoreWebView2Async()
wv.CoreWebView2.Navigate("https://journal.cinetelerevue.sudinfo.be")
End Sub
Public Sub NewWindowRequested(sender As Object, e As CoreWebView2NewWindowRequestedEventArgs)
e.Handled = True
Dim cwv As CoreWebView2 = sender
e.NewWindow = cwv
End Sub
Private Sub wv_NavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs) Handles wv.NavigationCompleted
AddHandler wv.CoreWebView2.NewWindowRequested, AddressOf Me.NewWindowRequested
Threading.Thread.Sleep(1000)
Call ClickOnPdfButton()
End Sub
Async Sub ClickOnPdfButton()
Dim sButtonCmd = "document.getElementById('readPdfBtn').click();"
Dim task = Await wv.ExecuteScriptAsync(sButtonCmd)
End Sub
End Class
After running this code, I obtain following result

Using threading in VB.net

I'm having trouble using threading in a VB.net application I'm writing. I have a really simple version here to illustrate the problem I'm having.
It works perfectly if I load my form and have the VNC control connect to my VNC server as a single thread application using this code:
Imports VncSharp
Imports System.Windows.Forms
Imports System.ComponentModel
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not RemoteDesktop1.IsConnected Then
Dim Host As String = "10.0.0.1"
RemoteDesktop1.Connect(Host)
End If
End Sub
End Class
In order to make the connection occur in the background, this is the code I've tried using the backgroundworker control. I followed the information in this article to handle referencing the controls on the form using an instance, but I still get an error when it tries to use the VNCSharp control (RemoteDesktop1) to connect to a VNC server at the line:
Instance.RemoteDesktop1.Connect(Host)
The error is:
System.InvalidOperationException: 'Cross-thread operation not valid: Control 'RemoteDesktop1' accessed from a thread other than the thread it was created on.'
Here's the code:
Imports VncSharp
Imports System.Windows.Forms
Imports System.ComponentModel
Public Class Form1
'Taken from https://stackoverflow.com/questions/29422339/update-control-on-main-form-via-background-worker-with-method-in-another-class-v
Private Shared _instance As Form1
Public ReadOnly Property Instance As Form1
Get
Return _instance
End Get
End Property
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
_instance = Me
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
System.Threading.Thread.Sleep(1000)
If Not Instance.RemoteDesktop1.IsConnected Then
Dim Host As String = "10.0.0.1"
Instance.RemoteDesktop1.Connect(Host)
'Connect()
End If
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
MsgBox("BG1 complete")
End Sub
End Class
I figured it out - one part of the code in the post I linked to on threading held the answer: I needed to use Invoke() to reference the form RemoteDesktop control:
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
System.Threading.Thread.Sleep(1000)
If Not Instance.RemoteDesktop1.IsConnected Then
Me.Instance.Invoke(Sub()
Dim Host As String = "10.61.41.7"
Me.RemoteDesktop1.Connect(Host)
End Sub)
End If
End Sub
instead of just putting Instance.RemoteDesktop1.Connect(Host)

Having problem in passing variables between two forms in vb.net

I'm almost a beginner of vb.net and I have to do some codes. I want to call a button click in form 2 from another class, i.e., form 1 and the problem is that the variables which defined as pubic, their values don't pass between two forms. My code simply presented as follows:
Public class form2
Public Property ResponseTime1 As String
Public Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
Responsetime2 = 20
End sub
End class
Pub class form1
Dim Resposetime as string
Dim z as new form2
Public Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
z.button1_click(sender, e)
ResponseTime = form2.ResponseTime2
MsgBox(ResponseTime) '' show nothing????????????
End sub
End class
I would be appreciated if someone helps me
The first problem I see is that Responsetime2 is never declared. A few spelling errors are further messing up the code. Even if I stick a Dim in front of Responsetime2 = 20 it is still not visible to from1. Just because a method is public doesn't mean that vaiables inside the method are visible. Even if it was visible, `Responsetime2 is an Integer and you have declared Responsetime as a String.
It is not a good idea to call an event procedure from another class. Event procedures are meant to respond to an event and are private by default. Make the code a separate public method and call it from both form2.button1_Click and form1.
Public Class form25
Public Property ResponseTime2 As Integer
Private Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
SetResponseTime2()
End Sub
Public Sub SetResponseTime2()
ResponseTime2 = 20
End Sub
End Class
Public Class form15
Dim Responsetime As Integer
Dim z As New form25
Public Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
z.SetResponseTime2()
Responsetime = z.ResponseTime2
MsgBox(Responsetime.ToString)
End Sub
End Class
Hi dear I have no idea why you made this Public Property ResponseTime1 As String most probably you have a reason for it but i am going to show for you how i would do it
Form1 Code
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
form2.show
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
MsgBox(Form2.ResponseTime)
End Sub
End Class
Form2 code
Public Class Form2
Public ResponseTime As String
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ResponseTime = "test"
End Sub
End Class
I tested this one and its working
Hope i could help to you

Progressbar freezing while the other form loading

I have 3 form ;
Form1 - Main Form
Form2 - Sub form ( contains progress bar and timer)
Form3 - Sub form with heavy contains which takes time for loading ( like parsing data from webpage and writing it to Datagridview at form load event)
I need to show form2 with a progress bar running while form3 is loading
I have following codes at Form1 ;
Me.Hide
Form2.Show()
Form3.Show()
codes from Form 2 ;
Public Class Form2
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LoadingTimer.Enabled = True
End Sub
Private Sub LoadingTimer_Tick(sender As Object, e As EventArgs) Handles LoadingTimer.Tick
If MyProgressBar.Value <= MyProgressBar.Maximum - 1 Then
MyProgressBar.Value += 10
End If
If MyProgressBar.Value = 100 Then
LoadingTimer.Enabled = False
Me.Close()
End If
If Label1.ForeColor = Color.LimeGreen Then
Label1.ForeColor = Color.White
Else
Label1.ForeColor = Color.LimeGreen
End If
End Sub
End Class
The problem is progress bar starting but freezing at the beginning while Form3 is loading
Any idea for solution?
If you're new to programming then this may be a bit confusing but the answer is to push the code from the Load event handler of Form3 into an Async method and await it. Your UI freezes because you're doing work synchronously on the UI thread. You need to either use a secondary thread explicitly or use Async/Await. This:
Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Do some work.
End Sub
would become this:
Private Async Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await DoWork()
End Sub
Private Async Function DoWork() As Task
Await Task.Run(Sub()
'Do some work.
End Sub).ConfigureAwait(False)
End Function
Actually, that's probably more complex than necessary and this should work fine:
Private Async Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await Task.Run(Sub()
'Do some work.
End Sub).ConfigureAwait(False)
End Sub
Having reread your question, what you probably need to do is have your async method be a function that retrieves and returns the data from the web page or whatever and then you load that data into your DataGridView synchronously afterwards, e.g.
Private Async Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DataGridView1.DataSource = Await GetDataAsync()
End Sub
Private Async Function GetDataAsync() As Task(Of DataTable)
Return Await Task.Run(Function()
Dim table As New DataTable
'Populate table here.
Return table
End Function).ConfigureAwait(False)
End Function
So the GetDataAsync method returns a Task(Of DataTable), i.e. a Task that asynchronously executes a function that returns a DataTable. In the Load event handler, you call that method and await the Task, which means that your code will wait until the Task has executed and returned its data but without blocking the UI thread as a synchronous call would do. The synchronous equivalent would be this:
Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DataGridView1.DataSource = GetData()
End Sub
Private Function GetData() As DataTable
Dim table As New DataTable
'Populate table here.
Return table
End Function
Try making the process asynchronous, it is in my understanding that the timer tick is already asynchronous but in the form1, you could use could have that code inside an Task
Me.Hide
Task.Run(Function() Form2.Show())
Form3.Show()
I never reached this far on vb.net since i started to program on c# but this should do the trick

form opening on load

My program was opening a different form to what I wanted it to. The answers solved it.
Basically I wanted to stop a form opening when the program started, but when it opened manually (on a button press), it updated the data. The second part of the problem has not been solved, but the first part has been.
You can try something like this:
Public Class HomeForm
Private WithEvents m_DataChangeForm As DataChangeForm
Private Sub HomeForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
m_DataChangeForm = New DataChangeForm()
m_DataChangeForm.Show()
End Sub
Private Sub OnDataSourceChanged(sender As Object, args As EventArgs) Handles m_DataChangeForm.OnDataSourceChanged
MessageBox.Show("Data source changed!")
End Sub
End Class
Public Class DataChangeForm
Inherits Form
Public Event OnDataSourceChanged(sender As Object, args As EventArgs)
Private WithEvents m_Button As Button
Public Sub New()
m_Button = New Button()
m_Button.Text = "Change"
m_Button.Parent = Me
End Sub
Public Sub buttonClick(sender As Object, args As EventArgs) Handles m_Button.Click
RaiseEvent OnDataSourceChanged(sender, args)
Me.Close()
End Sub
End Class
Reason your form is displayed before HomeForm is becaouse you call ShowDialog, it blocks until DataChangeForm is closed.
You should move your code from "Load" to "Shown" Event.
Private Sub Homefrm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
Using fp = New dataChangefrm(m_database)
If fp.ShowDialog() = DialogResult.OK Then
uwgHome.DataSource = Nothing
loadData()
End If
End Using
Me.Location = New Point(0, 0)
loadData()
End Sub
Please have a look on the Handle in the first line. It depends on your Project.