Link to home
Start Free TrialLog in
Avatar of Jarrod
JarrodFlag for Australia

asked on

vb.net form not refreshing when changing label.text

Hello experts

Im a bit of an amateur when it comes to vb.net programming and have hit a snag with my app. I originally wrote it in .net 2003 and have since learnt a fair bit about better coding practices. One of the objectives I wanted with my recoding to the new version was to restrict my app to a single instance, which seemed easy enough by going to the app properties and turning on application framework and ticking allow single instance.

My problem became that I was using a Sub Main located in a moduile where 90% of my code is to start my app and required a windows form as my startup object. This wasnt to much of a drama as I used more or less a loading splash screen form that showed status updates as the software started (Open connection to db, run command 1, authenticate user etc). Ive got the single instance part working properly but now on my form that is the startup object whenever I do a label.text change for the status label it doesnt refresh during the applications startup procedures (At the end of starting my loader form closes and opens a main form) so it sits on the original status which is Starting App Version xxxx

So my question is should I be looking at different thread modes (Ie having a seperate thread for the status changes) to resolve this issue ? Im currently using a form.refresh command after each label.text change which worked prior to changing to the application framework mode (Ive also tried the doevents as well without success).

Additionally on a side note, can someone confirm that when im using single instance via the method listed above that it applies to the logged on users session only (I have this app on both users workstation and a terminal server or two and this would pointless for me if it only allows one copy for all currently logged on users). Any help is greatly appreciated
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Avatar of klakkas
klakkas

My friend, the solution is a lot simpler than you imagine!!!

Just add
application.doEvents
after each label update. This will allow the UI to refresh the form and (of course) update your label.

Regarding your "side note" question, I do not exactly understand what you mean. Just to make it clear for you though, single instance application means one simple thing: You cannot run this applcation 2 times concurrently. That's it, nothing more, nothing less.
1.) I think it is per user session, but using the single instance option is in most cases not an ideal solution. The single instance feature prevents a second instance from starting, but afaik there is no way to catch that event, so there is no chance for you to inform the user that in old instance of the software is running etc. If the first instance is crashing and not closing there is no chance for the user to start it again without using the Task Manager. There is also no way of transmitting information to the first instance like in a media player where you want to enqueue another song into the playlist of one instance.
But I think the single instance feature is per user, but I am not sure.
I prefer to check with the Process class manually for previous instances and optionally establishing a connection to the previous instance using TCP. If connection fails the old instance is not working any more.

2.) I think the reason for the not updating GUI is that all is happening in one thread and one thread can handle only one event at a time. If you place several seconds of code in the Form_Load event the Form will freeze for this time maybe causing a "The application is not reacting any more" message.
Try to keep the code for events short (<1s) and do all longer work in another thread, if possible.

3.) Keep in mind that .NET Forms prevent cross thread references, so you cant change the content of a GUI object in another thread that it was created. You can turn this feature off, but it is not a good idea.
I prefer having a timer that updates the information of the GUI every 100ms and a background thread that does all the work.

4.) Application.DoEvents is a hack I used in VBA, because I had no threads and was executing an endless loop. This works, but can cause side effects, because you are handling a event in an event. Maybe the event on the second level accesses data that is not valid yet or causing the handling of the first event again. You can get loops here very fast.
Andr_gin, first of all, nice explanation in your first 3 points.

Regarding 4 though, what makes you say that Application.DoEvents is a hack? I beleive it to be the recomended statement when you want your UI to refresh during a time consumming operation.

Porka is not using any threads in his startup oprations. He is executing a set of commands, one after another, updating the InfoLabel in between. So, there can be absolutely no side effects in his case.

Still Porka, for the sake of completeness, you should (in my opinion) use a BackGroundWorker to perform the loading tasks and update your UI via the BackGroundWorker.ProgressChanged event. This way, your form will be completely operational during the startup progress.

Example.
What you do now (with my suggestion):

Private sub Button1_Click
       LblStatus.text = "Loading 1..."
       Application.DoEvents
       DoSomethingFor2Secs()
       LblStatus.text = "Loading 2..."
       Application.DoEvents
       DoSomethingFor3Secs()
       LblStatus.text = "Loading 3..."
       Application.DoEvents
       DoSomethingFor4Secs()
       LblStatus.text = "Loading 4..."
       Application.DoEvents
       DoSomethingFor5Secs()
End sub

'The problem with this is that the form in iresponsive during the execution of the time consuming operations.

BackgroundWorkerApproach:

Private sub Button1_Click
       bgWorker.DoWork
End Sub

Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
       bgWorker.ReportProgress("Loading 1...")
       DoSomethingFor2Secs()
       bgWorker.ReportProgress("Loading 2...")
       DoSomethingFor3Secs()
       bgWorker.ReportProgress("Loading 3...")
       DoSomethingFor4Secs()
       bgWorker.ReportProgress("Loading 4...")
       DoSomethingFor5Secs()            
End Sub

Private Sub bgWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
       
        lblStatus.text = e.UserState.ToString

End Sub


This way your form will be fully functional and movable, throughout the initialization process.
1.) I think it is per user session, but using the single instance option is in most cases not an ideal solution. The single instance feature prevents a second instance from starting, but afaik there is no way to catch that event, so there is no chance for you to inform the user that in old instance of the software is running etc. If the first instance is crashing and not closing there is no chance for the user to start it again without using the Task Manager. There is also no way of transmitting information to the first instance like in a media player where you want to enqueue another song into the playlist of one instance.
But I think the single instance feature is per user, but I am not sure.
I prefer to check with the Process class manually for previous instances and optionally establishing a connection to the previous instance using TCP. If connection fails the old instance is not working any more.

2.) I think the reason for the not updating GUI is that all is happening in one thread and one thread can handle only one event at a time. If you place several seconds of code in the Form_Load event the Form will freeze for this time maybe causing a "The application is not reacting any more" message.
Try to keep the code for events short (<1s) and do all longer work in another thread, if possible.

3.) Keep in mind that .NET Forms prevent cross thread references, so you cant change the content of a GUI object in another thread that it was created. You can turn this feature off, but it is not a good idea.
I prefer having a timer that updates the information of the GUI every 100ms and a background thread that does all the work.

4.) Application.DoEvents is a hack I used in VBA, because I had no threads and was executing an endless loop. This works, but can cause side effects, because you are handling a event in an event. Maybe the event on the second level accesses data that is not valid yet or causing the handling of the first event again. You can get loops here very fast.
Avatar of Jarrod

ASKER

Thanks for the posts guys, I had a quick look over idle_minds article and will have a bit of a play with that approach. Klakkas, I mightnt have made it clear up top but I already tried the application.doevents but it didnt resolve the issue but I basically understand the bgworker process example you have so will try it as well and see which approach works best. Thanks andr_gin for the indepth info about the single instance info, I guess ill give it a go and see if it works or not, worst case scenario ill take that feature back out as it was more wishlist than anything else.
Good luck, let us know which approach worked best for you.
1.) Sorry double post. Is there a way I can delete my comment.

2.) Application.DoEvents can cause side effects, because events can happen out of order.

Example:

Private sub Button1_Click
       LblStatus.text = "Loading 1..."
       Application.DoEvents
       DoSomethingFor2Secs()
       LblStatus.text = "Loading 2..."
       Application.DoEvents
       DoSomethingFor3Secs()
       LblStatus.text = "Loading 3..."
       Application.DoEvents
       DoSomethingFor4Secs()
       LblStatus.text = "Loading 4..."
       Application.DoEvents
       DoSomethingFor5Secs()
End sub

If the user clicks on the Button1 after 4 seconds this event may get handled in Application.DoEvents and Button1_Click will be executed while the first instance is still running. Imagine what happens, if you are opening a file for writing in DoSomethingFor2Secs() and close it in  DoSomethingFor5Secs(). Then you will get an exception with an error message. This error will be a nasty one, because it is very hard to reproduce.

3.) Also your windows does not get repainted while DoSomethingFor5Seconds() is executing. Depending on the system settings the "application does not respond" can be triggered faster, so you have to place the DoEvents in the Sub which generates even more problems regarding code design.
All,

Please read this article "Overview of the Visual Basic Application Model":
http://msdn.microsoft.com/en-us/library/w3xx6ewx(VS.80).aspx

The StartupNextInstances() event fires for instances that are not the first:
http://msdn.microsoft.com/en-us/library/b9z4eyh8(VS.80).aspx
...and it is able to communicate with the first instance along with being able to pass the parameters passed to the multiple instances.

If you set the "Splash Screen" dropdown in Project --> Properties it actually gets displayed by the framework IN ITS OWN THREAD so threading may be involved without you explicitly creating them.  Read the article I posted in my first link:
https://www.experts-exchange.com/Programming/Languages/.NET/Visual_Basic.NET/A_3671-Updating-a-Splash-Screen-with-Loading-Progress-in-a-VB-Net-WinForms-Application.html

@Porka...

If you post your code and explain your current setup I can help you setup it up so it will work correctly in the newer version.  Obviously there is more than one way to do this AND the newer versions of VB.Net give you even more options...
Avatar of Jarrod

ASKER

Hi Idle_mind

I tried implementing your method but hit a bit of a snag, at the moment by project uses a form name frmloader as my startup form which is my splash screen form so obviously that needed to be changed, so I created a blank dummy form that is not visible, as my startup form and changed frmloader to be my splash screen as per the application properties.

The way my app used to work was frmloader opens and does a quick check to see if a command line was suppied and then start a fadein timer on the form. Once the form had finished fadein it called a sub named appstartup in a module that contains most of the code for my app. That sub then more or less did all the work and updated the frmloader status as it went through and finally opens the main form and closes the loader form. Since trying the dummy form route im getting an error for the timer on frmloader for the fadein stating: "Cross-thread operation not valid: Control 'frmLoader' accessed from a thread other than the thread it was created on", so im guessing my way of thinking was wrong in using the dummy form to solve the initial problem since it didnt even make it to the call for the starup sub Im using ? I attached the code for the timer event, and is stands the dummy form has nothing changed except its visibility for the end user. Did I just miss something easy ?
Private Sub tmrFader_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrFader.Tick

       ' Fader 

        If Me.FadeIn = True Then

            ' FadeIn

            If Me.Opacity <= 0.99 Then
                Me.Opacity = Me.Opacity + 0.05
            Else
                Me.FadeIn = False
                Me.tmrFader.Stop()
                LauncherStartup()
            End If


        Else

            ' Fadeout

            If Me.Opacity >= 0.1 Then
                Me.Opacity = Me.Opacity - 0.05
            Else
                Me.tmrFader.Stop()
                Me.Close()
            End If
        End If

End Sub

Open in new window

Avatar of Jarrod

ASKER

Mentioned the wrong sub name above, meant to be launcherstartup instead of appstartup
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
*Here's a slight modification where I update a Label on frmLoader() from the Module:

    Public Sub LauncherStartup()
        For i As Integer = 1 To 5
            System.Threading.Thread.Sleep(1000)
            frmLoader.Label1.Text = "Step #" & i
            Application.DoEvents()
        Next
        frmLoader.FadeIn = False
        frmLoader.tmrFader.Start()
    End Sub
Avatar of Jarrod

ASKER

Hi idle mind

Thanks for the indepth info, worked perfectly after changing to the second method, in the future if im creating apps ill keep in mind the different approaches