views:

913

answers:

3

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?

+1  A: 

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.

Bogdan_Ch
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.
tzup
A: 

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();
NascarEd
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
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.
Zachary Yates
+1  A: 

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