[Resolved] How to detect when a child form as been closed?
Hello, in my program I need a way to detect when a child form has been closed. I've created an MdiWindowList thing (but in the form of a toolbar with buttons, similarly to the Windows taskbar), and everything works great, but the method I have for refreshing the taskbar's windows has to be called when a child form closes. I've trying calling the method from the child's Form_Closing event, but there's a slight lag so whenever there's more than 1 window open the first one gets skipped (then the others close properly, since the taskbar is being refreshed first before the form actually closes).
I don't think this should be terribly hard, I just can't figure it out myself.
Re: How to detect when a child form as been closed?
Your method is correct (except that you should probably handle the FormClosed event instead, the closing can still be canceled in the FormClosing event), but you're probably doing something wrong.
I would expect that you have a method that refreshes the 'mdi window list toolbar', let's call it "RefreshMdiToolbar". When you show a new child form, you can use AddHandler to listen for the event, and update the toolbar there.
vb.net Code:
Private Sub AddChildForm()
Dim f As New ChildForm1()
f.MdiParent = Me
AddHandler f.FormClosed, AddressOf ChildFormClosed
f.Show()
End Sub
Private Sub ChildFormClosed(ByVal sender As Object, ByVal e As EventArgs)
Me.RefreshMdiToolbar()
End Sub
That's what I would try. It sounds like you are already doing this though. Can you explain your last line more in detail? I have no idea what you mean by 'there's a slight lag and the first one gets skipped'. Also show your code, at least the relevant parts.
Re: How to detect when a child form as been closed?
I'm actually using your code for a custom MdiWindowList, but tailored to a toolstrip. I took the liberty of grabbing an image to maybe help you determine my problem:
http://i35.tinypic.com/32zngwh.png
With 3 windows, there are 3 toolstrip buttons. But, when I close any window for the first time, the buttons don't disappear. Now, I made an event to use your "RefreshWindowList" method when the mouse goes over the toolstrip. So, when I do that, the toolstrip refreshes accordingly and everything is ok. Now, if I close a second window, a button disappears. Starting with the 3 windows, I'd have 1 window left (after closing the first 2) but I'd be left with 2 buttons. If I close the last one, I'd now be left with 1 button which would, once again, disappear if I put my mouse over it because of the instantaneous refreshing.
I "cheated" and had a fix for it, I basically just put in a timer with an interval of 10 ms that would refresh the toolstrip automatically each interval. But, that would cause some extreme rendering/buffering issues when there would be more buttons than what the toolstrip could contain, causing them to go off in that separate menu off to the right side. The menu would flash and no items within it could be selected. This is because of the refreshing every 10 ms. Besides the buffering issues, everything else worked fine though.
So, essentially what I believe I'm trying to find now, is how to have a constant watch for closed forms, but to be able to determine which form it is. I tried AddHandler not only in the place where my form gets added, but also in the Form_Closing and Form_Closed events in the actual child form. All 3 methods did not work, the AddHandler in the parent form did nothing and the two in the child form had the same effect as everything else with the "skipping".
Re: How to detect when a child form as been closed?
Show your code for the AddHandler in the parent form. If you call the RefreshWindowList method in the FormClosed event handler of any MDI form, then it should work. The only reason when it won't work is that the MDI form is still in the MdiChildren collection at that point. In that case, you could set a global variable to the closed form, and don't add a button for that form.
Something like this
vb.net Code:
Private _ClosingForm As ChildForm1
Private Sub AddChild()
Dim f As New ChildForm1()
f.MdiParent = Me
AddHandler f.FormClosed, AddressOf ChildFormClosed
f.Show()
End Sub
Private Sub ChildFormClosed(ByVal sender As Object, ByVal e As EventArgs)
_ClosingForm = DirectCast(sender, ChildForm1)
Me.RefreshWindowList()
End Sub
Private Sub RefreshWindowList()
For Each f As Form In Me.MdiChildren
If f IsNot _ClosingForm Then
'add to toolbar
End If
Next
End Sub
You could also pass the closing form to the RefreshWindowList method, it doesn't really matter.
Re: How to detect when a child form as been closed?
I've tried what you've just suggested but I still get the same "skipping" effect. Here's my AddHandler code:
Code:
Private Sub RunescapeToolStripMenuItem1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RunescapeToolStripMenuItem1.Click
Dim F As New frmRunescape
F.MdiParent = Me
AddHandler F.FormClosed, AddressOf ChildFormClosed
F.Show()
RefreshWindows()
End Sub
Private Sub ChildFormClosed(ByVal sender As Object, ByVal e As EventArgs)
Dim c As frmRunescape
c = DirectCast(sender, frmRunescape)
RefreshWindows()
End Sub
Is there some way to dynamically remove a form from an MdiChildren collection directly?
EDIT: I think I know what the problem is. I added a pause to the AddHandler as well as the Form_Closed event for debugging . It seems that both events are not raised while the form is completely closed, but instead it's raised right before the form closes. So, by adding code into Form_Closed, it doesn't do anything since the form is still open and technically part of the MdiChildren collection.
Re: How to detect when a child form as been closed?
It's presumably your RefreshWindows method that's the issue. That's presumably the method that displays the window items yet you haven't shown us that code. I would guess that what's happening is that you're looping through a list that still contains the form that was just closed. In that case, simply test the IsDisposed property of each form in the list and ignore any that are True.
Re: How to detect when a child form as been closed?
Quote:
Originally Posted by
jmcilhinney
It's presumably your RefreshWindows method that's the issue. That's presumably the method that displays the window items yet you haven't shown us that code. I would guess that what's happening is that you're looping through a list that still contains the form that was just closed. In that case, simply test the IsDisposed property of each form in the list and ignore any that are True.
IsDisposed will never be true because 100% disposed forms are not in MdiChildren. For some reason, the form is not being completely disposed...
Here's the RefreshWindows() sub I have:
Code:
Public Sub RefreshWindows()
Dim mdiWindowCount As Integer = Me.MdiChildren.Count
Dim menuItemWindowCount As Integer = Me.toolSwitch.Items.Count
Dim item As ToolStripItem
For i As Integer = menuItemWindowCount - 1 To 0 Step -1
item = Me.toolSwitch.Items(i)
If Not (item Is cmdClose _
Or item Is cmdDesktop _
Or item Is cmdRadio _
Or item Is ToolStripSeparator1 _
Or item Is ToolStripSeparator2) Then
Me.toolSwitch.Items.RemoveAt(i)
End If
Next
If mdiWindowCount > 0 Then
Dim menuItem As ToolStripButton
Dim counter As Integer = 1
For Each window As Form In Me.MdiChildren
If window.IsDisposed = True Then MsgBox("got one")
menuItem = New ToolStripButton
menuItem.Text = window.Text
menuItem.Tag = window
If window Is Me.ActiveMdiChild Then
menuItem.Checked = True
End If
AddHandler menuItem.Click, AddressOf ButtonClicked
Me.toolSwitch.Items.Insert(Me.toolSwitch.Items.Count, menuItem)
counter += 1
Next
End If
End Sub
This is really breaking my balls.
Re: How to detect when a child form as been closed?
I just tested and it does appear that things are a bit screwy with MDI child forms as far as when the FormClosed event is raised. How about you add a parameter to your RefreshWindows method for a form to ignore. When you loop through the MdiChildren you can then compare each one to that parameter and ignore it if they match?
Re: How to detect when a child form as been closed?
I figured it out - I ended up declaring a variable as the MdiClient object, then made a handler to detect when a control is removed from it. The delegate I put in was RefreshWindows(), and now everything works like a charm. Thanks for all of you guys' help though :)
Here's the code in case someone else has the same issue:
Code:
Dim client = Me.Controls.OfType(Of MdiClient).FirstOrDefault()
AddHandler client.ControlRemoved, AddressOf RefreshWindowList
Re: How to detect when a child form as been closed?
Quote:
Originally Posted by
Blupig
I figured it out - I ended up declaring a variable as the MdiClient object, then made a handler to detect when a control is removed from it. The delegate I put in was RefreshWindows(), and now everything works like a charm. Thanks for all of you guys' help though :)
Here's the code in case someone else has the same issue:
Code:
Dim client = Me.Controls.OfType(Of MdiClient).FirstOrDefault()
AddHandler client.ControlRemoved, AddressOf RefreshWindowList
It won't actually make a difference in this case but you've got a bit of a logic flaw there. You're calling FirstOrDefault, which means get the first item in the collection or the default value for the type (i.e. Nothing for classes) if the collection is empty. You then go ahead and reference a member of the object returned by FirstOrDefault. That will throw an exception if FirstOrDefault returns Nothing so you're obviously assuming that it won't return Nothing. In that case, why bother with the default at all? You should just be calling First rather than FirstOrDefault.
Further to that, the collection you're using should always and only contain one item. In that case, it's more appropriate to call Single than First.
Like I said, none of this will make a difference to the end result but it's important to understand what is going on so that you do do the right thing when it will make a difference.
Re: How to detect when a child form as been closed?
Quote:
Originally Posted by
Blupig
EDIT: I think I know what the problem is. I added a pause to the AddHandler as well as the Form_Closed event for debugging . It seems that both events are not raised while the form is completely closed, but instead it's raised right before the form closes. So, by adding code into Form_Closed, it doesn't do anything since the form is still open and technically part of the MdiChildren collection.
That's what I said in my previous post. The FormClosed event is raised, but the form is still in the MdiChildren collection so if you call the RefreshWindowList method in the FormClosed event then it will still add the 'closed' form.
I also told you how to remedy this: keep track of the form that is being closed, either in a global variable or by passing it to the RefreshWindowList method.
You were almost doing that, but not quite. You were declaring a local variable c which you cannot use outside that method. Also, you weren't checking whether the current form you are looping with (in the RefreshWindowList method) was the form that was being closed.
Either way, I think handling the ControlRemoved event is even better, and you might want to handle the ControlAdded event too, instead of calling the method manually every time you show a new child form. This way you can have 36 different pieces of code where you add a new child form, but you'll call the method in just one place (in the ControlAdded event), instead of in 36 places separately.