Hans Passant gave me a great answer here, so I thought of asking for more details to try to understand the way Application.Run() works.
As far as I understand from the docs, it seems that Application.Run() starts a message loop on the current thread, which in turns enables it to process user input (Is that right?). The overloaded version Application.Run(Form) basically does the same, only it exists when the form closes, and it shows the form by default.
That raises a few questions:
How would one do to simply call from the Main() sub a function that can communicate with the user to (message boxes and so on) and wait for it to exit?
When the message loop is started without a form, how do you launch a new form from this loop, and wait for it to exit? ShowDialog could work, unless you don't want the form to display immediately when launched (eg. if you have a for that's launched minimized to the system tray)
Basically, the situation would be as follows: sub `Main` has a list of tasks to execute in 20mn, with a system tray icon telling the user that the program will operate in 20mn. A timer ticks after 20mns, and has to execute say approx. 15 tasks one by one, every time creating an instance of a progress dialog, initially hidden in the taskbar.
`ShowDialog` would display the form, which is not wanted; so the way I would do it would be to pass the progress dialog a callback to a function that starts the next task. But that wouldn't exit the first progress form before the second has exited, would it? Which means 15 forms would end up being opened...
So the solution may be to invoke (begininvoke?) the callback on the main application loop... Only, I don't know how to do this, because I don't have a form associated with the loop to invoke the callback on...
I hope my questions are clear (I might confuse many things, sorry),
Thanks,
CFP.
Drop a Timer, ProgressBar and a BackgroundWorker on the form. First thing you'll want to do is to prevent the form from getting visible when the program is started. Paste this code into the form class:
Protected Overrides Sub SetVisibleCore(ByVal value As Boolean)
If Not Me.IsHandleCreated Then
value = False
Me.CreateHandle
End If
MyBase.SetVisibleCore(value)
End Sub
Use the timer to get the job started. Set its Interval and Enabled properties, add the Tick event handler:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Me.Show()
ProgressBar1.Visible = True
Me.Enabled = False
BackgroundWorker1.RunWorkerAsync()
End Sub
That makes the form visible when the job is started and starts the background worker. Set the BGW's WorkerReportsProgress property to True and add the 3 event handlers:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'' Do stuff here, call BackgroundWorker1.ReportProgress to update the PB
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
ProgressBar1.Visible = False
Me.Enabled = True
Me.Hide()
End Sub
It is up to you to fill in the code for the DoWork event handler. Have it do those 15 jobs, be sure to call BackgroundWorker1.ReportProgess so that the progress bar gets updated. Which is what the ProgressChanged event handler does. The RunWorkerCompleted event handler hides the form again.
You can call the Show() method in the context menu item event for the NotifyIcon so that the user can make your form visible again. Call Application.Exit() in the context menu item that allow the user to quit your app. Make sure you disable that when the BGW is running. Or implement a way to cleanly stop the job.
Related
I use the following code in my form:
Public Class Form1
Private Sub Form1_ResizeEnd(sender As Object, e As EventArgs) Handles MyBase.ResizeEnd
MsgBox("Resized")
End Sub
End Class
When I move my form, it also seems to trigger MyBase.ResizeEnd. Why is that? A move of the panel doesn't change the size, so I don't understand why.
Why does a form move trigger ResizeEnd?
Because this is the documented behavior. From the documentation:
The ResizeEnd event is also generated after the user moves a form, typically by clicking and dragging on the caption bar.
If you want an event that doesn't get triggered when the form is moved, you should use either Resize or SizeChanged. The problem with those two events is that they will be triggered while the form is being resized by the user. To work around that, you may use it with both ResizeBegin and ResizeEnd with a couple of flags to signal when the user actually finishes resizing the form.
Here's a complete example:
Private _resizeBegin As Boolean
Private _sizeChanged As Boolean
Private Sub Form1_ResizeBegin(sender As Object, e As EventArgs) Handles MyBase.ResizeBegin
_resizeBegin = True
End Sub
Private Sub Form1_SizeChanged(sender As Object, e As EventArgs) Handles MyBase.SizeChanged
' This is to avoid registering this as a resize event if it was triggered
' by another action (e.g., when the form is first initialized).
If Not _resizeBegin Then Exit Sub
_sizeChanged = True
End Sub
Private Sub Form1_ResizeEnd(sender As Object, e As EventArgs) Handles MyBase.ResizeEnd
_resizeBegin = False
If _sizeChanged Then
_sizeChanged = False
MessageBox.Show("The form has been resized.")
End If
End Sub
One thing to note is that both ResizeBegin and ResizeEnd are only triggered when the user manually resizes* the form. It does not, however, handle other situations like when the form is resized via code, when the form is maximized, or restored.
* or moves the form, which is the part that we're trying to avoid here.
I wanted to create an application with two forms, a main one called Source and a subsidiary called Viewer. Using buttons on each form, the user could switch to the other, even if that form had previously been closed. So I set my program to Close When Last Form Closes. Moreover, I added message boxes when each form closed, just to check that the user was sure.
But now my application won't exit after I close both forms! It shows up in Task Manager, and if I run it in MSVS, the debugger never stops! When each form is closed, My.Application.Forms returns an empty collection. And if I force quit it using End/Application.Exit, the program still quits — see the MWE below. What do I do?
MWE
Create a blank VB.NET Windows Forms project. In the "Application" tab of your project's settings, choose "Close When Last Form Closes." In the designer, create a Form Source with one button.
Public Class Source
Public Sub Switch(sender As Object, e As EventArgs) Handles Button1.Click
My.Forms.Viewer.Show()
End Sub
Public Sub Free(sender As Object, e As FormClosingEventArgs) Handles Me.Closing
e.Cancel=(MsgBoxResult.Cancel=MsgBox("Are you sure?"))
End Sub
'Public Sub Test(sender As Object, e As EventArgs) Handles Me.Closed
'Debug.Assert(Not My.Application.Forms.Count))
'If the next line is uncommented, the application will close, like we want
'If Not My.Forms.Viewer.Visible Then Application.Exit()
'End Sub
End Class
Then create an identical form called Viewer.
Public Class Viewer
Public Sub Switch(sender As Object, e As EventArgs) Handles Button1.Click
My.Forms.Source.Show()
End Sub
Public Sub Free(sender As Object, e As FormClosingEventArgs) Handles Me.Closing
e.Cancel=(MsgBoxResult.Cancel=MsgBox("Are you sure?"))
End Sub
'Public Sub Test(sender As Object, e As EventArgs) Handles Me.Closed
'Debug.Assert(Not My.Application.Forms.Count))
'If the next line is uncommented, the application will close, like we want
'If Not My.Forms.Source.Visible Then Application.Exit()
'End Sub
End Class
Try closing any form last. Neither will cause the app to quit.
Don't use Form.Closing and Form.Closed
In MSDN's page on Form.Closing, it remarks:
Caution
The Closing event is obsolete in the .NET Framework version 2.0 [and above]; use the FormClosing event instead.
The events were deprecated because
The Form.Closed and Form.Closing events are not raised when the Application.Exit method is called to exit your application. If you have validation code in either of these events that must be executed, you should call the Form.Close method for each open form individually before calling the Exit method.
The MSDN documentation nowhere states this, but the reverse is true: in order for your form to signal to the VB.NET runtime that the form closed, Form.Closing and Form.Closed cannot be subscribed to.
If you replace Me.Closing with Me.FormClosing and Me.Closed with Me.FormClosed in your MWE, it works.
I am making a custom control that looks like this (there is a label on the right of the button):
I want the user to be able to define what the button does in his code, but still execute some code on the click event on top of the user's code.
What I want to execute :
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Button1.Enabled = False
Label1.ForeColor = Color.Gray
Label1.Text = "In Progress"
End Sub
then after this is executed the user's on click event would trigger.
How can I achieve this?
With "the user" you mean a developer which uses that user control, right? Well, the most common thing is to raise an event which the next developer can use to implement his own logic. This is exactly the same as you do - you use the Click-Event of the button.
So basically, take your code and add the RaiseEvent below:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Button1.Enabled = False
Label1.ForeColor = Color.Gray
Label1.Text = "In Progress"
' this does not affect your code but provides a "hook" for
' other developers
RaiseEvent OnButtonClick(Button1)
End Sub
Now you need to define the event itself like this ...
Public Event OnButtonClick(ByVal sender As Control)
... btw, you can pass other stuff (or nothing at all) as arguments. Sending the button as sender is just a habit.
A developer using your user control can attach a so called "Handler" to implement code as soon as the button was clicked, for example:
AddHandler UserControl1.OnButtonClick, AddressOf OnUserControlButtonClick
This code line should only be executed once, so typically it is placed in the Form_Load event.
Now, in this case the button click is routed to a method called OnUserControlButtonClick() which meets the signature of the event: that means it has one argument which is the sender.
Private Sub OnUserControlButtonClick(ByVal sender as Control)
' custom logic here ...
End Sub
There are so many examples on the web, you could start here.
I have used three timers in my program. All are working fine except the third one. i don't know why?
Private Sub Timer3_Tick(sender As Object, e As EventArgs) Handles Timer3.Tick
MessageBox.Show("dont repeat please")
Timer3.Stop()
End Sub
In the form_load i had started the timer, but its showing message box again and again at the interval of 3000ms which i have set. Please help.
Unless there is more to it a quick solution is to just move the call stop the timer to before the message box.
Private Sub Timer3_Tick(sender As Object, e As EventArgs) Handles Timer3.Tick
Timer3.Stop()
MessageBox.Show("dont repeat please")
End Sub
Basically the processing of the code in the Timer3_Ticket sub is being blocked by the display of the message box. The timer running on another thread will continue to raise tick events at each time interval until a message box is closed and the stop method is called.
I'm having a application with quite a few tabpages. One function of my program is that if the user leaves the application for more than 3 minutes, I display the main tabpage where the most relevant information is shown (this is done by a timer called "back_to_main_tab"). So far so good. However in a few tabpages you can enter text and "take a pause" for more than these 3 minutes, and if that happens, the user is taken back to the main tab, without his/her consent, erasing his/her entered text.
I realize that this could be solved by enabling/disabling the back_to_main_tab-timer at strategic places, but I would like to solve it by resetting the timer each time a click is registered in the application regardless of what is is clicked. The reason for this is that the problem isn't unique for a certain tabpage, so I would like to have a "all-purpose"-fix for all tabpages.
Is this possible?
Thanks in advance
Jonas
You can write an Event Handler for each element where user can write.
For example
Private Sub TextBox1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles TextBox1.KeyDown
'Reset timer
End Sub
(and you could try KeyUp or KeyPress too).
So you can reset timer each time the user write something somewhere.
Btw if the 3mins pass (after last KeyPress), the back_to_main_tab erase all.
EDIT
Ok so you could make one function to Manage the CLICK event. And attach the function to all items form.
To attacch an event handler to alla element you could try something like this
Private Sub AddGenericEventHandler()
For Each Ctrl As Item In Form.Items
AddHandler Ctrl.Click, AddressOf MyClickHandler
Next
End Sub
And write the function that reset your timer
Public Sub MyClickHandler(ByVal sender As System.Object, ByVal e As System.EventArgs)
'Timer Reset
End Sub
This is an Idea. You have to write correctly the FOR EACH to catch all form items do you need.