formswinformsfocusownertopmost

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


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?


Solution

  • 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();
        }
    }