VB.NET Application freezes when minimizing - vb.net

I have a windows forms application consisting of a single form which parses a large body of text upon clicking a few buttons.
There is a huge chunk of code which parses through every node of an XML file, typically around 5000 nodes containing 20-ish child nodes, and those have even more children, and so on and so forth, just to create an image of the file's size.
While iterating through the code, as in when the data is being parsed, if I press the minimize button, it won't minimize but goes into the "not responding" state. My guess is that the minimize function can't be processed while the data is being parsed.
Is there a way to set an interrupt to the minimize/maximize/close buttons, or set priority to them, or any other solution to enable smooth minimisation of the form?
Thanks in advance

Sorry to add almost the same answer as Stephan, but waited 10 hours for him to remove the DoEvents thing.
Data gathering or parsing should be handled in another Thread. The simpliest way is to use a BackgroundWorker, and implementing at least two event handlers :
BGWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
' Move your parsing code here
' use local variables whenever possible
' do the appropriate changes
' and
BGWorker_RunWorkerCompleted( _
ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
' tells your Main thread that it can use the resuls
Note :
You must be careful not to manipulate any user-interface objects
in your DoWork event handler. Instead, communicate to the user
interface through the ProgressChanged and RunWorkerCompleted
events.
And I would add :
Everything your BGW needs in its DoWork should remain untouched (*) by any other code. The DoWork() can access anything in your main thread (ex : manipulating a class instance of FileContent, calling a Function, reading a static variable...) You shouldn't take that lightly ! This could produce unexpected behaviours due to interference like multiple Workers, states changes or User inputs.
User inputs interfering with the parsing process should be handled : use the .RunWorkerCompleted(..) to capture when your main thread can safely (let user) access and manipulate datas, change states, etc.
(*) Best move is to make everything required by your BGW as a copy of the actual values at the beginning of the work. If you have a very long task to do, and states changes could occur in the meantime (ex : filesystem hierachy + file deletion - date changing while running a code that orders results by time...) you should :
either think again, whether all the required tools are available in your code to properly complete the task (best case)
either carefully handle the .Error Property of the RunWorkerCompletedEventArgs parameter (then fix the error, and try again..)
either validate your data integrity prior to use them (worst scenario)
EDIT : BGW is not the only way. Have a look at Threads and Threading while following Managed Threading Best Practices advices.

Since you are parsing your XML in your main thread, the GUI can't update until the parsing is finish. The best option is to use the BackgroundWorker. It will start an other thread that permit you to execute the parsing of your file while leaving the GUI responsive to the user input.

Related

Stop program from freezing while copying files

I have made a game installer which copies some files from the installer to the game directory. However, when I click the button which runs the sub below, the program freezes until the action is complete.
Private Sub InstallGameFilesClicked(sender as object, e as eventargs) Handles InstallGmF.click
My.computer.filesystem.copydirectory(environment.currentdirectory & "\res\gameFiles", installDir & "\res\")
End Sub
(installDir variable is a string set by the user previously in the program and determines the place where the game is going to be installed)
What I would like to have is for the program not to freeze when the button is clicked...
Is there a way of doing this?
Thanks, Rodit
An easy way of executing tasks in the background is to use the BackgroundWorker. See How to: Use a Background Worker. The BackgroundWorker also allows you to give feedback to the UI in order to display a progress bar for instance.
You should learn how to use a background worker object. It will run the copying in a different process than the one that the main part of your program uses and thus not cause it to freeze. The background worker object can be found in the toolbox under components.
BackgroundWorker1
A word of warning, you have to know how to use it to get it to work correctly. When it is done, it will return an event to the main part of your program and you should use that to signal the user that the copy process is complete, etc. Just search for examples on how this works.

Why does my form appear behind everything?

I have a program where I need to do some initial work before calling the form, so I disabled the Application Framework setting and wrote my own Main function with a call to Application.Run(myForm) when it's time to run the form.
Everything was working fine, no problems, but now I have need of some other service before opening the form. Rather than add all that code to this program, it has all been moved into its own executable. This second program can edit files that the first program will use, so I need the first program to wait so that it will read up those changes (should they be made). I suppose could just as easily use the Shell function, but for various reasons I'm creating my own Process object and calling it/waiting on it through that.
Anyway, I make this call to the second program some time before the Application.Run call. The first program waits its turn, and I can interact with the second program successfully, no trouble at all. But when it's done, the window for the first program is hidden behind any other windows that are on the screen. This doesn't happen in XP, only in Vista (and maybe 7, but I haven't confirmed yet). I've already tried manually forcing the form to appear in front, minimize then maximize, get focus, etc, but nothing brings it to the front unless the user manually clicks on it with the mouse.
What am I doing wrong? Why does this behavior occur? I know it has something to do with waiting for the executable to finish, because if I don't force the first program to wait everything is fine (other than it not waiting). I can circumvent the issue by calling the second program in the Load event of the form, but then I have to read the file a second time to catch the changes instead of reading it once, and it also looks bad because the form is being drawn really slowly while the second program is sitting there.
If anyone has any input, I'd appreciate it.
This isn't really an answer to why you're experiencing this behaviour, but a simple workaround would be to temporarily set the form's TopMost property to True in the load event. Then, depending on how intrusive you want that to be, you could either reset it under a short timer or wait for say the MouseEnter event to fire.
There are another topic in this site about that, but I not got the link. This problem seems be a bug into .NET framework. The API below (VB.NET example) works for me in windows XP and 8.1. Don't tested in other versions of Windows.
<Runtime.InteropServices.DllImport("user32")> _
Public Shared Function SetForegroundWindow(hwnd As IntPtr) As Integer
End Function
Private Sub Form_Load(sender As Object, e As EventArgs) Handles Me.Load
SetForegroundWindow(Handle)
End Sub

Button disable and enable

I have a vb.net based windows application, where when "GO" button is clicked a bunch of data is loaded into DB. So in my application as soon as "GO" button is clicked I want to just disable it and would like to enable it back when the uploading has completed.
Now in my specific method for btnGo_Click() I have:
btnGo.Enabled = False
as first line and
btnGo.Enabled = True
as last line in the same method.
But I fail to understand why the "GO" though appears as being disabled still allows click when processing is going on. Also if I remove the last line, it gets disabled permanently and doesn't allow the click event.
Kindly suggest what am I doing wrong?
Edit (Dated: 25th Jan 2012): I made changes as suggested by our collegues, but I am facing a new issue here. I am facing an issue where the textbox gets updated but not always. I have updated my textbox in "_ProgressChanged" event of the background worker thread. In my case if there is 10 records uploaded. Then there are 10 lines of updates that are expected in the texbox. But only few lines are shown in the textbox. Is it the repaint issue again? Kindly suggest...Because all other things are done as per your suggestion
You're not doing anything wrong. The problem is that the UI doesn't get updated until the code inside of your event handler method finishes executing. Then, the button is disabled and immediately enabled in rapid sequence.
That explains why if you forget to reenable the button control at the end of the event handler method, it is still disabled—because you told it to disable the button in the first line of the method.
This is a classic case of why you should never perform long-running computational tasks inside of an event handler method, because it blocks the UI from being updated. The computation actually needs to happen on a separate thread. But don't try to create the thread manually, and definitely don't try to update your UI from a separate thread. Instead, use the BackgroundWorker component to handle all of this for you automatically. The linked MSDN documentation has a great sample on how to use it.
Disable the button before starting the BackgroundWorker, and then re-enable it in its Completed event, signaling the completion of your database load.
Since you're trying to execute a function that can take some time, I'd advise you to make use of threading. In .NET there's a BackgroundWorker component which is excellent for performing tasks asynchronous.
On button click, invoke the BackgroundWorker like this:
if not bgwWorker.IsBusy then
btnGo.enabled = false
bgwWorker.RunWorkerAsync()
end if
And use the completed event to enable the button again:
Private Sub bgwWorker_DoWork(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles bgwWorker.DoWork
' Do your things
End Sub
Private Sub bgwWorker_RunWorkerCompleted(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles bgwWorker.RunWorkerCompleted
' Called when the BackgroundWorker is completed.
btnGo.enabled = true
End Sub
In the example above, I've used bgwWorker as the instance of a BackgroundWorker.
The button click event is handled as soon as the UI thread has idle time.
After you disable your button, the UI thread is keept busy by your code. At the end of your method, you re-enable the button, and after that you exit the method and allow for idle time.
As a consequence, the button will already be enabled at the point in time where the click event is handled, so your click is "recognized".
The solution is, as others already suggested, to use a Backgroundworker.
Dont try to use doEvents() as a solution (never do), since this would be prone to introduce other subtle problems. That said, you can prove the above explanation with some experimental doEvents in your code. You will see that the click is discarded if a doEvents is performed before the button gets re-enabled. On the other hand, performing a doEvents directly after the button.disable (to "update the GUI") will not help if it is executed before the click.
If your btnGo_Click() is ran inside main thread, UI could not be updated correctly inside a time-consuming task.
The best way you can do what you need is running your method in a BackgroundWorker.
I just tried disabling a button, Updateing the form, Sleeping, and enabling it again. It still performed the click (A click that was done while it "slept" with the button disabled) after it was enabled.
I guess forms "remember" clicks.
(EDIT: I did this in C#.)
It's usually not a good idea to manage the state of a submit button. Instead, perform validation on submit.
I would like to add 2 enhancements to the answer generally described here which is to 'do the work in another thread'.
Ensure button.enable=true always gets called
1.a. You should use a try block in button_click . If there is an error in launching the thread, CATCH should re-enable the button.
1.b. The task complete call back should also ensure the button is enabled using try/catch/finally
1.c The task timeout should also re-enable the button
A common error based on exactly the situation described here is rapid-clicker-person clicks the button twice in rapid succession.
This is possible because its possible, even if unlikely, that 2 click messages get queued and processed before the button is disabled. You can not assume the events happen synchronously.
IMHO a best practice is to use a static variable. Initialize it to 0. Set it to one as the very first statement and set it to 0 following the guidelines in POINT 1.
The second statement in button click should simply RETURN/EXIT if the value > 0
If you are using a worker thread, the static variable may have to be located in that code. I would not advise making it a form level variable.
I had a slightly different issue not being able to call click.
I have a routine that turns the UI on/off based on a validation routine.
i will say that I disagree w/ the suggestion to do validation in the submit. The button should not be enabled if we are able to tell the form is invalid.
My issue was that I was calling the validation from several places. One of the calls was the CustomCellDraw event of a grid which was firing very frequently.
So while it appeared that I was simply disabling/enabling the button a few times, I really was doing this almost continually.
I was able to trouble shoot by placing a label on the form and kind of doing a console.log thing. I immediately realized button.Enabled was flickering, which led me down the correct trouble shooting path.
I realize this addresses a different root cause than op described. But it does address the problem the op describes.

VB.NET Form.Show from another thread hanging form

I have a series of methods being called for my networking code. An event gets fired from the networking thread. Inside this event, which I've hooked into from a singleton class, I route messages to form level methods which they register on form load to handle certain messages they care about. Inside of these form message hooks I need to close the current form (which I was able to do) but also show a different one (which is giving me the trouble).
The new form sort of shows but it's hanging/not updating. I'm sure this has something to do with that form because it's .Show() was basically called from another thread (sort of) doesn't have a message loop, but I'm not sure how else to solve this. The network message that gets received indicates on the client machine what forms to close and show.
The flow might be confusing so I'll try better to explain.
Login form attaches user defined functions inside that form to a singleton class list of messages. For example when a message called LOGIN_STATUS is fired I assign a function from the Login form to a list defined in this singleton class.
The singleton class has the network class defined in it which actually runs on another thread, but this is all handled inside the class. In the private ctor I subscribe to the OnData event of this network class.
When OnData gets fired from the network class to the singleton class it passes to it the type of data. I loop through the list of function pointers to see if any of them are linked to LOGIN_STATUS and if so call them. This will call the Login forms function. Inside that function I need to close the Login form and open the Lobby form. That's when the Lobby form shows, but is hung up and not updating.
Hope that makes sense.
This is all being done in VB.NET where I have the "close when last form closed" setting on which is what I want. VB.NET also makes it easier to manage forms since I can just for formname.Show() instead of having to keep a list of the forms and manage them myself like in C# so if that's still possible with the solution that would be ideal.
If you want to ensure all forms are created on the same thread, and hence the same message loop, use the main from's Invoke method. The Form.Invoke and Form.BeginInvoke methods cause the code to run from the form's message loop. BeginInvoke allows the event calling thread to return immediately, where-as Invoke blocks the event thread until the method is complete. It depends how time sensitive your code is.
Private Sub OpenFormEvent(sender As Object, e As EventArgs)
If MainForm.InvokeRequired Then
Dim args As Object() = {sender, e}
MainForm.BeginInvoke(New EventHandler(AddressOf OpenFormEvent), args)
Else
Dim SecondForm As New Form()
SecondForm.Show()
End If
End Sub

Force multi-threaded VB.NET class to display results on a single form

I have a windows form application that uses a Shared class to house all of the common objects for the application. The settings class has a collection of objects that do things periodically, and then there's something of interest, they need to alert the main form and have it update.
I'm currently doing this through Events on the objects, and when each object is created, I add an EventHandler to maps the event back to the form. However, I'm running into some trouble that suggests that these requests aren't always ending up on the main copy of my form. For example, my form has a notification tray icon, but when the form captures and event and attempts to display a bubble, no bubble appears. However, if I modify that code to make the icon visible (though it already is), and then display the bubble, a second icon appears and displays the bubble properly.
Has anybody run into this before? Is there a way that I can force all of my events to be captured by the single instance of the form, or is there a completely different way to handle this? I can post code samples if necessary, but I'm thinking it's a common threading problem.
MORE INFORMATION: I'm currently using Me.InvokeRequired in the event handler on my form, and it always returns FALSE in this case. Also, the second tray icon created when I make it visible from this form doesn't have a context menu on it, whereas the "real" icon does - does that clue anybody in?
I'm going to pull my hair out! This can't be that hard!
SOLUTION: Thanks to nobugz for the clue, and it lead me to the code I'm now using (which works beautifully, though I can't help thinking there's a better way to do this). I added a private boolean variable to the form called "IsPrimary", and added the following code to the form constructor:
Public Sub New()
If My.Application.OpenForms(0).Equals(Me) Then
Me.IsFirstForm = True
End If
End Sub
Once this variable is set and the constructor finishes, it heads right to the event handler, and I deal with it this way (CAVEAT: Since the form I'm looking for is the primary form for the application, My.Application.OpenForms(0) gets what I need. If I was looking for the first instance of a non-startup form, I'd have to iterate through until I found it):
Public Sub EventHandler()
If Not IsFirstForm Then
Dim f As Form1 = My.Application.OpenForms(0)
f.EventHandler()
Me.Close()
ElseIf InvokeRequired Then
Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
Else
' Do your event handling code '
End If
End Sub
First, it checks to see if it's running on the correct form - if it's not, then call the right form. Then it checks to see if the thread is correct, and calls the UI thread if it's not. Then it runs the event code. I don't like that it's potentially three calls, but I can't think of another way to do it. It seems to work well, though it's a little cumbersome. If anybody has a better way to do it, I'd love to hear it!
Again, thanks for all the help - this was going to drive me nuts!
I think it is a threading problem too. Are you using Control.Invoke() in your event handler? .NET usually catches violations when you debug the app but there are cases it can't. NotifyIcon is one of them, there is no window handle to check thread affinity.
Edit after OP changed question:
A classic VB.NET trap is to reference a Form instance by its type name. Like Form1.NotifyIcon1.Something. That doesn't work as expected when you use threading. It will create a new instance of the Form1 class, not use the existing instance. That instance isn't visible (Show() was never called) and is otherwise dead as a doornail since it is running on thread that doesn't pump a message loop. Seeing a second icon appear is a dead give-away. So is getting InvokeRequired = False when you know you are using it from a thread.
You must use a reference to the existing form instance. If that is hard to come by (you usually pass "Me" as an argument to the class constructor), you can use Application.OpenForms:
Dim main As Form1 = CType(Application.OpenForms(0), Form1)
if (main.InvokeRequired)
' etc...
Use Control.InvokeRequired to determine if you're on the proper thread, then use Control.Invoke if you're not.
You should look at the documentation for the Invoke method on the Form. It will allow you to make the code that updates the form run on the thread that owns the form, (which it must do, Windows forms are not thread safe).
Something like
Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)
Public sub UpdateStatus(ByVal newStatus as String)
If Me.InvokeRequired Then
Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus)
Me.Invoke(d,new Object() {newStatus})
Else
'Update the form status
End If
If you provide some sample code I would be happy to provide a more tailored example.
Edit after OP said they are using InvokeRequired.
Before calling InvokeRequired, check that the form handle has been created, there is a HandleCreated property I belive. InvokeRequired always returns false if the control doesn't currently have a handle, this would then mean the code is not thread safe even though you have done the right thing to make it so. Update your question when you find out. Some sample code would be helpful too.
in c# it looks like this:
private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(StatusHandler, sender, e);
}
else
{
//do work
}
}