如何在特定全屏应用上层以悬浮窗形式打开Form?
如何在特定全屏应用上层显示悬浮Form
全屏应用通常处于更高的显示层级,普通TopMost属性无法覆盖,结合Win32 API调整窗口关联与层级是可行方案,以下是具体实现步骤:
1. 配置悬浮Form的基础属性
确保Form不抢占焦点,避免干扰全屏应用,并设置合适的窗口样式:
public class DeckOverlayForm : Form { // 禁止窗体激活,保证全屏应用始终获得焦点 protected override bool ShowWithoutActivation => true; public DeckOverlayForm() { InitializeComponent(); // 无边框样式,适配悬浮需求 FormBorderStyle = FormBorderStyle.None; // 可选:设置透明背景,实现异形悬浮窗 BackColor = Color.Magenta; TransparencyKey = Color.Magenta; // 铺满主屏幕,可根据需求调整区域 Bounds = Screen.PrimaryScreen.Bounds; } }
2. 用Win32 API绑定目标应用并设置层级
通过API将悬浮窗与目标应用窗口关联,确保悬浮窗始终显示在其上层,同时避免直接绑定父窗口导致的崩溃:
首先定义Win32 API封装类:
public static class NativeMethods { [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] public static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); public const int GWL_HWNDPARENT = -8; public const uint SWP_NOSIZE = 0x0001; public const uint SWP_NOMOVE = 0x0002; public const uint SWP_SHOWWINDOW = 0x0040; public static readonly IntPtr HWND_TOP = new IntPtr(0); }
然后实现显示悬浮窗的逻辑:
private void TrackerManagerOnGameStarted(object? sender, EventArgs e) { // 获取目标进程的主窗口句柄 var targetProcess = Process.GetProcessesByName(PROCESS_NAME).FirstOrDefault(); if (targetProcess == null || targetProcess.MainWindowHandle == IntPtr.Zero) return; _frm = new DeckOverlayForm(); // 将悬浮窗与目标应用窗口建立关联,而非直接设为子窗口,避免生命周期绑定崩溃 NativeMethods.SetWindowLongPtr(_frm.Handle, NativeMethods.GWL_HWNDPARENT, targetProcess.MainWindowHandle); // 强制将悬浮窗置于目标窗口上层 NativeMethods.SetWindowPos(_frm.Handle, NativeMethods.HWND_TOP, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE | NativeMethods.SWP_SHOWWINDOW); _frm.Show(); }
3. 适配全屏应用的模式切换
部分全屏应用会在窗口/全屏模式间切换,可通过监听窗口激活事件,在目标应用重新激活时重新设置悬浮窗层级,确保显示正常:
// 示例:监听系统窗口激活事件 [DllImport("user32.dll")] private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); private void SetupWindowHook() { SetWinEventHook(0x0008 /* EVENT_OBJECT_SHOW */, 0x0008, IntPtr.Zero, (hook, eventType, hwnd, idObj, idChild, thread, time) => { // 判断激活的窗口是否为目标应用 if (hwnd == targetProcess.MainWindowHandle && _frm != null) { NativeMethods.SetWindowPos(_frm.Handle, NativeMethods.HWND_TOP, 0, 0, 0, 0, NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOMOVE); } }, 0, 0, 0x0005 /* WINEVENT_OUTOFCONTEXT */); }
关键注意事项
- 禁止使用
NativeWindow.AssignHandle绑定父窗口,该方法会将窗体生命周期与目标应用强绑定,极易引发崩溃。 - 必须保持
ShowWithoutActivation = true,防止悬浮窗抢占全屏应用焦点,影响用户操作。 - 针对DirectX/OpenGL类全屏应用,可额外设置窗体为分层窗口(添加
WS_EX_LAYERED扩展样式),配合SetLayeredWindowAttributes实现更稳定的顶层显示。
内容的提问来源于stack exchange,提问作者Paul Karam
相关产品推荐
相关产品推荐

