如何在WinForm应用程序中获取最顶层窗体的句柄?

9

我有一个WinForm应用程序,其中包含其他子窗体(不是MDI)。如果用户按下“Esc”,则应关闭最上层的窗体,即使它没有焦点。

我可以使用键盘钩子来全局捕获Escape键,但我还需要关闭窗体的句柄。

我猜想可以使用Win32 API来实现,但是否有使用托管代码的解决方案?

5个回答

7

这里有一种使用Win32获取最上层窗体的方法(不太优雅,但却可行):

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

在循环中调用GetNextWindow可能会产生无限循环。因此,我不确定这个解决方案是否是最好的想法。请参见以下内容:“EnumChildWindows函数比在循环中调用GetWindow更可靠。调用GetWindow执行此任务的应用程序存在被卡在无限循环中或引用已被销毁的窗口句柄的风险。”https://www.pinvoke.net/default.aspx/user32.GetWindow - bruestle2

4
如何使用 Application.Openforms呢?
Form GetTopMostForm()
{
    return Application.OpenForms
        .Cast<Form>()
        .First(x => x.Focused);
}

要求关闭最上层的窗体,该窗体可能没有焦点。 - tzup

3
我知道这个帖子已经四年了,但我有一个类似的问题,并提供了另一种解决方案,以防其他人遇到这个问题而不想使用Win32调用。我假设最上面的窗体是最后激活的窗体。因此,您可以保留一个单独的窗体集合(类似于Application.OpenForms),除此之外,此集合将按每个窗体最后激活的时间排序。每当窗体被激活时,将其移动到集合的第一项。每当你看到ESC键时,你应该关闭collection[0]并将其删除。

2
或者使用栈,对于这个问题来说比集合更自然。 - Boris B.

1
你可以在最顶层的窗体中实现类似单例模式的设计,提供一个静态属性来返回自身的唯一实例,并在需要时简单地关闭它。
   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();

我认为你误解了问题。想象一下一个WinForm应用程序,其中一个主窗体最大化,许多其他较小的窗体层叠在主窗体上方。每次按Esc键时,最上面的窗体都应该关闭(请记住它可能没有焦点)。希望这能让事情更清晰明了。 - tzup
我认为你可能误解了他的回答。一次只能打开一个MainForm,对吧?单例模式可以在应用程序中的任何地方(包括键盘钩子)提供静态句柄以访问该窗体。 - Zachary Yates
@Zachary Yates,要求是能够关闭子窗体,而不是主窗体。 - tzup
啊,我明白了。当你说“topmost”时,你的意思是最高的z-order,而不是父子窗体层次结构的顶部。这有点令人困惑。 - Zachary Yates

1

2
不幸的是,Form.TopMost属性获取或设置一个值,指示该窗体是否应显示为最顶层的窗体。但这并不能告诉我该窗体是否确实处于最顶层。 - tzup

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接