话不多说,先上效果:
这里展示的是通过下拉框选择窗口,让窗口显示并置顶,其实还可以直接通过快捷键(先鼠标点击要置顶的窗口,再使用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 29
HWND 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
快捷键进行取消置顶的操作。
本文简单实现了一个置顶任意窗口的小工具,实现思路很简单,还只算是一个半成品,不过核心功能已经有了,只需要自己额外进行一些扩展就足够日常使用,欢迎一起交流讨论。