Facosenpai
Facosenpai

Reputation: 121

Closing child Form causes Parent Form to lose focus

I have a simple form with a button Button1 that, once pressed, it opens a new _frm as follows:

Protected _frm As Form
[...]
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
      _frm = New Form()
      AddHandler _frm.Deactivate, AddressOf OnFormDeactivated
      AddHandler _frm.FormClosed, AddressOf OnFormClosed
      With _frm
         .Location = PointToScreen(New Point(0, Height))
         .Text = "_frm"
         .Show(Me.FindForm)
      End With
End Sub

And the handlers for the Deactivate and FormClosed events are defined as follows:

Private Sub OnFormDeactivated(sender As Object, e As EventArgs)
      _frm.Close()
End Sub

Private Sub OnFormClosed(sender As Object, e As FormClosedEventArgs)
      Me.FindForm.RemoveOwnedForm(_frm)

      RemoveHandler _frm.Deactivate, AddressOf OnFormDeactivated
      RemoveHandler _frm.FormClosed, AddressOf OnFormClosed

      _frm.Dispose()
      _frm = Nothing

      WindowUtil.ShowWindow(Process.GetCurrentProcess) 'call to user32.dll ShowWindow
      WindowUtil.ToForeground(Process.GetCurrentProcess) 'call to user32.dll SetForegroundWindow
End Sub

When I close the child Form, the Parent form gets the focus back and the same is true if I select another application, which is the desired behaviour.
But if I click anywhere inside the Parent Form, the Child Form disappears correctly but the Parent Form (and therefore the entire application) loses focus.

EDIT
WindowUtil.vb methods:

<DllImport("user32.dll", EntryPoint:="SetForegroundWindow",
                 CallingConvention:=CallingConvention.StdCall,
                  CharSet:=CharSet.Unicode, SetLastError:=True)>
Private Shared Function SetForegroundWindow(ByVal handle As IntPtr) As Boolean
End Function

Public Shared Sub ToForeground(proc As Process)
         Dim ForegroundReturn As Boolean = SetForegroundWindow(proc.MainWindowHandle)
End Sub

<DllImport("user32.dll", EntryPoint:="ShowWindow",
                 CallingConvention:=CallingConvention.StdCall,
                 CharSet:=CharSet.Unicode, SetLastError:=True)>
Private Shared Function ShowWindow(ByVal handle As IntPtr, ByVal nCmd As Int32) As Boolean
End Function

Public Shared Sub ShowWindow(proc As Process)
         Dim ShowReturn As Boolean = ShowWindow(proc.MainWindowHandle, 1)
End Sub

Upvotes: 1

Views: 779

Answers (1)

Jimi
Jimi

Reputation: 32248

I suggest to add a delay between the BringToFront() / Activate() methods calls and the Form.Close() call that closes the owned Form: it should give more stable results in both situations: when you click the Owner Form and when you click a Window that belongs to another application.

As a note:

That's why PInvoking is not necessary (meaning, the Framework already does this for you).

Form.Close() is called after the the activation of the Owner Form because this causes the Control (in the Owner Form) that receives the mouse action to become the ActiveControl, without setting this property explicitly.

► The 15 milliseconds delay can be fine-tuned, since the nature of the Form (how it's built and how many Controls it contains) can affect the time it needs to close (because OwnerForm.Activate() is called right before returning from the async ActivateForm() method and the Form on top is closed right after). This generates a smoother transition, but it may also cause the Owner Form to blink when you click the Window that belongs to another Process.

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
   ' someForm is clearly a made-up class name :) Set it to actual the class name.
    Dim frm As New someForm()
    AddHandler frm.Deactivate, AddressOf OnFormDeactivated
    With frm
        ' Whatever this means to you
        .Location = PointToScreen(New Point(0, Height))
        .Text = "frm"
        .Show(Me)
    End With
End Sub

Private Async Sub OnFormDeactivated(sender As Object, e As EventArgs)
    Dim frm = DirectCast(sender, Form)
    RemoveHandler frm.Deactivate, AddressOf OnFormDeactivated
    Await ActivateForm(Me)
    frm.Close()
End Sub

Private Async Function ActivateForm(frm As Form) As Task
    Await Task.Delay(15)
    frm.BringToFront()
    Await Task.Delay(15)
    frm.Activate()
End Function

Upvotes: 1

Related Questions