VB.net progress bar while installing a printer - vb.net

i have a listbox with a bunch of printers and by selecting a printer and pressing the install button, the printer gets installed. the process takes 30-45 seconds where the application freezing but it is installing at the background. is there anyway to place a progress bar that shows something is happening instead of the freezing. this is the part of the code where the printer is installing. doesnt need a progress bar but any type of activity to show something is happening while the driver is installing
Dim objNetwork = CreateObject("WScript.Network")
MsgBox("Printer Driver is Installing, Please wait",, "Installing")
objNetwork.AddWindowsPrinterConnection("\\printserver\" + CheckedListBox1.SelectedItem)
objNetwork.SetDefaultPrinter(CheckedListBox1.SelectedItem)
objNetwork = Nothing
thanx in advance

You can really choose any way to tell the user to wait while doing background tasks. Here is an option with a ToolStripStatusLabel and Cursor update. It uses Async/Await to create the Task to not freeze your ui.
Option Strict On
Imports IWshRuntimeLibrary
' ---
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Try
Me.Cursor = Cursors.WaitCursor
Me.ToolStripStatusLabel1.Text = "Printer Driver is Installing, Please wait"
Dim name = CheckedListBox1.SelectedItem.ToString()
Await addPrinterAsync(name)
Finally
Me.Cursor = Cursors.Default
Me.ToolStripStatusLabel1.Text = "Ready"
End Try
End Sub
Private Function addPrinterAsync(name As String) As Task
Return Task.Run(
Sub()
Dim objNetwork As New WshNetwork() '= CreateObject("WScript.Network")
objNetwork.AddWindowsPrinterConnection($"\\printserver\{name}")
objNetwork.SetDefaultPrinter(name)
End Sub)
End Function
I have also added a COM reference to Windows Script Host Object Model which allows you to put Option Strict On and create the printer with strong types. But that is optional but you can always go without as you have.
I just don't think a MsgBox will work because the user can close it any time they like even if you manage to get it to show non-modal and not freeze the ui.

Related

How can I run code in a background thread and still access the UI?

I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus
(something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what
causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else
it keeps running smoohtly, updating a label like it should with the
amount of files found.. and even finish the loop with 100% success
(again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop
called from a button...
2) windows 7 could possibly support a new
thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see
the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can
still see the label refresh BUT on windows 7 everything works the same
UNLESS i click somewhere, no matter where i click on windows the loop
crashes
im using framework 4.6.2 developer
While I'm glad you found a solution, I advise against using Application.DoEvents() because it is bad practice.
Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put, Application.DoEvents() is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages. WM_PAINT is one of those messages which is why your window redraws.
However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the ScanButtonInForm1() method is called by the click of a button you'd be able to click that button again (unless you set Enabled = False) and starting the process once more, which brings us to yet another backside:
The more Application.DoEvents()-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.
The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the Thread class and pass a delegate to the method you want the thread to run:
Dim myThread As New Thread(AddressOf <your method here>)
...then you should set its IsBackground property to True if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).
Then you just call Start() and you have a running background thread!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the Control.Invoke() or Control.BeginInvoke() methods. BeginInvoke() is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.
One should also make sure to check the Control.InvokeRequired property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.
The basic InvokeRequired/Invoke pattern looks like this (mostly for reference, keep reading below for shorter ways):
'This delegate will be used to tell Control.Invoke() which method we want to invoke on the UI thread.
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox, ByVal Text As String)
Private Sub myThreadMethod() 'The method that our thread runs.
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), TextBox1, "Status update!") 'We are in a background thread, therefore we must invoke.
Else
UpdateTextBox(TextBox1, "Status update!") 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
'This is the method that Control.Invoke() will execute.
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox, ByVal Text As String)
TargetTextBox.Text = Text
End Sub
New UpdateTextBoxDelegate(AddressOf UpdateTextBox) creates a new instance of the UpdateTextBoxDelegate that points to our UpdateTextBox method (the method to invoke on the UI).
However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Private Sub myThreadMethod()
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(Sub() TextBox1.Text = "Status update!") 'We are in a background thread, therefore we must invoke.
Else
TextBox1.Text = "Status update!" 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
Now all you have to do is type Sub() and then continue typing code like if you were in a regular method:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End Sub)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and InvokeRequired check for you.
Place this in a separate code file:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
Now you only need to call this single method when you want to access the UI, no additional If-Then-Else required:
Private Sub myThreadMethod()
'Do some background stuff...
Me.InvokeIfRequired(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
End Sub)
'Do some more background stuff...
End Sub
Returning objects/data from the UI with InvokeIfRequired()
With my InvokeIfRequired() extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Example
The following code will increment a counter that tells you for how long the thread has run:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False 'Make the button unclickable (so that we cannot start yet another thread).
End Sub
Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000) 'Wait for approximately 1000 ms (1 second).
Time += 1
Me.InvokeIfRequired(Sub() Label1.Text = "Thread has been running for: " & Time & " seconds.")
End While
End Sub
Hope this helps!
The reason your application is freezing is that you are doing all the work on the UI thread. Check out Async and Await. It uses threading in the background but makes it way easier to manage. An example here:
https://stephenhaunts.com/2014/10/14/using-async-and-await-to-update-the-ui-thread/

MultiThreaded solution to avoid DialogBox pausing execution

I am currently automating a series of calls to a library in VB.NET consoleApplication. The functioncalls usually require a series of user selected inputs. My problem with this is that a set of these functions create a programmatically inaccessible DialogBox instance and pauses the execution of the program until they have been interacted with.
Right now I have tried to solve this problem by using multiple threads according to the code below.
Public Sub StartFormFunction(ByVal inputValue As String)
frameWork.showHiddenDialogBox(inputValue)
End Sub
Public Sub threadFunction(ByValue inputValue As String)
Dim nrOfOpenForms As Integer = Application.OpenForms.Count()
Try
Dim t As New Thread(New ParameterizedThreadStart(AddressOf StartFormFunction))
t.Priority = Threading.ThreadPriority.Highest
t.Start(inputValue)
'Wait until the prompt has been created.
While (Application.OpenForms.Count() = nrOfOpenForms) And (t.IsAlive)
End While
if Not t.IsAlive Then
log.Error("Thread did not open dialogBox")
Return
End If
'Select preffered button on dialogBox
Dim isFinished As Boolean = False
For Each curForm As Form In Application.OpenForms
For Each btn As Button In curForm.Controls.OfType(Of Button)
If btn.Name = "Button3" Then
btn.PerformClick()
isFinished = True
Exit For
End If
Next
if isFinished Then
Exit For
End If
Next
'Wait until thread completed Function
While t.IsAlive
End While
Catch ex As Exception
log.Error("Thread Error")
End Try
End Sub
I have not found a way to use Control.Invoke() in a console application yet and is because of this the reason it is not used.
The way I can get my code to be able to execute completely is to disable CheckForIllegalCrossThreadCalls which I am trying to avoid.
Is it possible to solve the problem of accessing a DialogBox without using multiple threads? If not, is the problem solvable by invoking the subcall?
EDIT
Some of my description might have been lacking in detailed information.
My problem is that my application run a method showHiddenDialogBox(), that run a set of instructions in a class that is kept out of scope from my code. This inaccessible class displays a form when all functionality have been executed. When this form is shown the application pause all execution of code until a user is promoting a input.
This makes it necessary to use multiple threads to get around. However this new thread will own this form while it is displayed an all of the content. This included in the buttons that I would need the other thread to access.
Dont use "CheckForIllegalCrossThreadCalls", just invoke a control with this code:
'Insert this in a module
<Runtime.CompilerServices.Extension()>
Public Sub InvokeCustom(ByVal Control As Control, ByVal Action As Action)
If Control.InvokeRequired Then Control.Invoke(New MethodInvoker(Sub() Action()), Nothing) Else Action.Invoke()
End Sub
The call this sub for every control in a thread
textbox1.InvokeCustom(sub() textbox1.text = "abc")

Modifying Labels and Objects in Forms from Classes (VB.NET)

I have a class that gets the internet time and displays it in a text box. This is that class name InternetTimeGet
I have added a "progress meter" in that class that will show how far through the list of IPs the program is. Right now the Debug Console is displaying these IPs and updates fine as is shown here: IPs and Progress
Everything is already multithreaded and nothing is hanging my UI
However, I'm having trouble getting the program to display the progress on a label named ProgLbl in my main form named MainForm. Here is the code of my InternetTimeGet class:
Public Class InternetTimeGet
'Irrelevant variables omitted...
'Used to update progress
Public Shared IPCount As Integer
Public Shared ProgDivision As Single
Public Shared ProgBar As Single
Public Shared FullBar As Integer = 100
IPCount = My.Settings.ServerIPList.Count
ProgDivision = FullBar / IPCount
For Each IP As String In ServArray
Try
'Shows each IP and the Percentage completion in Debug console
Debug.WriteLine(IP, "IP Addresses")
ProgBar = ProgBar + ProgDivision
Debug.WriteLine(Math.Round(ProgBar, 1) & "%", "Progress")
MainForm.ProgLbl.Text = "Progress: " & Math.Round(ProgBar, 1) & "%"
If My.Computer.Network.Ping(IP) Then
LastHost = IP
Result = GetNISTTime(IP)
End If
Catch ex As Exception
'Return "Sync Error 0x01"
MsgBox("There was a sync error while retrieving the updated internet time. Please try again.", MsgBoxStyle.Critical, "Sync Error 0x01")
Debug.WriteLine("There was a sync error while retrieving the updated internet time. Please try again.", "Sync Error 0x01")
End Try
Next
The Debug console shows each IP and the percentage of completion fine and does not not show intermediate updates or hang the UI, but the label ProgLbl in my main form MainForm does not show these updates at all and just remains saying "Progress: ". Once again my main form MainForm's UI DOES NOT hang.
The problem is updating the label, ProgLbl, in my main form, MainForm, from the InternetTimeGet class shown above. That's what I'd appreciate help with.
I have been looking for an answer and tried several things but finally gave up and decided to ask those who know better.
Thank you for your help
Well, it is not clear where your code is implemented but if you want to make some operation (such as getting time and going through a list of IPs), as a general rule, you'll want to avoid to do it in the main thread to avoid freezing the UI.
The background worker is perfect for that kind of purpose, as it will allow you to run code Async while reporting progress so you can update your main form.
Here is a simple example using a background worker
that you will have to rework to fit your need.
Private WithEvents _GetTimeWorker As BackgroundWorker
Somewhere in your form, you need to initialize your worker and set the WorkerReportProgress to true. You can then start the worker when you need.
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
_GetTimeWorker = New BackgroundWorker With {.WorkerReportsProgress = True}
_GetTimeWorker.RunWorkerAsync()
End Sub
DoWork is actually the async method that you put your logic in so it does not block the UI. When you want to update the progress bar, you call ReportProgress on your background worker.
Private Sub _GetTimeWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles _GetTimeWorker.DoWork
Dim watch As New Stopwatch
watch.Start()
'Just simulating some work.
While watch.ElapsedMilliseconds < 10000
System.Threading.Thread.Sleep(100)
_GetTimeWorker.ReportProgress(CInt(watch.ElapsedMilliseconds / 10000 * 100))
End While
End Sub
The progress bar value is updated from the progress changed function.
Private Sub _GetTimeWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles _GetTimeWorker.ProgressChanged
'There we change the progress bar value
prog1.Value = e.ProgressPercentage
End Sub

Session Implementation in windows Forms

I am using a Windows form application due to added security measures i have to work around for session in my application.Currently i am using a Timer to achieve the functionality I am able to close the form but i need to again restart the application to return to the login form.I am using the below code
Private Sub sessionTimer_Tick(sender As Object, e As EventArgs) Handles sessionTimer.Tick
Try
Me.sessionTimer.Stop()
Me.sessionTimer.Enabled = False
Process.Start(Application.StartupPath + "\application.exe")
Process.GetCurrentProcess().Kill()
Catch ex As Exception
End Try
End Sub
I am getting an exception when i use the above method and it doesn't serve the purpose,also i have already tried using Application.Restart didn't work out.Please help i am new to windows form. Also adding to this in order to reset the timer i am using the below code.
Private Sub frmMain_MouseMove(sender As Object, e As MouseEventArgs) Handles MyBase.MouseMove
Me.sessionTimer.Stop()
Me.sessionTimer.Start()
End Sub
But this doesn't seem to work the main form has menu's which i am using to navigate to other forms so the idle time should not include the time spent in other forms which are opened via the menu's. What event should i use in frmMain to handle this problem.Thanks
Just let the Framework do the work.
Application.Restart()
If your session Timer fires from a background thread (maybe you use System.Timers.Timer instead of System.Windows.Forms.Timer) you propable have to sync with your main thread.
Me.Invoke(new MethodInvoker(Addressof Application.Restart))
If Application.Restart does not work there is propably something wrong with your app. You should try the following.
If you created threads, be sure they are created with "thread.IsBackground = true" otherwise they will keep your process open
Stop your timers and background workers.
Be sure you don't have forms that handle the FormClosing event and set e.Cancel = true. In that case you have to take e.CloseReason into account.
User Mark Hurd posted a great post about what happens during Application.Restart, have a look here
Iam using this code to restart my application. It works very well.
System.Diagnostics.Process.Start(Application.ExecutablePath) 'First start a new instance
Me.Close() 'Close the current application
If this doesnt work. I think there is no way around than using another application which restarts the Process. Here is a code example (second application)
Private Shared Sub RestartApp(pid As Integer, applicationName As String, arguments As String)
' Wait for the process to terminate
Dim process__1 As Process = Nothing
Try
process__1 = Process.GetProcessById(pid)
process__1.WaitForExit(1000)
' ArgumentException to indicate that the
' process doesn't exist?
Catch ex As ArgumentException
End Try
Process.Start(applicationName, arguments)
'Arguments?
End Sub
Source of the code

SerialPort.DataReceived Event stops during PowerPoint Slideshow

Using VS2012 (.NET) I am developing a ribbon bar style add-in for PowerPoint (2010) where I want the incoming serial port values to be passed through an algorithm and, depending on the output, perform an action such as next slide or previous slide. I am using the SerialPort.DataReceived event handler.
My problem is simpler explained using an iterating variable j which increases in value by 1 each time the SerialPort.DataReceived event handler is called. I will detail the problem more after the code but, in short, once my code is called j increases in value as expected prior to a slideshow presentation being started and for the first 30 seconds or so of the slideshow presentation. After about 30 seconds of being in presentation mode, j stops iterating. I am watching the value of j using Breakpoint When Hit and the debugging Output window.
In one class (class2), associated with a form launched by clicking a ribbon button, a button click in said form sets the SerialPort.DataReceived handler and opens the serial port (abbreviated relevant code):
Public Class class2 'called by clicking ribbon button
Public sp As New SerialPort
Public Baud As Integer = 9600
Public Port As String
Public c1 As class1
Public Sub New(oParent As class1) 'get reference to parent class
c1= oParent
InitializeComponent()
End Sub
....
Public Sub btn1_Click(sender As Object, e As EventArgs) _
Handles btn1.Click
Port = lbCom.SelectedItem 'Port selected from form listbox
Try
With sp
.BaudRate = Baud
.PortName = Port
.ReadTimeout = 5000 'Give serial port 5sto open
.RtsEnable = True
End With
c1.mySP = sp
AddHandler sp.DataReceived, AddressOf c1.spDataReceivedHandler
sp.Open()
Me.Hide()
Catch ex As Exception
MsgBox("Error", vbOKOnly, "Connection Error")
End Try
End Sub
End Class
In the main class (class1) I have the DataReceived Handler and iterating variable j (again, abbreviated relevant code):
Public Class class1
...
Public Sub spDataReceivedHandler(ByVal sender As Object, _
e As SerialDataReceivedEventArgs)
j = j + 1 ' value monitored using Breakpoint When Hit
End Sub
End Class
More details about the problem. Once I start my code, if I don't start a slideshow presentation j will iterate without any problem (5+minutes). If I start a slideshow presentation but immediately hit alt+tab to give focus to another application, say VS, j will iterate without any problem (with PowerPoint still being in presentation mode). If I start a slideshow presentation and let it have the focus j will stop iterating after ~30 seconds, regardless of how long I let j iterate prior to starting the slideshow presentation.
I have also tried using a background worker instead of serial port event handler where I use a do loop to get data from the serial port but I run into the same issue; works as expected until slideshow presentation has been running for ~30 seconds.
I have already written two separate Windows Form style applications using the same serial port parameters and same device which work fine. The issue, as far as I can tell, is with the slideshow presentation having the focus (and I'm guessing resource availability?).
I'm going to try my luck using the Task Parallel library, but if anyone has any insight as to why the aforementioned maddening problem exists do please enlighten me (Note: relatively novice programmer, so if there is a glaring error in my approach please let me know as well). Thanks.
Okay, so no idea why the program was behaving that way but a work around was indeed to put the "open and monitor" serial port code into a sub in the ThisAddIn class and call it as a new task when appropriate (clicking a button).
Clicking a button on my custom ribbon:
Public Class myribbon
Private Sub btn_Click(sender As Object, e As RibbonControlEventArgs) _
Handles btn.Click
' Define new task spTask (sub located in ThisAddIn)
Dim spTask = New Task(Sub() Globals.ThisAddIn.readToSP())
End Sub
End Class
Public Class ThisAddIn
...' other stuff
Dim mySP As New SerialPort
Public Sub readToSP()
...' serial port params
Try
mySP.Open()
While (mySP.IsOpen)
Dim analogV As String = mySP.ReadTo(delimStr)
...' do something with analogV
End While
Catch ex as Exception 'appropriate catches...
End Try
End Sub
End Class