Show a loading screen in vb.net - vb.net

I need to show a screen or something, saying 'Loading' or whatever while long process are working.
I am creating a application with the Windows Media Encoder SDK and it takes awhile to initialize the encoder. I would like for a screen to pop up saying 'Loading' while it is starting the encoder, and then for it to disappear when the encoder is done and they can continue with the application.
Any help would be appreciated. Thanks!

Create a Form that will serve as the "Loading" dialog. When you're ready to initialize the encoder, display this form using the ShowDialog() method. This causes it to stop the user from interacting with the form that is showing the loading dialog.
The loading dialog should be coded in such a way that when it loads, it uses a BackgroundWorker to initialize the encoder on a separate thread. This ensures the loading dialog will remain responsive. Here's an example of what the dialog form might look like:
Imports System.ComponentModel
Public Class LoadingForm ' Inherits Form from the designer.vb file
Private _worker As BackgroundWorker
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
_worker = New BackgroundWorker()
AddHandler _worker.DoWork, AddressOf WorkerDoWork
AddHandler _worker.RunWorkerCompleted, AddressOf WorkerCompleted
_worker.RunWorkerAsync()
End Sub
' This is executed on a worker thread and will not make the dialog unresponsive. If you want
' to interact with the dialog (like changing a progress bar or label), you need to use the
' worker's ReportProgress() method (see documentation for details)
Private Sub WorkerDoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
' Initialize encoder here
End Sub
' This is executed on the UI thread after the work is complete. It's a good place to either
' close the dialog or indicate that the initialization is complete. It's safe to work with
' controls from this event.
Private Sub WorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Me.DialogResult = Windows.Forms.DialogResult.OK
Me.Close()
End Sub
End Class
And, when you're ready to display the dialog, you would do so like this:
Dim frm As New LoadingForm()
frm.ShowDialog()
There are more elegant implementations and better practices to follow, but this is the simplest.

There are many ways you could do this. The most simple might be to show a modal dialog, then kick off the other process, once it is completed you an then close the displayed dialog. You will need to handle the display of the standard X to close though. However, doing that all in the standard UI thread would lock the UI until the operation is complete.
Another option might be to have a "loading" screen that fills your default form, bring it to the front, then trigger the long running process on a secondary thread, once it is completed you can notify the UI thread and remove the loading screen.
Those are just a few ideas, and it really depends on what you are doing.

Two things you can try.
After setting your label (as mentioned in the comment to Mitchel) call Application.DoEvents()
Another option you have is to run the initialization code for the encoder in a BackgroundWorker process.

Related

Most efficient way to programmatically update and configure forms and controls

I am looking for a way to prevent user form controls from appearing one by one when I'm programmatically adding them and for ways to enhance application performance and visual appeal.
Say, I have a Panel_Top in which I programmatically add comboboxes. What is happening is that they appear one by one as they are created and I am looking for a way to suspend the refreshing of the panel and or user form to make all of those programmatically added comboboxes to appear at the same time and faster than it happens right now.
I've tried suspendlayout which doesn't do anything for me or maybe I'm doing it wrong.
MyForm.PanelTop.SuspendLayout = true
And also I've tried to set the Panel_Top to invisible like:
MyForm.Top_Panel.visible = false
Which kind of sorta looks and performs better, or it might be a placebo.
What is the correct approach to this problem?
PS: I do have form set to doublebuffer = true, if that matters
What I tend to do is create a loading modal to appear on top of the form rendering the controls that need to be created/made visible, this can optionally have a progress bar that gets incremented as the control is created/shown. With the loading modal running, the container that needs to add the controls starts with SuspendLayout, adds the controls, and then finished with ResumeLayout.
This makes it so that controls are added/shown while giving the user a visual indicator that something is going on behind the scenes.
Here is a phenomenal example of a loading modal: https://www.vbforums.com/showthread.php?869567-Modal-Wait-Dialogue-with-BackgroundWorker and here is an example of using it:
Private ReadOnly _controlsToAdd As New List(Of Control)()
Private Sub MyForm_Show(sender As Object, e As EventArgs) Handles MyBase.Shown
Using waitModal = New BackgroundWorkerForm(AddressOf backgroundWorker_DoWork,
AddressOf backgroundWorker_ProgressChanged,
AddressOf backgroundWorker_RunWorkerCompleted)
waitModal.ShowDialog()
End Using
End Sub
Private Sub backgroundWorker_DoWork(sender As Object, e As DoWorkEventArgs)
Dim worker = DirectCast(sender, BackgroundWorker)
For index = 1 To 100
_controlsToAdd.Add(New ComboBox() With {.Name = $"ComboBox{index}"})
worker.ReportProgress(index)
Threading.Thread.Sleep(100) ' Zzz to simulate a long running process
Next
End Sub
Private Sub backgroundWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
Dim percentageCompleted = e.ProgressPercentage / 100
' do something with the percentageCompleted value
End Sub
Private Sub backgroundWorker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
PanelTop.SuspendLayout()
PanelTop.Controls.AddRange(_controlsToAdd.ToArray())
PanelTop.ResumeLayout()
End Sub
SuspendLayout() is the correct way to handle this with WinForms.
But first of all, this is a function you call, and not a flag you set.
Secondly, don't forget to call ResumeLayout() at the end of the changes.
Finally, you need to ensure you only call them once when you start to change around the controls in the panel and then again at the very end. If you use them with every control you won't get any benefit.
So the pattern might look something like this:
Public Sub SomeMethod()
PanelTop.SuspendLayout() ' Prevent the panel from updating until you've finished
' Make a bunch of changes
PanelTop.Controls.Clear()
For Each ...
PanelTop.Controls.Add( ... )
Next
PanelTop.ResumeLayout() ' Allow the panel to show all the changes in the same WM_PAINT event
End Sub
You also need to ensure you don't have anything in there like DoEvents()/Invalidate() that might invoke the windows message loop and cause the form to redraw itself.

VB.net PrintForm Not Working in New Thread

I am developing a e-filing app and I need to print an adhesive label with some info to attach to the physical folder.
I already designed the label as a Form put the logo and everything that I need there. Then on the Form.Shown event I put the command to print:
Me.PrintLabelForm.Print() (This is VisualStudio PowerPack Control)
And here is where I bump into a problem. The print out is totally empty (I already changed margins setup the printer, etc). The issue is that the form is not actually fully loaded, I switch the method to the print preview and the controls are there but they are empty.
I tried several approaches but I have been not able to do this automatically. One solution that I found was to have a button to do the Me.PrintLabelForm.Print() then it works because the form is already fully loaded and displayed but this is not an option. I need the form to open automatically, print and close.
An option that I think it should work will be to have a new thread with a timer then printing so I did this:
Private Sub LabelPrint_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub LabelPrint_Shown(sender As Object, e As EventArgs) Handles Me.Shown
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Left = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Right = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Top = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Bottom = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Landscape = True
Dim PrintThread As New System.Threading.Thread(AddressOf PrintSub)
PrintThread.Start()
End Sub
Private Sub PrintSub()
Threading.Thread.Sleep(1000)
Me.PrintLabelForm.Print()
Me.Close()
End Sub
The idea was to have the PrintSub to give the app enough time to finish to render the whole thing then print but I am getting this error:
**An unhandled exception of type 'System.Exception' occurred in Microsoft.VisualBasic.PowerPacks.dll
Additional information: The window being printed must be visible and contain focus.**
So I wonder how to make this thread have the window form in focus in order to be able to print.
That is all. Thanks for all the help.
Always work with the form only from main thread.
You found it right – form printing will not run from new thread.
When you do any actions on forms, you must perform all the work from Dispatcher thread. It is the thread on which all event methods run. If you fail doing so, you can encounter many side effects. (Not only problem with printing. I've been there and this advice from senior Windows programmer helped me to get things back to normal.) So do not use form printing from any other thread.
If you want a workaround for this, print form to the image (in main thread) and then you can print the image using new thread.
This has nothing to do with .NET, this is related to internals of Windows Forms technology. Welcome to Windows programming.
I manage to solve it putting this line in the Form.Shown
PrintLabelForm.Print(Me, PrintForm.PrintOption.ClientAreaOnly)
I don't know why or how but it works.
Thanks to all of you guys for your help. Let's hope I don't find myself trying to do stuff when the form is fully displayed.
This is my full code let's hope it works for someone else:
Imports Microsoft.VisualBasic.PowerPacks.Printing
Public Class PrintAdhesiveLabel
Private Sub LabelPrint_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub LabelPrint_Shown(sender As Object, e As EventArgs) Handles Me.Shown
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Left = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Right = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Top = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Bottom = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Landscape = True
PrintLabelForm.Print(Me, PrintForm.PrintOption.ClientAreaOnly)
Me.Close()
End Sub
End Class
Perhaps this is relevant:
Only the form that currently has focus can be printed by using this
method. If you have set the Form property to another form before
calling this method, the image of the form may not be rendered as
expected. To avoid this, call the Focus method of the form before you
call Print.
So call Me.PrintLabelForm.Focus() before calling Me.PrintLabelForm.Print():
Private Sub PrintSub()
Threading.Thread.Sleep(1000)
Me.PrintLabelForm.Focus()
Me.PrintLabelForm.Print()
Me.Close()
End Sub

Is there a way to extend splash screen into form shown event?

I recently was dealing with this error: BeginInvokeStackflowError
I am using threading,and according to my research it is because within the threading .start() event it calls .invoke. If that is done in the mainform_Load event, before it is ready, then you get a BeginInvoke error.
So I've move my code from the load to the shown event. However, there is a lot of stuff going on in the background that I don't want the user to see. Is there a way in my code to extend the splashscreen I have to wait until the mainwindow shown is finished for only the first time?
Private Sub MainWindow_Shown(sender As Object, e As EventArgs) Handles Me.Shown
'update table /search network
updateTable()
'clean
cleanupTable()
'fix label
updateLabel()
End Sub
Your app can be started other than the default "MainForm" method provided by the VB Application Framework. This will use a Sub Main as the starting point allowing you to control what forms show and when, and what happens before that:
' IF your form is declared here, it will be
' available to everything. e.g.:
' Friend mainfrm As Form1
Public Sub Main()
' this must be done before any forms/controls are referenced
Application.EnableVisualStyles()
' the main form for the app
' just "mainfrm = New Form1" if declared above
Dim mainfrm As New Form1
' eye candy for the user
Dim splash As New SplashScreen1
splash.Show()
' your code here to do stuff.
' you can also invoke your procedures on the main form
' mainfrm.FirstTimeSetup()
' for demo purposes
System.Threading.Thread.Sleep(2500)
' close/hide the splash once you are done
splash.Close()
' see note
' start the app with the main form
Application.Run(mainfrm)
End Sub
Add a module to the project, typically "program"
Add your Sub Main
Go to Project Properties, uncheck use Application Framework
Select Sub Main in the the StartUp object drop down
If you declare the splash screen as Friend at the top, you can truly extend it until all the form's load event is complete and close it there/then.
There is no way to extend the splash screen if you've implemented it using the project settings, however you can use the splash screen form as the initial form instead of your main form. As for waiting until the thread has finished to show the form (or hide the splash screen) consider using a public Boolean in the main form and change it to True once the thread has completed. You can use a timer on the splash screen to check for this Boolean change and then change the form's opacity back to 1.

Timer does not work

I try to run a timer from my winform application. For some reason the function that should run on the timer's tick (IsTimeOffsetValid) is not called nor stopped on break point, and basically nothing happens. I attached a code sample below.
I appreciate the help.
Module Module1
Sub main()
Dim OutputForm As New Form17
Application.Run(OutputForm)
End Sub
End Module
Public Class Form17
Private TimerServerOffset As New System.Timers.Timer
Private Sub Form17_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
AddHandler TimerServerOffset.Elapsed, New System.Timers.ElapsedEventHandler(AddressOf IsTimeOffsetValid)
TimerServerOffset.Interval = 1
TimerServerOffset.Start()
End Sub
Private Sub IsTimeOffsetValid()
MsgBox("IsTimeOffsetValid")
End Sub
End Class
Apart from errors in the code that you posted there are other issues with the design.
Read this question: System.Timers.Timer vs System.Threading.Timer
The callback is called on a worker thread (not the UI thread) so displaying a message box could be a big problem.
then switch to a more fitting timer. If all you want to do is validate the inputs every second, switch to the System.Windows.Forms.Timer. The tick handler runs on the UI thread so you can change the UI in the handler.
Then consider changing the interval a message box popping up every millisecond is not possible and not user friendly.
Finally, I would suggest NOT using a timer for this: just handle changes to the input fields and respond to changed inputs or use the standard validation events of the WinForms controls. This is much cheaper (on the CPU) and will not mess with the focus.

Using thread to open form

I am currently studying VB.NET and I got question about using thread to open the form.
For example, when I click open button, then tread will start and open another form to adding or changing data.
Therefore, I tried to implement this part such as
Private Sub menu_Click(sender As Object, e As EventArgs) Handles menu.Click
Dim A As System.Threading.Thread = New Threading.Thread(AddressOf Task_A)
A.Start()
End Sub
Public Sub Task_A()
frmBuild.Show()
End Sub
However, I am getting error to open the frmBuild by thread. Do I need to use other method to open form?
And, How can we kill the thread when fromBuild closes?
This is almost always a bad idea. You shouldn't try to use a separate thread to open a Form - instead, open all of your forms on the main UI thread, and move the "work" that would otherwise block onto background threads. BackgroundWorker is a common means of handling the work.
That being said, if you need to do this for some unusual reason, you need to do two other things.
First, you need to set the apartment state of that thread. You also need to use Application.Run to display the form, and that form must be created on the proper thread:
Private Sub menu_Click(sender As Object, e As EventArgs) Handles menu.Click
Dim th As System.Threading.Thread = New Threading.Thread(AddressOf Task_A)
th.SetApartmentState(ApartmentState.STA);
th.Start()
End Sub
Public Sub Task_A()
frmBuild = New YourForm() ' Must be created on this thread!
Application.Run(frmBuild)
End Sub
In order to close the Form from the other thread, you can use:
frmBuild.BeginInvoke(New Action(Sub() frmBuild.Close()))
And, How can we kill the thread when fromBuild closes ?
The thread will automatically shut down when the form is closed, if it's written as shown above.