Re0: 从零实现一个置顶任意窗口的小工具

前言

话不多说,先上效果:

这里展示的是通过下拉框选择窗口,让窗口显示并置顶,其实还可以直接通过快捷键(先鼠标点击要置顶的窗口,再使用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函数使用,但是由于官网中提到的如下限制:

Re0: 从零实现一个置顶任意窗口的小工具_第1张图片

因此还需要使用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快捷键进行取消置顶的操作。

总结

本文简单实现了一个置顶任意窗口的小工具,实现思路很简单,还只算是一个半成品,不过核心功能已经有了,只需要自己额外进行一些扩展就足够日常使用,欢迎一起交流讨论。

你可能感兴趣的:(C,工具,windows,c++)