前言
话不多说,先上效果:
这里展示的是通过下拉框选择窗口,让窗口显示并置顶,其实还可以直接通过快捷键(先鼠标点击要置顶的窗口,再使用CTRL+SHIFT+T
),本文涉及到的完整代码已上传到GitHub,也可以选择直接下载exe(35k)体验。
实现
实现显示并置顶功能
其实置顶的核心功能就是通过SetWindowPos函数实现的:
// 置顶窗口
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);// 取消置顶
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE);
不过如果只使用该函数,无法满足窗口未处于前台(例如最小化状态)无法显示在前台的情况,需要手动让窗口处于前台才能满足最终效果,因此还需要结合SetForegroundWindow函数使用,但是由于官网中提到的如下限制:
因此还需要使用AttachThreadInput函数进行处理,最终的函数如下:
// 展示并置顶窗口
void topwin(HWND hWnd)
{// 通过快捷键方式不需要传递窗口句柄if (hWnd == nullptr) {hWnd = GetForegroundWindow();}DWORD currentId = GetCurrentThreadId();DWORD topId = GetWindowThreadProcessId(GetForegroundWindow(), NULL);AttachThreadInput(currentId, topId, TRUE);// 展示并置顶窗口ShowWindow(hWnd, SW_SHOWNORMAL);SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);SetForegroundWindow(hWnd);AttachThreadInput(currentId, topId, FALSE);
}
快捷键方式实现
讲完置顶功能后,再说一下如何将功能和快捷键进行关联。
首先,需要注册快捷键(CTRL+SHIFT+T
用于置顶,CTRL+SHIFT+C
用于取消置顶):
int topHotkeyId = 1;
int untopHotkeyId = 2;// 注册全局快捷键
if (!RegisterHotKey(NULL, topHotkeyId, MOD_CONTROL | MOD_SHIFT, 'T')) {return 1;
}if (!RegisterHotKey(NULL, untopHotkeyId, MOD_CONTROL | MOD_SHIFT, 'C')) {return 1;
}
然后就可以在消息循环中处理快捷键事件:
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);if (msg.message == WM_HOTKEY) {HWND hWnd = GetForegroundWindow();if (msg.wParam == topHotkeyId) {topwin(nullptr);}if (msg.wParam == untopHotkeyId) {SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE);}}}
}
WM_HOTKEY 的处理需要放在 DispatchMessage(&msg); 之后,否则可能出现打包后的 exe 无法正常运行。在使用快捷键方式置顶窗口时,需要先选中指定窗口使其获取焦点。
下拉列表方式实现
最后再讲一下如何实现下拉列表选择窗口进行置顶。
这里先展示获取窗口列表的具体实现:
std::vector<HWND> windows;// 判断窗口是否在桌面正常显示
bool IsWindowOnDesktop(HWND hwnd) {WINDOWPLACEMENT placement = { sizeof(WINDOWPLACEMENT) };if (GetWindowPlacement(hwnd, &placement)) {return placement.showCmd == SW_SHOWNORMAL;}return false;
}// 遍历窗口并获取窗口标题展示在下拉框中
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {if (IsWindowVisible(hwnd) && !IsWindowOnDesktop(hwnd)) {int titleLength = GetWindowTextLength(hwnd);if (titleLength > 0) {std::wstring windowTitle(titleLength + 1, L'\0');GetWindowText(hwnd, &windowTitle[0], titleLength + 1);// 将窗口标题加入到下拉列表中SendMessageW(hComboBox, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(windowTitle.c_str()));windows.push_back(hwnd);}}return TRUE;
}// 遍历窗口
EnumWindows(EnumWindowsProc, 0);
结合代码可以直观的知道就是先进行窗口遍历,获取符合条件的窗口(窗口可见且是在桌面正常显示的窗口)标题。此外通过windows.push_back(hwnd);
保存窗口句柄,用于后续选中条件的处理。
实现了获取窗口标题列表的功能后,再来说明一下如何将数据填充到下拉列表中并处理选择事件:
#define MENU_ID 29HWND hComboBox;
// 创建下拉框
hComboBox = CreateWindowW(L"ComboBox", L"", CBS_DROPDOWN | WS_CHILD | WS_VISIBLE,10, 10, 360, 200, hWnd, (HMENU)MENU_ID, nullptr, nullptr);// 设置默认选中项
SendMessageW(hComboBox, CB_SETCURSEL, 0, 0);case WM_COMMAND:{// 处理下拉列表选中事件if (HIWORD(wParam) == CBN_SELCHANGE) {LRESULT selectedIndex = SendMessageW(GetDlgItem(hWnd, MENU_ID), CB_GETCURSEL, 0, 0);topwin(windows.at(selectedIndex));}}break;
通过代码不难理解,就是创建一个下拉框组件,并设置唯一标识MENU_ID
,之后处理相应的下拉选择事件即可,窗口句柄通过windows.at(selectedIndex)
进行获取。
这里暂时未实现取消置顶的方式,可以使用
CTRL+SHIFT+C
快捷键进行取消置顶的操作。
总结
本文简单实现了一个置顶任意窗口的小工具,实现思路很简单,还只算是一个半成品,不过核心功能已经有了,只需要自己额外进行一些扩展就足够日常使用,欢迎一起交流讨论。