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?
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;
}
How about this using Application.Openforms
Form GetTopMostForm()
{
return Application.OpenForms
.Cast<Form>()
.First(x => x.Focused);
}
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.
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.
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();