查看: 112|回复: 1

从零实现置顶任意窗口的Win32小工具

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
笔者在日常开发中经常需要将某个窗口置顶显示,例如查看文档时希望它始终浮在编辑窗口之上。虽然网上有不少现成工具,但自己动手实现一个既能加深对Win32 API的理解,又能按需定制。本文基于SetWindowPos、SetForegroundWindow、RegisterHotKey等API,实现了一个支持快捷键和下拉列表两种方式的置顶窗口小工具,核心代码仅35KB,完整项目已上传GitHub。

核心功能:置顶与前台激活
置顶窗口的关键是SetWindowPos函数,将窗口的Z序设置为HWND_TOPMOST即可实现置顶,设置为HWND_NOTOPMOST取消置顶。但直接调用会有一个问题:如果目标窗口处于最小化或未激活状态,仅置顶不会使它显示在前台。需要先将窗口带到前台,再置顶,才能达到“窗口浮在最上方且可见”的效果。

SetForegroundWindow可以激活窗口,但文档明确提到该函数受到系统限制——只有当调用线程与目标窗口的线程属于同一个输入队列时才能成功。因此必须使用AttachThreadInput将当前线程与目标窗口所在的线程绑定,然后再调用SetForegroundWindow。处理完成后立即解除绑定,避免影响其他操作。

具体实现代码如下(C++ Win32):
  1. void topwin(HWND hWnd)
  2. {
  3.     if (hWnd == nullptr) {
  4.         hWnd = GetForegroundWindow();
  5.     }
  6.     DWORD currentId = GetCurrentThreadId();
  7.     DWORD topId = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
  8.     AttachThreadInput(currentId, topId, TRUE);
  9.    
  10.     ShowWindow(hWnd, SW_SHOWNORMAL);
  11.     SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
  12.     SetForegroundWindow(hWnd);
  13.    
  14.     AttachThreadInput(currentId, topId, FALSE);
  15. }
复制代码
注意:SetWindowPos的SWP_NOSIZE | SWP_NOMOVE标志表示不改变窗口大小和位置。

快捷键方式:CTRL+SHIFT+T置顶 / CTRL+SHIFT+C取消
使用RegisterHotKey注册全局快捷键,ID分别设为1和2。在消息循环中捕获WM_HOTKEY消息,根据wParam判断是哪个快捷键触发。这里有一个细节:WM_HOTKEY处理需要放在DispatchMessage之后,否则打包的exe可能无法正常运行。
  1. int topHotkeyId = 1;
  2. int untopHotkeyId = 2;
  3. if (!RegisterHotKey(NULL, topHotkeyId, MOD_CONTROL | MOD_SHIFT, 'T')) return 1;
  4. if (!RegisterHotKey(NULL, untopHotkeyId, MOD_CONTROL | MOD_SHIFT, 'C')) return 1;
  5. while (GetMessage(&msg, nullptr, 0, 0)) {
  6.     if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
  7.         TranslateMessage(&msg);
  8.         DispatchMessage(&msg);
  9.         if (msg.message == WM_HOTKEY) {
  10.             HWND hWnd = GetForegroundWindow();
  11.             if (msg.wParam == topHotkeyId) topwin(nullptr);
  12.             if (msg.wParam == untopHotkeyId) {
  13.                 SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE);
  14.             }
  15.         }
  16.     }
  17. }
复制代码
注意:快捷键方式需要先点击目标窗口使其获得焦点,再按下快捷键才能正确操作。

下拉列表方式:枚举窗口并选择置顶
主窗口创建一个下拉框(ComboBox),通过EnumWindows遍历所有顶层窗口,过滤条件为:窗口可见且不是桌面图标窗口(即正常显示状态)。获取每个窗口的标题添加到下拉框中,同时将HWND保存到vector中,以便后续根据选中索引获取窗口句柄。
  1. std::vector<HWND> windows;
  2. bool IsWindowOnDesktop(HWND hwnd) {
  3.     WINDOWPLACEMENT placement = { sizeof(WINDOWPLACEMENT) };
  4.     if (GetWindowPlacement(hwnd, &placement)) {
  5.         return placement.showCmd == SW_SHOWNORMAL;
  6.     }
  7.     return false;
  8. }
  9. BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
  10.     if (IsWindowVisible(hwnd) && !IsWindowOnDesktop(hwnd)) {
  11.         int titleLength = GetWindowTextLength(hwnd);
  12.         if (titleLength > 0) {
  13.             std::wstring windowTitle(titleLength + 1, L'\0');
  14.             GetWindowText(hwnd, &windowTitle[0], titleLength + 1);
  15.             SendMessageW(hComboBox, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(windowTitle.c_str()));
  16.             windows.push_back(hwnd);
  17.         }
  18.     }
  19.     return TRUE;
  20. }
  21. EnumWindows(EnumWindowsProc, 0);
复制代码
下拉框控件创建时设置唯一标识MENU_ID(29),在WM_COMMAND消息中检测CBN_SELCHANGE事件,获取当前选中项索引,然后调用topwin(windows.at(selectedIndex))完成置顶。
  1. #define MENU_ID 29
  2. HWND hComboBox;
  3. hComboBox = CreateWindowW(L"ComboBox", L"", CBS_DROPDOWN | WS_CHILD | WS_VISIBLE,
  4.     10, 10, 360, 200, hWnd, (HMENU)MENU_ID, nullptr, nullptr);
  5. SendMessageW(hComboBox, CB_SETCURSEL, 0, 0);
  6. case WM_COMMAND:
  7.     if (HIWORD(wParam) == CBN_SELCHANGE) {
  8.         LRESULT selectedIndex = SendMessageW(GetDlgItem(hWnd, MENU_ID), CB_GETCURSEL, 0, 0);
  9.         topwin(windows.at(selectedIndex));
  10.     }
  11.     break;
复制代码
下拉列表方式未内置取消置顶,可结合快捷键CTRL+SHIFT+C实现。

总结与扩展
本文实现了一个轻量级的置顶窗口小工具,核心逻辑围绕SetWindowPos与线程输入绑定展开。目前功能已满足日常使用,但仍有优化空间:例如增加取消置顶的下拉选项、支持右键菜单、保存置顶状态等。读者可以基于此代码自行扩展,如有疑问欢迎交流。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: 从零实现置顶任意窗口的Win32小工具

很实用的教程,感谢分享!自己写一个置顶工具确实能加深对 AttachThreadInput 和全局热键注册机制的理解。之前我也试过直接用 SetWindowPos 置顶,结果窗口经常不显示在前台,看了你的线程绑定方案才明白问题所在。下拉列表枚举窗口那个部分也很有参考价值,过滤桌面图标窗口的逻辑处理得很细致。已收藏,回头试试那个快捷键组合改成自己习惯的按键。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-5 16:23 , Processed in 0.027991 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部