Better way to kill a Form after the FormClosing event is overridden to hide rather than close? - vb.net

I have a simple Windows Form that hosts property controls at runtime. When the user clicks Close [X] I want to keep the window and its contents alive rather than killing it by handling the FormClosing event, canceling the event and simply hiding the form.
That's fine but at close of the application I need to actually close the window. I implemented the below but it feels kludgey. Is there a simpler, more clever way to handle this situation? (The form's controller calls KillForm explicitly after it receives a closing event from the main window.)
Friend Class HostForm
Private _hideInsteadOfClosing As Boolean = True
Private Sub HostForm_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) _
Handles Me.FormClosing
If _hideInsteadOfClosing Then
Me.Hide()
e.Cancel = True
End If
End Sub
Public Sub KillForm()
_hideInsteadOfClosing = False
Me.Close()
End Sub
End Class

You can examine the value of the CloseReason property of the event args object. If it is UserClosing, hide the form, otherwise close it. For all possible values of this property, check the CloseReason enumeration documentation.

Related

Windows form actions like close through custom user control code?

I have made a title bar (custom user control) that contains five controls. They are all labels but each one do different "job".
For example, one of them is an exit form button. If I put a click event into my custom user control's code, for example...
Private Sub ExitButton_Click(sender As Object, e As EventArgs) Handles ExitButton.Click
Close()
End Sub
I get this error...
BC30451 'Close' is not declared. It may be inaccessible due to its protection level.
On the other hand I can't put it into my project's code cause it can't find ExitButton as "isolated" control and do close().
Any suggestions? I also want to do the same thing with minimize, maximize etc.
Let me guess; your button is in the user control. You try to call Close() on the UserControl class, which obviously is not a window and does not have it.
There are three solutions:
Use the ParentForm property and call Close() on it (e.g. ParentForm.Close()). Easy but not too flexible; if you want to do other things than those which are implemented in the Form base class (like Close()), e.g. specific to the main form, you would have to cast it first and check if it's really the form you thought of. Also, all those things would need to be exposed with Public or Internal, don't expose what you don't have to expose.
You pass the Form to the UserControl. Horrible because passing stuff around just ends up in spaghetti code.
Better, raise an event by the UserControl which you handle in the form the UserControl is on. That's probably the most flexible approach.
Here's a small code example solving this with an event:
Open the code of the UserControl and add an event signature and raise that event when you click the button:
Public Class MyUserControl
Public Event ButtonClicked(sender As Object, e As EventArgs)
Private Sub MyButton_Click(sender As Object, e As EventArgs) Handles MyButton.Click
RaiseEvent ButtonClicked(sender, e)
End Sub
End Class
Then, in your Form, attach to the ButtonClicked event of the UserControl:
Public Class MyForm
Private Sub MyUserControl1_ButtonClicked(sender As Object, e As EventArgs) Handles MyUserControl1.ButtonClicked
Close()
End Sub
End Class
If you re-use the event for multiple buttons, you can check which button it is through the sender passed to the event. (Of course this can be optimized by just passing a casted Button instance as the event parameter, this is just a simple example).
Where did you get "close" from? You exit an application with application.exit()
If you want to close Application you can use:
Application.Exit()
If you want to close Form:
Me.Close()
To close the form you use me.
me.close

User Control Event against Form Control Event

Here is my situation, I have a user control that have the Leave event:
Private Sub MyControl_Leave(sender As Object, e As EventArgs) Handles Me.Leave
If Me.Enabled Then
MsgBox(Property1)
End If
End Sub
I have this to prevent Leave Event from triggering when the control is Disabled.
Then on my form, the control also has its own Leave event because I need to set some Properties that the Leave Event on the User Control needs.
Private Sub myControlOnForm_Leave(sender As Object, e As EventArgs) Handles MyControlOnForm.Leave
MyControlOnForm.Property1 = "value1"
End Sub
What happens is the first event that triggers is the one on the User Control and then the one on the form.
Now my problem is, as the code states above, I need the Form Event to trigger first before the User Control Event.
Is there any work around for this?
The form needs to call a procedure in the user control after it's finished handling the event. Just remove the Handles Me.leave statement, and the private statement. use the sub from your form to call the controls sub which was intended to handle the event.
Note that I've changed sender As object to sender As Mycontrol.
Sub MyControl_Leave(sender As Mycontrol, e As EventArgs)
If Me.Enabled Then
MsgBox(Property1)
End If
End Sub
Code on form
Private Sub myControlOnForm_Leave(sender As Mycontrol, e As EventArgs) Handles MyControlOnForm.Leave
MyControlOnForm.Property1 = "value1"
sender.MyControl_Leave(sender, e)
End Sub
What happens is the first event that triggers is the one on the User Control and then the one on the form.
Now my problem is, as the code states above, I need the Form Event to
trigger first before the User Control Event.
First off, you are using the incorrect term in that problem statement. It is not an event triggering order issue, but rather an issue in order in which the event handlers registered for the UserControl's Leave event execute.
.Net events are a form of syntactic sugar for the invocation of a multicast delegate. When an event is raised a delegate is invoked and the order in which the handlers are executed is the order in which they were added to the delegate. You can gain an understanding of this by working through the various "Walkthrough" tutorials located under Events (Visual Basic).
The Leave event is Raised by calling the Overridable OnLeave method inherited from the Control Class that is in the inheritance tree of the UserControl Class. It is considered bad form for a class to handle its own generated event; the preferred method is Override the method that raise the event.
In your case, you want the form that subscribes to the event to be notified first so that it can modify a property on the UserControl before some it performs some action in response to Leaving the UserControl.
Public Class UserControl1
Protected Overrides Sub OnLeave(e As EventArgs)
MyBase.OnLeave(e) ' this calls the base method that Raises the event
' all event handlers will run before the subsequent code
' executes
If Me.Enabled Then
'do something
End If
End Sub
End Class

VB.NET Disable/Hide x button but WITHOUT disabling closing altogether

I would like to remove/disable/hide the x button from my form but WITHOUT disabling closing altogether. So I want it so the program can't be closed by using the x button but ONLY by a certain key command (Through a menu item or similar). The only way I know of is to disable closing altogether which then means that the x button doesn't work but I cannot close it using the key command.
Are there any ways of disabling JUST the x button?
Thanks.
In the properties for the form, set the ControlBox property to False. Then, in the code, when you want to close the form, just call the form's Close method.
However, doing that will not stop the user from closing the window via standard OS methods (e.g. via the button on the task bar, via ALT+F4). In order to stop that, you would need to cancel the closing of the form in its FormClosing event. For instance:
Public Class Form1
Private _closeAllowed As Boolean = False
Private Sub Form1_Click(sender As Object, e As EventArgs) Handles Me.Click
_closeAllowed = True
Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If Not _closeAllowed Then
e.Cancel = True
End If
End Sub
End Class
However, even that won't stop the application from being terminated. For more thorough solutions, you may want to do some searches on best-practices for developing kiosk applications for Windows.

Non modal form that blocks its Owner

I was trying to get forms configuration for my project that can suit my needs here and didn't get to a solution. It seem's I find a solution and now I have to implement it application-wide.
Here I have main form and a few subforms (which have many subforms) and through the main form I should ALWAYS be able to close all opened forms, watch for universal keypresses, close application permanently and watch for critical events.
I find solution in this facts:
From main form I open main subforms instantiated but non modally.
From sub forms I open deeper subforms also instantiated and non modally.
In all those subforms _Load event handler I disable calling form and in _FormClosing handler I enable it again. That way non modal forms act like modal forms in order to caller but not in order to main form which stays responsive all the time!
In forms that have to block its own caller (Owner) I added property "Blocking" so that my code looks like this:
If Not formIsOpened(frm_myFirstChild) Then
Dim f As New frm_myFirstChild
f.Blocking = True
f.Show(Me)
f = Nothing
End If
In the frm_myFirstChild I have property:
<Browsable(True), _
DefaultValue(False)> _
Public Property Blocking() As Boolean
Get
Return _Blocking
End Get
Set(ByVal value As Boolean)
_Blocking = value
End Set
End Property
If boolean property "Blocking" is TRUE then under _Load this code has to be executed:
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = False
End If
In _FormClosing that:
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
All of that work as expected so I try to implement that for all forms and use when needed in a subclass "cls_transform":
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
MyBase.OnFormClosing(e)
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = False
End If
MyBase.OnLoad(e)
End Sub
Here I have problem that subclass doesn't understand the property Blocked (not declared).
How do you get a Form's property Blocked to subclass so I can use those subclasses for all forms, and switch the property Blocking from the outside regarding functionality needs?
Sounds to me that "cls_transform" is actually a class derived from Form, the only way that OnFormClosing could work. Which is okay, your "sub-forms" now need to derive from cls_transform instead of Form. Do pick a better name.
Then simply add the Blocking property to that cls_transform class to solve your problem.
Do note that there's a bug in your OnFormClosing method. It can be canceled and that will leave the form opened with its owner in the wrong state. You need to write it like this instead:
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
MyBase.OnFormClosing(e)
If Not e.Cancel And Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
End Sub

VB.Net: Understanding the way Application.Run() works

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.