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

19,028

Solution 1

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;
}

Solution 2

How about this using Application.Openforms

Form GetTopMostForm()
{
    return Application.OpenForms
        .Cast<Form>()
        .First(x => x.Focused);
}

Solution 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.

Solution 4

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.

Solution 5

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();
Share:
19,028

Related videos on Youtube

tzup
Author by

tzup

C# programmer trying to keep up with technology.

Updated on April 17, 2022

Comments

  • tzup
    tzup about 2 years

    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?

  • tzup
    tzup about 15 years
    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.
  • tzup
    tzup about 15 years
    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.
  • Chris Fong
    Chris Fong almost 14 years
    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.
  • tzup
    tzup almost 13 years
    @Zachary Yates, the requirement is to be able to close child forms, not the main form.
  • Chris Fong
    Chris Fong over 12 years
    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.
  • bruestle2
    bruestle2 almost 6 years
    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.GetWindow