I am using DownloadFileAsync to download a larger file (1.3 GB), but i'd like to add a simple percentage indicator (ex. 64%). I'm new to Visual Basic I have no idea how to do this.
Any help would be appreciated.
The WebClient class has a DownloadProgressChanged event that you can listen to if you want to update a progresss display. For instance, if you’ve got a console application, it’s as simple as:
Dim client As New WebClient()
AddHandler client.DownloadProgressChanged, AddressOf ProgressUpdate
client.DownloadFileAsync(yourURI, yourFile)
Sub ProgressUpdate(sender As Object, e As DownloadProgressChangedEventArgs)
' Reset cursor position …
Console.CursorTop -= 1
Console.CursorLeft = 0
Console.WriteLine("{0}% completed", e.ProgressPercentage)
End Sub
If, on the other hand, you are on a Form in a WinForms project and you’ve got a label ProgressLabel that you want to update, the following code will do that:
Sub ProgressUpdate(sender As Object, e As DownloadProgressChangedEventArgs)
Dim s = String.Format("{0}% completed", e.ProgressPercentage)
Me.Invoke(New Action(Sub()
ProgressLabel.Text = s
End Sub))
End Sub
The ProgressUpdate method is a bit complicated due to multithreading:
The WebClient is running the asynchronous file download in a background thread. However, form controls can only be updated from the foreground thread that the form is running in. For that reason, we cannot update the label directly inside the ProgressUpdate event (because that, too, is being invoked, and running, in the background thread1).
So what we do instead is use the Form.Invoke method which guarantees that whatever we want to execute is execute in the form’s own thread. We pass an Action delegate to the Invoke method which contains the code that we want to execute. And that code is just updating the label.
1 At least I couldn’t find anything in the documentation saying otherwise – the event might actually execute in the foreground thread but in that case the above code still works.
Related
im running a backgroundworker which is calling a function to download huge files and folders. im not able to stop this through cancelasync. The cancelationpending event is received, but my function still calculates.
How can I stop the thread?
Private Sub copydownloads_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles copydownloads.DoWork
DirectoryCopy()
End Sub
public sub DirectoryCopy()
'here is the work done
end sub
CancelAsync wont magically stop BackgroundWorker. You have to manually poll CancelPending property in your download code.
The worker code should periodically check the CancellationPending property to see if it has been set to true.
When you see that CancellationPending is set to true, you have to stop your downloading (ie. download small chunks in a loop and check flag constantly).
https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync%28v=vs.110%29.aspx
See this question for an example code how to download file in chunks. Add CancelPending checking inside that code:
Download File With Start / Pause / Stop
I have started a process:
Dim getUdpate as Process
getUpdate = New Process
getUpdate.StartInfo.FileName = "C:\UTIL\GETBTCH.BAT"
getUpdate.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
getUpdate.StartInfo.UseShellExecute = False
getUpdate.StartInfo.WorkingDirectory = "C:\UTIL\"
getUpdate.Start()
getUpdate.Close()
Then, I want to Run another process but I want to check first if the getUpdate process is already finished.
How do I check if the process is already finished?
I already tried to look at the processes ID, but it only display cmd.exe and there are a lot of cmd.exe as the processes ID so I can't just go and stop all of those.
You can check the HasExited property of the process. It will return true if the process has ended, and false if it is still running.
You will need to check this before you call Close() on your getUpdate Process object. So getProcess will have to remain open until the procsses has exited.
Try:
getUpdate.WaitForExit(); instead of
getUpdate.Close()
If you are making a WinForms application or similarly interactive UI I suggest hooking a function into the object's Exited event instead of polling HasExited.
(You may already know this but) if you use WaitForExit or poll HasExited your UI is hanging exactly because your code is actually waiting for the process to end.
Your UI only has one thread and cannot "multitask". That's why these "processing" type of actions should be done in a different thread (or, as is the case here, a different process) and report back to the UI when they finish.
Example:
' Handle Exited event and display process information.
Private Sub myProcess_Exited(ByVal sender As Object, ByVal e As System.EventArgs)
'Do something in your UI
End Sub
and in your starting code:
getUpdate.EnableRaisingEvents = True
AddHandler getUpdate.Exited, AddressOf myProcess_Exited
I am trying to read/use the output from a python program in my vb.net project so far I'm not getting any results. What I'd like to see is the python program run (just by itself first) and all of the output get redirected into a textbox.
I've looked at some other posts about this, but I'm either missing something or not understanding something, as all I'm getting is blank output.
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim PythonPath = "C:\Python27\"
Dim strPath As String = Application.StartupPath
MessageBox.Show(PythonPath & "python.exe """ & strPath & "\Resources\import_logs.py"" ")
Dim start_info As New ProcessStartInfo(TextBox1.Text)
' Make the process and set its start information.
Dim process As New Process()
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
process.StartInfo.FileName = PythonPath & "\python.exe"
process.StartInfo.Arguments = """" & strPath & "\resources\import_logs.py"""""
process.StartInfo.UseShellExecute = False
process.StartInfo.CreateNoWindow = True
process.StartInfo.RedirectStandardOutput = True
'process.StartInfo.RedirectStandardError = True
AddHandler process.OutputDataReceived, AddressOf proccess_OutputDataReceived
process.Start()
process.BeginOutputReadLine()
End Sub
Public Sub proccess_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
On Error Resume Next
' output will be in string e.Data
' modify TextBox.Text here
'Server_Logs.Text = e.Data ` Does not display anything in textbox
MsgBox(e.Data) 'It works but I want output in text box field
End Sub
End Class
Eventually I'm going to pass arguments to the python script and I'd like to get feedback that I can then use (insert error into a database, email when it's done, etc), so I'd like it to capture the process while running and not just a data dump at the end.
Any help would be much appreciated.
First things first—it's no wonder you aren't sure what's wrong with your code, you're silencing all errors that could possibly help you to diagnose it. That's the only purpose of On Error Resume Next in VB.NET. That unstructured error handling was included only for backwards compatibility with the pre-.NET versions of VB and it's time to forget that it ever existed. You certainly don't want to use it in code. (I would say "in code that you're debugging", but all code is a potential candidate for debugging and ignoring errors is just dumb.)
Anyway, on to the specific problem. We know that the call to MsgBox works, but it doesn't work right when you start interacting with controls on your form. So something is falling apart there.
It turns out that the OutputDataReceived event is raised on an entirely different thread, a different one than was used to create the process and a different one than is running your application's UI. It actually just retrieves a thread from the system thread pool.
And that's where the problem lies: you cannot manipulate UI objects on a thread other than the one that created those objects (at least not without jumping through some hoops), which is precisely what your code tries to do here. In fact, you're probably swallowing an exception that would have rather obtusely informed you of this situation.
The simple fix is to set the SynchronizingObject property of the Process class to one of your UI components (like the form, or the specific control you want to output to). This forces all event handlers to execute on the same thread that created that component. At that point, your code should work fine, because you're not trying to do any cross-thread UI access. (Message boxes are not vulnerable to this because any thread can display a message box. You're not trying to access an existing UI object that is bound to another thread.)
Alternatively, you could handle the marshalling yourself in the event handler method through the use of delegates and the BeginInvoke method, but this seems like unnecessary work to me.
Ok, the following codes shows how I am enterting a value into a textbox, adding that value to the listbox, updating a picturebox next to it and blanking out the textbox so the user can add additional values to the listbox.
ListBox1.Items.Add(TextBoxTicketID.Text)
If CStr(ListBox1.Items(0)) = TextBoxTicketID.Text Then
PictureBoxStatus1.Image = My.Resources.Orange_Information
End If
TextBoxTicketID.Text = ""
I have another process not shown here that will create a PDF based on the value that was entered into the listbox.
I'm having trouble with a loop to check a specific directory if the PDF exists or not. When the PDF exists, I'll change the picturebox to another image.
Here is the loop that I was using, but the issue I ran into was that the user couldn't enter a second value unless the first value was present.
Loop Until My.Computer.FileSystem.FileExists("c:\Temp\" + ListBox1.Items(0) + ".pdf")
PictureBoxStatus1.Image = My.Resources.Green_Checkmark
So in theory, I need to be able to enter X amount of values into the listbox and keep checking to see if the file exists and if it does, change those images that needed.
EDIT
Here's what I ended up doing...seems to be working fine though...
ListBox1.Items.Add(TextBoxTicketID.Text)
If CStr(ListBox1.Items(0)) = TextBoxTicketID.Text Then
PictureBoxStatus1.Image = My.Resources.Orange_Information
End If
TextBoxTicketID.Text = ""
Call CheckFiles()
Added a public sub
Public Sub CheckSpooling()
Dim Watcher As New FileSystemWatcher()
Watcher.Path = "C:\Temp\"
Watcher.Filter = ListBox1.Items(0) + ".pdf"
AddHandler Watcher.Created, AddressOf OnChanged
Watcher.EnableRaisingEvents = True
End Sub
Then the sub to run whatever is needed if the file was added. I used a msgbox for testing.
Private Shared Sub OnChanged(source As Object, e As FileSystemEventArgs)
' Specify what is done when a file is created.
MsgBox("File has been created!")
End Sub
Check out the FileSystemWatcher
The reason the user can't enter anything while you are looping is because the WinForm framework is essentially single threaded. Everything in the UI occurs on the same thread, including the event handler. So, if you are sitting in a loop for a long time in a button click event handler, then the UI will be locked up and unresponsive until the code exits the loop. The way to get around this is to start a new thread to perform whatever work needs to be done. That worker thread can take as long as it needs to complete and it won't interfere with the UI thread so the UI remains responsive. This is made easier by the BackgroundWorker component which you can drop onto your forms in the form designer.
However, the FileSystemWatcher, as Dan-o has recommended is probably a better solution than creating your own worker thread that keeps checking if the file exists. Not only does it avoid re-inventing the wheel, but it also will be more efficient. Instead of constantly asking the file system if a file exists, it just listens to messages from the file system to find out when changes occur.
Team,
I have build a VB.Net windows application which does uploads data into database and basically updates two controls:
1. A textbox which is constantly updated with one line per database record upload.
2. A label which keeps track of the count of database record uploaded.
I have used BackgroundWorker thread concept, where the thread's bgwWorker_DoWork() method contains the business logic for upload and bgwWorker_ProgressChanged() updates the 2 UI controls based on uploads.
But the issue I am facing is that I do not get complete updates on both the UI controls. Sometimes the thread bypasses update of textbox and sometimes of label. I could resolve this issue by adding System.Threading.Thread.Sleep(25) before each UI control update code. Is this correct way of solving the issue? OR is there something I am missing?
Kindly suggest.
Below is the code in both these methods:
Private Sub bgwWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwWorker.DoWork
.................
.................
'Updates database record related update in textbox
System.Threading.Thread.Sleep(25)
updater.eventName = "UpdateStatusBox"
updater.errorMessageToLog = String.Empty
updater.errorMessageToLog += GetErrorMessage(dataTable(rowNumber)("Name").ToString(), ExceptionData)
bgwWorker.ReportProgress(1, updater)
.................
.................
'Updates Status Count in LABEL
System.Threading.Thread.Sleep(25)
updater.eventName = "UpdateStatusBar"
updater.successCount = successCount.ToString()
updater.failureCount = failureCount.ToString()
bgwWorker.ReportProgress(2, updater)
End Sub
Private Sub bgwWorker_ProgressChanged(ByVal sender As System.Object, ByVal e As ProgressChangedEventArgs) Handles bgwWorker.ProgressChanged
Dim updater As UIUpdater = TryCast(e.UserState, UIUpdater)
..........................................
If updater.eventName = "UpdateStatusBar" Then
UpdateStatusBar(updater.successCount, updater.failureCount)
ElseIf updater.eventName = "UpdateStatusBox" Then
txtUpdates.Text = txtUpdates.Text & updater.errorMessageToLog
End If
.....................................
End Sub
I'm almost positive that your problem is your instance of the UIUpdater object called updater. This object appears to be declared globally and is thus shared between calls.
Omitting a little bit of code this is what you have:
updater.eventName = "UpdateStatusBox"
bgwWorker.ReportProgress(1, updater)
updater.eventName = "UpdateStatusBar"
bgwWorker.ReportProgress(2, updater)
Although you call ReportProgress() linearly, it doesn't fire your ProgressChanged event immediately nor does it block until that method completed. To do so would defeat the purpose of threading if you think about it.
To put it another way, you have a global object that you are setting a property on. You then say "when someone gets a chance, do something with this". You then change a property on that global object and sometimes this happens before "someone has done something" happens.
The solution is either to create two global variables, one for each possible event or to just create an instance variable when needed. I'm not sure that its thread safe to use a global variable the way you are so I would recommend just creating an instance variable. In fact, the state object you pass to ReportProgress could just be a string.
I would NOT use a sleep in your DoWork event.
Have you tried refreshing the control after you update it? Each control has a Refresh method which forces a redraw. This may result in flickering though.
Another option is to include the information needed for both controls (textbox and label) in a single call to ReportProgress rather than trying to make two calls.