Home > Enterprise >  WinForms: topmost form loses focus on hiding dialog called from it
WinForms: topmost form loses focus on hiding dialog called from it

Time:09-23

I have a WinForms project with a main topmost form from which a non-modal dialog is displayed. I need to hide (not close) the dialog if it loses the input focus - no matter what was the reason (the user clicked the main form, switched to another app, etc). The following bare part of the project source code shows what is going on:

public partial class MainForm : Form
{
    Form _dialog = new Form();

    public MainForm()
    {
        InitializeComponent();

        this.TopMost = true;
        this.Text = "Main Form";

        _dialog.Text = "Dialog";
        _dialog.Owner = this;
        _dialog.TopMost = true;
        _dialog.Deactivate  = Dialog_Deactivate;
        _dialog.FormClosing  = Dialog_FormClosing;
    }

    private void Dialog_Deactivate(object sender, EventArgs e)
    {
        _dialog.Hide();
    }

    private void Dialog_FormClosing(object sender, FormClosingEventArgs e)
    {
        _dialog.Hide();
        e.Cancel = true;
    }

    private void ButtonShowDialog_Click(object sender, EventArgs e)
    {
        _dialog.Show();
    }
}

The main problem I am trying to solve is the following. If the user opened the dialog and clicks the main form like I depicted on the following screenshot

enter image description here

, the dialog becomes hidden as expected, but the main form loses the focus and another app that was previously active becomes active in the background - the Windows Explorer on the next screenshot:

enter image description here

Is it a known issue in Windows or WinForms? How to cause the main form not to lose the focus in this construction?

CodePudding user response:

Try calling Activate() on the main form after the dialog is hidden, i.e:

private void Dialog_Deactivate(object sender, EventArgs e)
{
    _dialog.Hide();
    this.Activate();
}

CodePudding user response:

The issue seems to be that when the Main Form is clicked, it triggers a WM_WINDOWPOSCHANGING event. Since the child dialog is open, the hwndInsertAfter handle is the child dialog. But then in the Dialog_Deactivate the child dialog is hidden, causing the Main Form to fall behind all the other windows because the hwndInsertAfter handle is no longer a visible window.

Possible solutions are 1) set the child dialog owner to null before calling Hide() or 2) use a different event, like LostFocus, i.e:

public class MainForm3 : Form {
    Form _dialog = null;

    public MainForm3() {
        this.Text = "Main Formmmmmmmm";
        Button btn = new Button { Text = "Show" };
        btn.Click  = ButtonShowDialog_Click;
        this.Controls.Add(btn);
    }

    bool b = false;
    protected override void WndProc(ref Message m) {
        base.WndProc(ref m);
        if (_dialog != null && _dialog.Visible)
            b = true;

        if (b)
            Debug.WriteLine(m);

        int WM_WINDOWPOSCHANGING = 0x46;
        if (b && m.Msg == WM_WINDOWPOSCHANGING) {
            var wp = Marshal.PtrToStructure<WINDOWPOS>(m.LParam);
            Debug.WriteLine("hwnd: "   wp.hwnd   "  "   GetWindowText(wp.hwnd));
            Debug.WriteLine("hwndInsertAfter: "   wp.hwndInsertAfter   "  "   GetWindowText(wp.hwndInsertAfter));
            Debug.WriteLine("flags: "   wp.flags);
        }
    }

    private void Dialog_Deactivate(object sender, EventArgs e) {
        _dialog.Owner = null; // solution 1
        _dialog.Hide();
    }

    private void _dialog_LostFocus(object sender, EventArgs e) { // solution 2
        _dialog.Hide();
    }

    private void Dialog_FormClosing(object sender, FormClosingEventArgs e) {
        if (_dialog.Visible) {
            _dialog.Hide();
            e.Cancel = true;
        }
    }

    private void ButtonShowDialog_Click(object sender, EventArgs e) {
        if (_dialog == null) {
            _dialog = new Form();
            _dialog.Text = "Dialoggggggg";
            //_dialog.Deactivate  = Dialog_Deactivate;
            _dialog.LostFocus  = _dialog_LostFocus; // solution 2, use LostFocus instead
            _dialog.FormClosing  = Dialog_FormClosing;
        }

        _dialog.Owner = this;
        _dialog.Show();
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPOS {
        public IntPtr hwnd, hwndInsertAfter;
        public int x, y, cx, cy;
        public SWP flags;
    }

    [Flags]
    public enum SWP : uint {
        SWP_ASYNCWINDOWPOS = 0x4000,
        SWP_DEFERERASE = 0x2000,
        SWP_DRAWFRAME = 0x0020,
        SWP_FRAMECHANGED = 0x0020,
        SWP_HIDEWINDOW = 0x0080,
        SWP_NOACTIVATE = 0x0010,
        SWP_NOCOPYBITS = 0x0100,
        SWP_NOMOVE = 0x0002,
        SWP_NOOWNERZORDER = 0x0200,
        SWP_NOREDRAW = 0x0008,
        SWP_NOREPOSITION = 0x0200,
        SWP_NOSENDCHANGING = 0x0400,
        SWP_NOSIZE = 0x0001,
        SWP_NOZORDER = 0x0004,
        SWP_SHOWWINDOW = 0x0040
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    private static String GetWindowText(IntPtr hWnd) {
        StringBuilder sb = new StringBuilder(256);
        GetWindowText(hWnd, sb, sb.Capacity);
        return sb.ToString();
    }
}
  • Related