How to make WebClient's DownloadString perform asynchronously? - vb.net

I created a simple hello world program to download data from the web.
Private Sub cmdSurf_Click(sender As Object, e As EventArgs) Handles cmdSurf.Click
Dim wb As New System.Net.WebClient
Dim uri1 = New Uri(TextBox1.Text)
Dim str = wb.DownloadString(uri1)
TextBox2.Text = str
End Sub
It's pretty simple right. I use a WebClient object to download a string syncrhonously and then I display the result in a TextBox.
Now, I want to do so asynchronously.
Basically, after I download the URI I do something else.
Then after the download is finished, I am doing what it should be.
So I did
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim wb As New System.Net.WebClient
Dim uri1 = New Uri(TextBox1.Text)
wb.DownloadStringAsync(uri1)
TextBox2.Text = ""
End Sub
It turns out DownloadStringAsync(uri1) is a Sub, so it doesn't return anything.
So, well, what should be displayed in TextBox2 then? What am I missing?
Update: I realized that I should have used DownloadStringAsyncTask().
So I did this:
Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Await downloadStringAndAssignText()
TextBox2.Text = "I am downloading this. This text will change once the download is finished"
End Sub
Private Async Function downloadStringAndAssignText() As Task
Dim wb As New System.Net.WebClient
Dim uri1 = New Uri(TextBox1.Text)
Dim str = Await wb.DownloadStringTaskAsync(uri1)
TextBox2.Text = str
End Function
This is almost correct.
The thing is I want to do this properly so that
TextBox2.Text = "I am downloading this. This text will change once..."
is called BEFORE wb.DownloadStringTaskAsync(uri1) is finished.
Also I do not want warning. So how exactly should I do that?

You can set the text of your TextBox before you call the async method that downloads the string. When Await DownloadStringAndAssignText() is called, control will return to the calling Thread (the UI Thread), so the TextBox.Text is set and shown.
When the async method returns, you can assign to the same TextBox the returned string value.
Your method should return a value, the string it downloaded, not assign a value to a UI element that doesn't belong to this method (its responsibility is to download a string): here the return type is set to Task(Of String).
You can directly assign the return value of the async method to the same TextBox.Text property.
The WebClient object should be declared with a Using statement, so it's disposed of when the method completes.
You can directly return the result of WebClient.DownloadStringTaskAsync(), since this method returns a string, the same return type of your method.
Imports System.Net
Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
TextBox2.Text = "I am downloading..."
TextBox2.Text = Await DownloadStringAndAssignText(TextBox1.Text)
End Sub
Private Async Function DownloadStringAndAssignText(url As String) As Task(Of String)
Using wb As New WebClient()
Return Await wb.DownloadStringTaskAsync(New Uri(url))
End Using
End Function

Related

How to save checkbox value in xml using vb.net

I know, maybe this is a question that comes up quite often, but I haven't been able to work on it.
How can I save this checkbox values in an XML file using VB.Net ?
Private Sub ChkVul_Click(sender As Object, e As EventArgs) Handles ChkVul.Click
If ChkVul.Checked = True Then
Me.pnlInsert.Visible = True
Else
Me.pnlInsert.Visible = False
End If
End Sub
Before I provide you with an answer, I wanted to recommend a slight code change. Since you are setting the Boolean value of pnlInsert.Visible based on a condition, which itself returns a Boolean value, simply get rid of the conditional check in the first place:
pnlInsert.Visible = ChkVul.Checked
Now to your question. What you are essentially asking is how to write a value to an XML file. Something to consider is that XML is only a markup language. At the end of the day, an XML file is simply a file that contains formatted text.
If you do not already have an XML file to read from, simply create a new instance of a XDocument (documentation). If you do have an XML file, then create a new instance of a XDocument by calling the static XDocument.Load method (documentation). Here is a function that takes in a file location and attempts to load a XDocument, if it is unable to then it returns a blank XDocument with a single <root> element:
Private Function LoadOrCreateXml(filename As String) As XDocument
Dim document = New XDocument()
document.Add(New XElement("root"))
Try
If (Not String.IsNullOrWhiteSpace(filename) AndAlso IO.File.Exists(filename)) Then
document = XDocument.Load(filename)
End If
Catch ex As Exception
' for the sake of this example, just silently fail
End Try
Return document
End Function
Now that you have an XDocument, it is just a matter of writing the value. You did not provide any details as to where the value should go or what the tag name should be, so I am going to assume that it should be a child of the <root /> element and the value would look something like this: <ChkVul>true/false</ChkVul>.
To do this, we will need to get the <root /> element by calling the Element method (documentation) on the XDocument to get the element and then call the Add method (documentation) on the resulting XElement to add our node with the value:
Dim document = LoadOrCreateXml("my-xml-file.txt")
document.Element("root").Add(New XElement("ChkVul", ChkVul.Checked))
The final piece of all this is to write the in-memory XDocument back to the file. You can leverage the XDocument.Save method (documentation):
Dim filename = "my-xml-file.txt"
Dim document = LoadOrCreateXml(filename)
document.Element("root").Add(New XElement("ChkVul", ChkVul.Checked))
document.Save(filename)
Maybe this is what you have in mind. I tested this with other controls,
Public Class Form1
Private SavePath As String = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"MyControls.xml") 'some valid path <-----------<<<
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
SaveCheckedState(ChkVul, "ChkVul", True)
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
If IO.File.Exists(SavePath) Then
Try
MyControls = XElement.Load(SavePath)
For Each el As XElement In MyControls.Elements
Dim ctrl() As Control = Me.Controls.Find(el.#name, True)
If ctrl.Length > 0 Then
Dim chkd As System.Reflection.PropertyInfo = ctrl(0).GetType().GetProperty("Checked")
Dim chkdV As Boolean = Boolean.Parse(el.#checked)
chkd.SetValue(ctrl(0), chkdV)
End If
Next
Catch ex As Exception
'todo
End Try
End If
End Sub
Private Sub ChkVul_CheckedChanged(sender As Object, e As EventArgs) Handles ChkVul.CheckedChanged
If ChkVul.Checked Then
Me.pnlInsert.Visible = True
Else
Me.pnlInsert.Visible = False
End If
SaveCheckedState(ChkVul, "ChkVul")
End Sub
Private Sub RadioButton1_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton1.CheckedChanged
SaveCheckedState(RadioButton1, "RadioButton1")
End Sub
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
SaveCheckedState(CheckBox1, "CheckBox1")
End Sub
Private MyControls As XElement = <controls>
</controls>
Private Sub SaveCheckedState(Ctrl As Control,
Name As String,
Optional Save As Boolean = False)
If Ctrl.GetType().GetProperty("Checked") IsNot Nothing Then
Dim chkd As System.Reflection.PropertyInfo = Ctrl.GetType().GetProperty("Checked")
Dim chkdV As Boolean = CBool(chkd.GetValue(Ctrl))
Dim ie As IEnumerable(Of XElement)
ie = From el In MyControls.Elements
Where el.#name = Ctrl.Name
Select el Take 1
Dim thisXML As XElement
If ie.Count = 0 Then
thisXML = <ctrl name=<%= Name %>></ctrl>
MyControls.Add(thisXML)
Else
thisXML = ie(0)
End If
thisXML.#checked = chkdV.ToString
End If
If Save Then
Try
MyControls.Save(SavePath)
Catch ex As Exception
'todo
' Stop
End Try
End If
End Sub
End Class

Could someone explain where are the errors in this code in vb.net

I'm new to this site and also a newbee in vb.net, I created a simple form in vb.net, a form with 3 buttons, by clicking Button1 Species1.txt is created, and by clicking Button2 the lines in Species1.txt are copied in a String Array called astSpecies(), and by Button3 the String Array is copied in a new file, named Species2.txt, below is the code.
Public Class Form4
Dim astSpecies() As String
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim myStreamWriter = New StreamWriter("C:\Users\Administrator\Documents\species1.txt", True)
myStreamWriter.WriteLine("Pagasius pangasius")
myStreamWriter.WriteLine("Meretrix lyrata")
myStreamWriter.WriteLine("Psetta maxima")
myStreamWriter.WriteLine("Nephrops norvegicus")
myStreamWriter.WriteLine("Homarus americanus")
myStreamWriter.WriteLine("Procambarus clarkii")
myStreamWriter.Close()
MsgBox("list complete")
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim myStreamReader = New StreamReader("C:\Users\Administrator\Documents\species1.txt")
Dim i As Integer
Dim stOutput As String
stOutput = ""
Do While Not myStreamReader.EndOfStream
astSpecies(i) = myStreamReader.ReadLine
stOutput = stOutput & astSpecies(i) & vbNewLine
i = i + 1
Loop
myStreamReader.Close()
MsgBox(stOutput)
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim myStreamWriter = New StreamWriter("C:\Users\Administrator\Documents\species2.txt", True)
Dim o As Integer
Do While o <= astSpecies.Length
myStreamWriter.WriteLine(astSpecies(o))
o = o + 1
Loop
myStreamWriter.Close()
End Sub
End Class
First of all, you should make a few settings when it comes to VB.Net. 1.) set Option Strict to On 2.) remove the VB6 namespace. VB6 is the old Visual Basic. There are many functions in this that are inefficient from today's perspective. So please do not write MsgBox() but MessageBox.Show("").
(If you still need control characters such as NewLine or Tab, you can set a selective reference with Imports Microsoft.VisualBasic.ControlChars. Sounds contradictory, but it is useful, because why should you also write ChrW(9), it is not legible.)
I quickly started a project myself and wrote whatever you wanted.
I still don't quite understand why you first write things into a text file, then read them out, and then write that into a second text file – I want to say: where do the strings originally come from? The strings must have been there already? Anyway, I filled a List(of String) in the Button2_Click procedure. This has the advantage that you don't have to know in advance how many strings are coming, and you can sort them later and so on ...
You should also discard all Writers when you no longer need them. So use Using. Otherwise it can happen that the written files are not discarded and you can no longer edit the file.
Imports Microsoft.VisualBasic.ControlChars
Imports Microsoft.WindowsAPICodePack.Dialogs
Public NotInheritable Class FormMain
Private Path As String = ""
Private allLines As New List(Of String)
Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.BackColor = Color.FromArgb(161, 181, 165)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using OFolderD As New CommonOpenFileDialog
OFolderD.Title = "Ordner auswählen"
OFolderD.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
OFolderD.IsFolderPicker = True
If OFolderD.ShowDialog() = CommonFileDialogResult.Ok Then
Path = OFolderD.FileName
Else
Return
End If
End Using
Path &= "\Data.txt"
Using txtfile As System.IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(Path, True)
txtfile.WriteLine("Pagasius pangasius")
txtfile.WriteLine("Meretrix lyrata")
txtfile.WriteLine("Psetta maxima")
txtfile.WriteLine("Nephrops norvegicus")
txtfile.WriteLine("Homarus americanus")
txtfile.WriteLine("Procambarus clarkii")
txtfile.Close()
End Using
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'read all Text
Dim RAT() As String = System.IO.File.ReadAllLines(Path, System.Text.Encoding.UTF8)
If RAT.Length = 0 OrElse RAT.Length = 1 Then
MessageBox.Show("The File only contains 0 or 1 characters.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand)
Return
End If
allLines.AddRange(RAT)
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim Pfad_txt As String = Path.Substring(0, Path.LastIndexOf("\"c)) & "\Data2.txt"
Using txtfile As System.IO.StreamWriter = My.Computer.FileSystem.OpenTextFileWriter(Pfad_txt, True)
For Each Line As String In allLines
txtfile.WriteLine(Line)
Next
txtfile.Close()
End Using
End Sub
End Class
By the way: I use a FolderBrowserDialog in the Button1_Click procedure. This should be done so that the program also runs properly on other PCs. In order to be able to use the FBD, you have to download Microsoft.WindowsAPICodePack.Dialogs in Visual Studio's own Nuget package manager.
how to set Option Strict to On
How to uncheck VB6.
how to install FolderBrowserDialog in Visual Studio
Button1
If you want to use a StreamWriter it should be disposed. Classes in the .net Framework that have a Dispose method may use resources outside of the framework which need to be cleaned up. The classes shield you from these details by provided a Dispose method which must be called to properly do the clean up. Normally this is done with Using blocks.
I used a string builder which saves creating and throwing away a string each time you change the string. You may have heard that strings are immutable (cannot be changed). The StringBuilder class gets around this limitation. It is worth using if you have many changes to your string.
The File class is a .net class that you can use to read or write files. It is not as flexible as the stream classes but it is very easy to use.
Button 2
When you declared your Array, you declared an array with no elements. You cannot add elements to an array with no space for them. As Daniel pointed out, you can use the .net class List(Of T) The T stands for Type. This is very good suggestion when you don't know the number of elements in advance. I stuck with the array idea by assigning the array returned by File.ReadAllLines to the lines variable.
You get the same result by simply reading all the text and displaying it.
Button 3
Again I used the File class here which allows you to complete your task in a single line of code. Using 2 parameters for the String.Join method, the separator string and the array to join, we reproduce the original file.
Private SpeciesPath As String = "C:\Users\maryo\Documents\species1.txt"
Private lines As String()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim sb As New StringBuilder
sb.AppendLine("Pagasius pangasius")
sb.AppendLine("Meretrix lyrata")
sb.AppendLine("Psetta maxima")
sb.AppendLine("Nephrops norvegicus")
sb.AppendLine("Homarus americanus")
sb.AppendLine("Procambarus clarkii")
File.WriteAllText(SpeciesPath, sb.ToString)
MsgBox("list complete")
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
lines = File.ReadAllLines(SpeciesPath)
MessageBox.Show(String.Join(Environment.NewLine, lines))
'OR
MessageBox.Show(File.ReadAllText(SpeciesPath))
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
File.WriteAllLines("C:\Users\maryo\Documents\species2.txt", lines))
End Sub

Cannot update textbox vb.net -crossthreading

I am trying to make an application to run multiple (adb specially) commands and get the output and display in a label.
First of all, i need to start the process to execute the commands. Thanks to stackoverflow and #pasty I found this (second reply): How to get Output of a Command Prompt Window line by line in Visual Basic?
Well, i thought that because it outputted to the console, it would be simple to just write it to the label. BIG MISTAKE! It gives me a cross threading error!
A little bit of googling and stack overflow I found this: vb.net accessed from a thread other than the thread it was created on
Well, i tried to implement that, but the program just crashes freezes.
Here is the code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' store error output lines
Dim executable As String() = {"adb", "adb"}
Dim arguments As String() = {"help", "reboot"}
For i As Integer = 0 To 0
Dim process = New Process()
process.StartInfo = createStartInfo(executable(i), arguments(i))
process.EnableRaisingEvents = True
AddHandler process.Exited, Sub(ByVal sendera As Object, ByVal ea As System.EventArgs)
Console.WriteLine(process.ExitTime)
Console.WriteLine(". Processing done.")
'UpdateTextBox(ea)
End Sub
' catch standard output
AddHandler process.OutputDataReceived, Sub(ByVal senderb As Object, ByVal eb As DataReceivedEventArgs)
If (Not String.IsNullOrEmpty(eb.Data)) Then
Console.WriteLine(String.Format("{0}> {1}", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"), eb.Data))
'UpdateTextBox(eb.Data)
End If
End Sub
' catch errors
AddHandler process.ErrorDataReceived, Sub(ByVal senderc As Object, ByVal ec As DataReceivedEventArgs)
Console.WriteLine(String.Format("! {0}", ec.Data))
Dim a As String = String.Format("! {0}", ec.Data)
'UpdateTextBox(a)
End Sub
' start process
Dim result = process.Start()
' and wait for output
process.BeginOutputReadLine()
' and wait for errors :-)
process.BeginErrorReadLine()
process.WaitForExit()
Next
End Sub
Private Sub UpdateTextBox(ByVal a As String)
If Me.InvokeRequired Then
Dim args() As String = {a}
Me.Invoke(New Action(Of String)(AddressOf UpdateTextBox), args)
Return
End If
Label1.Text += "a"
End Sub
Private Function createStartInfo(ByVal executable As String, ByVal arguments As String) As ProcessStartInfo
Dim processStartInfo = New ProcessStartInfo(executable, arguments)
processStartInfo.WorkingDirectory = Path.GetDirectoryName(executable)
' we want to read standard output
processStartInfo.RedirectStandardOutput = True
' we want to read the standard error
processStartInfo.RedirectStandardError = True
processStartInfo.UseShellExecute = False
processStartInfo.ErrorDialog = False
processStartInfo.CreateNoWindow = True
Return processStartInfo
End Function
And the source code: https://github.com/iAmAOpenSource/SyncfusionWindowsFormsApplication3
The call to process.WaitForExit() will block the UI thread until the spawned process exits, but while processing the output in the process.OutputDataReceived event you are calling Me.Invoke which tries to run the code on the UI thread, which is blocked, so the program freezes. You could move the logic in Button1_Click onto another thread, e.g.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Task.Run(
Sub()
... current logic
End Sub
)
End Sub
That way the UI thread won't be blocked and the Me.Invoke call won't cause a deadlock.

Trigger richtextbox textchanged event until it detects certain text

I am working on serialport that can send and received certain commands. I would like to implement a retry feature which will allow me (the client) to resend data until the device (server) received and send a response to me.
Because of that I created a simple code that can illustrate this kind of function.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RichTextBox1.AppendText(Environment.NewLine & "Sample")
End Sub
Private Sub RichTextBox1_TextChanged(sender As Object, e As EventArgs) Handles RichTextBox1.TextChanged
Console.WriteLine("Trigger textchanged")
Dim totalLines As Integer = Me.RichTextBox1.Lines.Length
Dim lastLine As String = Me.RichTextBox1.Lines(totalLines - 1)
Dim CSTAT_Check As Boolean = lastLine Like "*Sample*"
If CSTAT_Check = True Then
RichTextBox1.AppendText(Environment.NewLine & "Sample")
End If
End Sub
End Class
The way it works is like this, I will clicked the button to append a sample string to richtextbox then the richtextbox textchange_event will be triggered causing it to resend the sample string to itself and will causes it to trigger another textchange_event and so on and so forth until the device received the sample string which in return the device (server) will send a sample_accepted string to my device (client) and because the textchanged_event doesnt detect the sample string in the last line of richtextbox it will no longer send another sample string to richtextbox.
It's little hard to understand so I will create a simple diagram
Client (Me) Server (Device)
Send sample string Doesn't detected
Send sample string again Doesn't detected again
Send sample string again Doesn't detected again
Send sample string again Doesn't detected again
Send sample string again Detected sample will send sample_accepted
Client Will no longer send sample string because the server detected it already.
The problem in my code is it seems like it doesn't trigger the textchanged_event again after its first trigger.
???If you change a property inside the code that responds to that property being changed, another changed event will not fire.??? You need to manually trigger the textchanged event after you make the changes.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RichTextBox1.AppendText(Environment.NewLine & "Sample")
End Sub
Private Sub RichTextBox1_TextChanged(sender As Object, e As EventArgs) Handles RichTextBox1.TextChanged
Console.WriteLine("Trigger textchanged")
Dim totalLines As Integer = Me.RichTextBox1.Lines.Length
Dim lastLine As String = Me.RichTextBox1.Lines(totalLines - 1)
Dim CSTAT_Check As Boolean = lastLine Like "*Sample*"
If CSTAT_Check = True Then
RichTextBox1.AppendText(Environment.NewLine & "Sample")
RichTextBox1_TextChanged(sender, New EventArgs())
End If
End Sub
End Class

How to pass listbox into background worker in VB .Net 2010

I am trying to loop through a listbox which contains filenames and upload them to an FTP server with a background worker. I am getting a cross-thread exception at my for loop when I attempt to access Listbox1.Items.Count within background worker (obviously because it's on a different thread) so I'm curious how I can pass the listbox into my background worker to execute the code they way I have written it below?
Private Sub bgw_upAllFiles_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw_upAllFiles.DoWork
Dim i
Dim toPath As String = MyForms.MoveOutFTPFormDir & PDFVar_PHOTO_URL_NUM & "/"
For i = 0 To e.Argument.Items.Count - 1
Try
retryDL:
My.Computer.Network.UploadFile(ListBox1.Items(i).ToString, toPath & IO.Path.GetFileName(ListBox1.Items(i).ToString), MyForms.MoveOutFTPUser, MyForms.MoveOutFTPPwd)
Catch ex As Exception
If ex.ToString.Contains("error: (550)") Then
'MsgBox("Need to create FTP folder")
Try
Dim myftprequest As Net.FtpWebRequest = CType(Net.FtpWebRequest.Create(toPath), System.Net.FtpWebRequest)
myftprequest.Credentials = New System.Net.NetworkCredential("JeffreyGinsburg", "andy86")
myftprequest.Method = System.Net.WebRequestMethods.Ftp.MakeDirectory
myftprequest.GetResponse()
GoTo retryDL
Catch ex2 As Exception
ex2.ToString()
End Try
Else
MsgBox(ex.ToString)
End If
MDIParent1.StatusStrip.Items.Item(2).Text = "Upload Complete"
End Try
Next
End Sub
When you call RunWorkerAsync, you are able to pass an object as a parameter. you could use this object and pass in your DDL.
Then, in the DoWork event, you can make use of the DDL like so:
Dim ddl = CType(e.Arugment, DropDownList)
BackgroundWorker.RunWorkerAsync Method
Pass the items to the backgroundworker as a string array:
BackgroundWorker1.RunWorkerAsync(ListBox1.Items.Cast(Of String).ToArray)
Then iterate that array in the dowork sub:
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim files As String() = DirectCast(e.Argument, String())
For Each file As String In files
'My.Computer.Network.UploadFile(file, ......
Next
End Sub
you have two choices:
Run on a different thread:
worker.RunWorkerAsync(Listbox1.Items.Cast().ToList())
then use:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var items = e.Argument as List<string>;
}
or you call the action on the main thread:
ListBox1.Invoke(new Action(() =>
{
var items = ListBox1.Items;
}));