How to get the handle of the topmost form in a WinForm app?
Asked Answered
P

5

9

I have a WinForm app that has other child forms (not mdi). If the user presses "Esc" the topmost form should be closed even if it doesn't have the focus.

I can use a keyboard hook to globally catch the Escape but I also need the handle of the form to be closed.

I guess there is a way to do that using Win32 API, but is there a solution using managed code?

Pentateuch answered 16/6, 2009 at 11:17 Comment(0)
P
7

Here is one way to get the topmost form that uses Win32 (not very elegant, but it works):

public const int GW_HWNDNEXT = 2; // The next window is below the specified window
public const int GW_HWNDPREV = 3; // The previous window is above

[DllImport("user32.dll")]
static extern IntPtr GetTopWindow(IntPtr hWnd);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "GetWindow", SetLastError = true)]
public static extern IntPtr GetNextWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.U4)] int wFlag);

/// <summary>
/// Searches for the topmost visible form of your app in all the forms opened in the current Windows session.
/// </summary>
/// <param name="hWnd_mainFrm">Handle of the main form</param>
/// <returns>The Form that is currently TopMost, or null</returns>
public static Form GetTopMostWindow(IntPtr hWnd_mainFrm)
{
    Form frm = null;

    IntPtr hwnd = GetTopWindow((IntPtr)null);
    if (hwnd != IntPtr.Zero)
    {
        while ((!IsWindowVisible(hwnd) || frm == null) && hwnd != hWnd_mainFrm)
        {
            // Get next window under the current handler
            hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);

            try
            {
                frm = (Form)Form.FromHandle(hwnd);
            }
            catch
            {
                // Weird behaviour: In some cases, trying to cast to a Form a handle of an object 
                // that isn't a form will just return null. In other cases, will throw an exception.
            }
        }
    }

    return frm;
}
Pentateuch answered 17/6, 2009 at 8:34 Comment(1)
Calling GetNextWindow in a loop can yield an infinite loop. So I'm not sure if this solution is the best idea. See the following: "The EnumChildWindows function is more reliable than calling GetWindow in a loop. An application that calls GetWindow to perform this task risks being caught in an infinite loop or referencing a handle to a window that has been destroyed." pinvoke.net/default.aspx/user32.GetWindowDiarchy
D
4

How about this using Application.Openforms

Form GetTopMostForm()
{
    return Application.OpenForms
        .Cast<Form>()
        .First(x => x.Focused);
}
Demark answered 13/2, 2012 at 23:40 Comment(1)
The requirement is to close the form that is topmost, which might not have the focus.Pentateuch
P
3

I know this is a 4 yr old thread, but I had a similar problem and just came up with an alternative solution just in case anyone else stumbles on this question and doesn't want to mess around with Win32 calls.

I assume the top-most form will be the one that was last activated. So you could keep a separate collection of forms, similar to Application.OpenForms, except this collection would be ordered by when each was last activated. Whenever a form is activated, move it to the first item of the collection. Whenever you see the ESC key, you would close collection[0] and remove it.

Playhouse answered 7/6, 2013 at 23:22 Comment(1)
Or use a stack, it's more natural than a collection for thisFunchal
G
1

FormCollection is used by the Application object to list the currently open forms in an application through the OpenForms property

See http://msdn.microsoft.com/en-us/library/system.windows.forms.application.openforms.aspx

Then you could check TopMost() property of each form. And when you find a topmost form, you close it.

Gorlicki answered 16/6, 2009 at 13:4 Comment(1)
Unfortunatelly the Form.TopMost property gets or sets a value indicating whether the form should be displayed as a topmost form. This doesn't tell me if the form IS top most.Pentateuch
P
1

You could implement a singleton-like pattern in your topmost form, and provide a static property that returns the one instance of itself and simply close it.

   public class MainForm : Form
   {
      private static MainForm mainForm;

      public static MainForm { get { return mainForm; } }

      public MainForm()
      {
         mainForm = this;
      }
   }


   // When the ESC key is pressed...
   MainForm.MainForm.Close();
Pithos answered 16/6, 2009 at 15:4 Comment(4)
I think that you misunderstood the question. Imagine a WinForm app with one main form maximized and many other smaller forms cascading over the main form. Every time you hit Esc the topmost form should close (keep in mind that it might not have the focus). Hope this makes things clearer.Pentateuch
I think you may have misunderstood his response. There is only one MainForm open at a time, right? The singleton pattern presents a static handle to the form from anywhere in the app, including your keyboard hook.Moncton
@Zachary Yates, the requirement is to be able to close child forms, not the main form.Pentateuch
Ah I understand. When you say 'topmost' you mean highest z-order, not the top of the parent-child form hierarchy. It's a little confusing.Moncton

© 2022 - 2024 — McMap. All rights reserved.