(VB.NET) Quick way for a Simple Splashscreen for WinForms [duplicate] - vb.net

This question already has answers here:
How to create a Splash screen for VB.net program
(3 answers)
Closed 6 days ago.
My program took ~5-10 seconds to load and sometimes people using it would end up trying to open it again, which caused problems. I found a quick and easy way to make a "splashscreen" (in a sense) that pops up for a set amount of time immediately on execution. I found that the first order of events in a WinForm EXE loading was Handle Created. The answer is not a true splashscreen, but for a couple lines of code that can be easily added to a project, I think some people will like it.

The below code will show a MessageBox immediately on running the EXE and closes after 10 seconds.
Imports System.Threading
Private Sub Control1_HandleCreated(ByVal sender As Object, ByVal e As EventArgs) Handles Me.HandleCreated
Dim SplashScreen As New Thread(
Sub()
CreateObject("WScript.Shell").Popup("Program Initializing, Please Wait...",10, "Setup Tool")
End Sub)
SplashScreen.Start()
End Sub
I use Threading so that the MessageBox will not freeze the code and the program will open with or without the OK button being pressed. Doing a regular MessageBox.Show() will prevent any more code from running until the user clicks OK I have found.

The best way I have found to implement a splash screen which keeps the user informed via messages and/or a progress bar or animated wheel is the following.
Have a startup form eg Form1, and have it carry out all the tedious startup procedures which might cause any animated or progress bar graphic to get stalled in the event queue. Add a "BackgroundWorker" object to Form1 from the Toolbox and in my case I just named it BackgroundWorker1.
Before starting these routines, usually in the Form1_Load event, make a call to the BackgroundWorker.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
CallBackgroundWork()
StartRoutines() 'this is the heavy lifting routines to get the app working. Set the LoadingStatusflag (declared as a Global Variable"
to various values to tell the splashscreen to display different messages
Loadingstatus = 10 'triggers splashform to exit
CancelBackgroundWork()
End Sub
These are the other subs to support this
Sub CallBackgroundWork()
BackgroundWorker1.WorkerSupportsCancellation = True
BackgroundWorker1.WorkerReportsProgress = True
' call this method to start your asynchronous Task.
BackgroundWorker1.RunWorkerAsync()
End Sub
Sub CancelBackgroundWork()
' to cancel the task, just call the BackgroundWorker1.CancelAsync method.
BackgroundWorker1.CancelAsync()
End Sub
Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'' The asynchronous task we want to perform goes here
FormSplash.Show()
End Sub
My splashscreen has some label controls and pictureboxes and the FormSplash_Load event runs a stopwatch loop of 40ms and loads a series of images (24 in total) of a spinning wheel. This keeps running while the splashscreen is active. By setting the global variable Loadingstatus to various values within different part of the loading sequence in Form1 it can trigger the loop routine to display different messages example shown. An easy way to communicate between threads as you can't directly access objects between threads The wheel keeps spinning no matter how intensive the load routine in Form1 as it is running in another thread. I used a stopwatch loop as starting a timer doesn't work for me - maybe an event queue issue in splash form.
Private Sub FormSplash_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Show()
Me.Opacity = 1 'show this form
'now start a loop that gets ended by other thread through variable Loadingstatus flag
Dim ggtimer As New Stopwatch, lastvalue As Integer, FProgPosition as integer
ggtimer.Start()
lastvalue = ggtimer.ElapsedMilliseconds
nextimage:
FProgPosition += 1
If FProgPosition = 24 Then FProgPosition = 1 'has 24 frames in the animated image
Do 'loop for 40 ms
If ggtimer.ElapsedMilliseconds - lastvalue > 40 Then
lastvalue = ggtimer.ElapsedMilliseconds
Exit Do
End If
Loop
PictureBoxProgress1.Image = FProgIMG(FProgPosition)
PictureBoxProgress1.Refresh()
If Loadingstatus = 10 Then GoTo endsplash
If Loadingstatus = 1 Then
If CoreTempRunning = False Then
Me.LabelCoreTemp.Text = "CoreTemp is NOT Running"
Me.LabelCoreTemp.ForeColor = Color.White
'insert cross picturebox
PictureBoxCoreTemp.Image = My.Resources.ResourceManager.GetObject("Cross24x24")
loaderrorflag2 = True
Else
Me.LabelCoreTemp.Text = "CoreTemp is Running"
Me.LabelCoreTemp.ForeColor = Color.White
'insert tick picturebox
PictureBoxCoreTemp.Image = My.Resources.ResourceManager.GetObject("Tick24x24")
loaderrorflag2 = False
End If
Me.PictureBoxCoreTemp.Visible = True
Me.PictureBoxCoreTemp.Refresh()
Me.LabelCoreTemp.Left = Me.Width * 2 / 3 - Me.LabelCoreTemp.Width
Me.LabelCoreTemp.Refresh()
GoTo nextimage
endsplash:
ggtimer.Stop()
Me.Opacity = 0.01
Me.Hide()
End Sub

Related

How to handle long running tasks in VB.NET forms?

I am currently working on a VB.NET form that automatically create Word documents according to an Excel file and a few extra data asked by the form (Project Name, Customer Name, Use SQL, ...).
This procedure works fine and takes approximatelly 1 or 2 minutes to complete.
The issue is that all my script is in ButtonGenerate.Click Handler. So when the Generate button is pressed the form window is bricked and it's impossible to Cancel...
It shouldn't be in a Click handler. Opening a new thread for that long task seems better. But Multithreading isn't very familiar to me.
I tryed launching the script with
ThreadPool.QueueUserWorkItem(...
but my Generate Sub sets labels and update a Progress Bar in the main form, so I doesn't work unless I use
Me.Invoke(New MethodInvoker(Sub()
label.Text = "..."
ProgressBar.Value = 10
' ...
End Sub)
each time I need to update something on the form and I can't even retrieve any new push of a button with that (A cancel button would be nice).
This is basically my code :
Public Class TestFichesAutomation
Private Sub BtnGenerate_Click(sender As Object, e As EventArgs) Handles BtnGenerate.Click
System.Threading.ThreadPool.QueueUserWorkItem(Sub() Generate())
End Sub
Public Sub Generate()
' Check user options, retrieve Excel Data, SQL, Fill in custom classes, create Word docs (~ 1 minute)
End Sub
So How would you handle that script ? Is Threading even a good solution ?
Thanks a lot for your help ^^ and for the useful doc.
My app now open a new thread and uses 2 custom classes to act like buffers :
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
myProgress = New Progress
' a custom class just for the UI with the current task, current SQL connection status and progress value in %
_Options.ProjectName = TextBoxProjectName.Text
_Options.CustomerName = TextBoxCustomerName.Text
...
' Fill in a custom "_Options" private class to act as a buffer between the 2 thread (the user choices)
Loading = New Loading()
Me.Visible = False
Loading.Show() ' Show the Loading window (a ProgressBar and a label : inputLine)
Task.Run(Function() Generate(Progress, _Options))
Me.Visible = True
End Sub
Public Async Function Generate(ByVal myProgress As Progress, ByVal Options As Options) As Task(Of Boolean)
' DO THE LONG JOB and sometimes update the UI :
myProgress.LoadingValue = 50 ' %
myProgress.CurrentTask= "SQL query : " & ...
Me.Invoke(New MethodInvoker(Sub() UpdateLoading()))
' Check if the task has been cancelled ("Cancelled" is changed by a passvalue from the Loading window):
If myProgress.Cancelled = True Then ...
' Continue ...
End Function
Public Shared Sub UpdateLoading()
MyForm.Loading.ProgressBar.Value = myProgress.LoadingValue
MyForm.Loading.inputLine.Text = myProgress.CurrentTask
' ...
End Sub
You should look into using the Async/Await structure
if the work you need to do is CPU bound, i like using Task.Run() doc here
By making your event handler Async and having it Await the work, you prevent the UI from locking up and avoid the use of Invoke in most cases.
ex:
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
Dim Result As Object = Await Task.Run(Function() SomeFunction())
'after the task returned by Task.Run is completed, the sub will continue, thus allowing you to update the UI etc..
End Sub
For progress reporting with Async/Await you might be interested in this

Open multiple URLs successively in WebBrowser control in VB.NET

I'm trying to make a "rotator" on a form that cycles through a series of urls and displays the url in the WebBrowser control. The following code displays my form, but the form remains white/blank and then the last url in the array appears after a while. When I put a MessageBox in-between each url, to create a stop, it works and each url appears. I've tried putting a Sleep in place of the MessageBox, but that didn't work. I've also tried increasing the Sleep time, but that didn't work either. How can I make it work correctly?
Sub Rotate()
Dim Urls() As String = {"www.stackoverflow.com", "www.google.com", "www.yahoo.com"}
Dim counter As Integer = 0
Form3.Show()
Do Until counter = 3
Form3.WebBrowser1.ScriptErrorsSuppressed = True
Form3.WebBrowser1.Navigate(Urls(counter))
'MessageBox.Show("Next")
counter = counter + 1
System.Threading.Thread.Sleep(2000)
Loop
End Sub
You can call Application.DoEvents after changing the URL so that the control gets the chance to redraw itself.
However, a better approach would be to use a timer which fires every 2 seconds and then change the URL in the event handler so that your UI keeps responsive.
For example setup a new field myTimer in your form, init it in your form's loading event and in the Tick event you call your Rotate method. As Rotate is now called several times, we have to move the counter variable out of the method and make it a field so that we keep its value between the invocations. I usually write C# so hopefully I did not make some typos below :)
Private WithEvents myTimer As System.Windows.Forms.Timer
Private counter As Integer
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
' ... your existing code ...
counter = 0
myTimer = New System.Windows.Forms.Timer
myTimer.Interval = 2000
myTimer.Enabled = True
myTimer.Start()
End Sub
Private Sub myTimerTick() Handles myTimer.Tick
Rotate()
End Sub
Sub Rotate()
Dim Urls() As String = {"www.stackoverflow.com", "www.google.com", "www.yahoo.com"}
WebBrowser1.ScriptErrorsSuppressed = True
WebBrowser1.Navigate(Urls(counter))
counter = counter + 1
If counter > 3 Then myTimer.Stop()
End Sub

VB.NET Marquee Progress Until Process Exits

While I have some VBScript experience, this is my first attempt at creating a very simple VB.NET (Windows Forms Application) wrapper for a command line application. Please be kind!
I have a very simple GUI with two buttons that both do an action and I'd like to show a marquee progress bar until the action (read: the process) is complete (read: exits).
The 'save' button does this:
Dim SaveEXE As Process = Process.Start("save.exe", "/whatever /arguments")
From there I'm starting the marquee progress bar:
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I thought I could use SaveEXE.WaitForExit() but the Marquee starts, then stops in the middle until the process exits. Not very useful for those watching; they'll think it hung.
I thought maybe I could do something like this but that causes my VB.Net app to crash
Do
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
Loop Until SaveEXE.ExitCode = 0
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I'm not entirely sure what needs to be done, short of getting some formal training.
You can use the new Async/Await Feature of .NET 4.5 for this:
Public Class Form1
Private Async Sub RunProcess()
ProgressBar1.Visible = True
Dim p As Process = Process.Start("C:\test\test.exe")
Await Task.Run(Sub() p.WaitForExit())
ProgressBar1.Visible = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RunProcess()
End Sub
End Class
Note the Async keyword in the declaration of the RunProcess sub and the Await keyword.
You run the WaitForExit in another thread and by using Await the application basically stops at this line as long as the task takes to complete.
This however also keeps your GUI reponsive meanwhile. For the example I just show the progressbar (it is invisible before) and hide it once the task is complete.
This also avoids any Application.DoEvents hocus pocus.

Background process hangs application vb.net

I have a background process:
Public Shared Function CheckForInternetConnection() As Boolean
Try
Using client = New WebClient()
Using stream = client.OpenRead("http://www.google.com")
Return True
End Using
End Using
Catch
Return False
End Try
End Function
This process ties up my application, causing it to hang until completed.
I'm trying to incorporate this into a "BackGroundWorker" and tie it to a progress bar.
However, though the application no longer hangs when the process is running, the progress bar does.
I'd like the progress to progress from 0 - 50% during the above function, however currently, the progress bar sits at 0% until the process is complete, them jumps to 50%.
Private Sub bgwLongTask_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwLongTask.DoWork
For i As Integer = 1 To 10
' If we should stop, do so.
If (bgwLongTask.CancellationPending) Then
' Indicate that the task was canceled.
e.Cancel = True
Exit For
End If
CheckForInternetConnection()
LoadingScreen.CheckForIllegalCrossThreadCalls = False
Me.Text = i
' Notify the UI thread of our progress.
bgwLongTask.ReportProgress(i * 10)
Next i
End Sub
Private Sub bgwLongTask_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwLongTask.ProgressChanged
If e.ProgressPercentage >= 0% And e.ProgressPercentage <= 50% Then
lblStatus.Text = "Checking Netowrk ..."
Else
lblStatus.Text = "Loading ..."
End If
prgPercentComplete.Value = e.ProgressPercentage
End Sub
By using:
LoadingScreen.CheckForIllegalCrossThreadCalls = False
Me.Text = i
I can see that the progress bar doesn't even start until CheckForInternetConnection() has completed.
Can someone please help me sort this out?
Much appreciated. Thanks.
You're looping from 1 to 10 calling CheckForInternetConnection() in every iteration, that doesn't makes sense. In this case the long-running process is CheckForInternetConnection() method, so you need to somehow "report progress" while the method is executing.
Possible way to notify user that the program is processing something when we can't calculate progress percentage of the process is using progress bar in indeterminate mode (by setting Style to Marquee in WinForm or setting IsIndeterminate to True in WPF).

Showing form from form_load event handler and showing progressbar

Here are some things which I don't know how to do properly in .Net but I'm sure that some solution surely exists.
I have form which is open like dialog and under (that) form_load I have some checks of data and after that I call a procedure which may take 10 seconds to process.
During this time my form is not showed until my process don't finish and I can see some 'garbage of menu' in my main form instead of progressbar in those new form which is in process.
Second thing I see and which may be close to first problem is that in some of my forms progressbar don't comes to end when results of some process is showed but program (for progressbar) is maked properly. It seem's like progressbar work in some asynchronous task.
How to fix those problems and get my form showed before my procedure starts?
How to get progressbar to show a value reliable? Or I can say beter like it works in VB6?
For first problem I try:
Me.Activate
or
Me.Refresh
Call myProcedureWithProgressBar(myArgs)
From Form_Load but without results.
Form is showed when my procedure finishes but should be showed before.
Event Form_Load executes the code before showing the Form. You can create a Timer and use this to automatically execute code after form load:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Timer1.Start()
End Sub
Private Sub OnFormLoaded(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
'...
End Sub
And make sure you stop the timer on the method like I did, and make sure you prevent user to do something that could interfiere with the execution (clicking buttons, closing form...). Hope it helps.
And about the ProgressBar issue, I don't know what could be the problem without more information or code. Maybe you can try something like this:
Public Sub UpdateProgressBar(ByVal cont As Integer, ByVal max As Integer, ByRef objTarget As ProgressBar)
Dim dProgress As Double = cont * 100 / max
objTarget.Value = CInt(dProgress)
objTarget.Refresh()
End Sub