Windows 自定义窗口锁定

本技术文档所用到的实例点击这里下载( 提取码:6cth),内含x86\x64不同处理器支持的版本。如果打开失败,使用另外一个版本:

Windows 自定义窗口锁定_第1张图片


记得网课期间使用的会议软件吗?课堂锁定\会议锁定 功能真的是。。。

要想实现这种功能其实并不难,有人会说使用ShowWindow再用SetFocus就可以了,其实不然,这样子做有些时候窗口还是会被覆盖。

如果用SetForegroundWindow函数将创建指定窗口的线程设置到前台,并且激活该窗口,也不是100%好用。

最后,也是几年前我发现User32有个函数SwitchToThisWindow可以实现选择到当前窗口,但前提是指定的窗口必须是Visable的。

SwitchToThisWindow函数原型:

WINAPI SwitchToThisWindow(
        HWND dwHwnds,        // 要切换到前列的窗口
        BOOL bEnable         // TRUE为切换
)

这个函数需要通过以下方法获得:

typedef void (WINAPI* PSWITCHTOTHISWINDOW) (HWND, BOOL);
    PSWITCHTOTHISWINDOW SwitchToThisWindow;
    HMODULE hUser32 = GetModuleHandle(TEXT("user32"));
    SwitchToThisWindow = (PSWITCHTOTHISWINDOW)GetProcAddress(hUser32, "SwitchToThisWindow");

然后,这样写即可让想放在前台的窗口切到前台,而不改变后面的窗口顺序。

ShowWindow(hwnd, SW_SHOW);//或者SW_NORMAL
SwitchToThisWindow(hwnd, TRUE);// 切前台窗口

然而,要是我们的窗口能够锁定,即要知道什么窗口正处于最上方,判断这个窗口是不是我们要的窗口。

这就用到GetForegroundWindow()找到前台窗口句柄然后和我们的窗口句柄进行比较,不同就Switch,当然你也可以把前台窗口最小化,甚至可以来个弹窗警告。

一个简单的判断如下,写在线程中即可:

HWND fwnd = GetForgroundWindow();
if (fwnd != hwnd)
{
  ...
  // 窗口没有处于前台,通过操作使hwnd成为前台,
  // Do What You Want!
}
std::cout << "窗口已经锁定" << endl;//在线程中不要使用printf,否则提示会频繁显示

当然,对于特殊的窗口我们要注意:比如桌面、任务栏等不能最小化,警告框等不能错误识别为其他窗口,否则警告会一直弹出。

带警告模式:

Windows 自定义窗口锁定_第2张图片

然后就是指定的窗口菜单栏标题栏按钮的改写,可以使得窗口不能关闭或者最小化:

    HMENU hmenu = GetSystemMenu(mhwnd, false);// 取菜单窗口句柄
    RemoveMenu(hmenu, SC_CLOSE, MF_BYCOMMAND);// 移除菜单项标识符,关闭按钮、最小化按钮
    LONG style = GetWindowLong(mhwnd, GWL_STYLE);// 获取窗口风格
    style &= ~(WS_MINIMIZEBOX);// 指定的风格为最小化按钮菜单
    SetWindowLong(mhwnd, GWL_STYLE, style);// 重设窗口风格
    SetWindowPos(mhwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
    // 改变窗口的位置与状态,HWND_TOPMOST使窗口置顶
    //SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
    ShowWindow(mhwnd, SW_SHOWNORMAL);// 窗口以正常大小显示
    DestroyMenu(hmenu);// 该函数销毁命令行(hmenu)的菜单,并释放此菜单占用的存储器。
    ReleaseDC(mhwnd, NULL);// 刷新DC数据,更新窗口

当然不建议在SetWindowPos中指定HWND_TOPMOST。

按照以上思路,尝试运行效果如下:

Windows 自定义窗口锁定_第3张图片

然而,这种消息窗口会被遮挡,解决方法就是发出消息框时候第一个参数设置为父窗口,也可以配合SetParent设置置顶窗口为子窗口。

然后,就是处理全屏效果:

/*以下函数用于计算具体的全屏大小,但不包括任务栏
             * 这是仅当任务栏位于下方时有效。
            调节高度lHeight,调节宽度pWidth
            */
            HWND hProgMan, hTrayWnd, hReBarWnd;
            int pWidth, pHeight, ctHeight, lHeight;
            RECT rect;
            // 取桌面窗口句柄,可以用GetDesktopWindow代替
            hProgMan = FindWindow(L"ProgMan", NULL);
            // 获取SHELL任务窗格(总窗口)
            hTrayWnd = FindWindow(L"Shell_TrayWnd", NULL);
            // 获取ReBarWindow32窗口句柄
            /*HWND WINAPI FindWindowExA(
            _In_opt_ HWND hWndParent,
            _In_opt_ HWND hWndChildAfter,
            _In_opt_ LPCSTR lpszClass,
            _In_opt_ LPCSTR lpszWindow);
            */
            hReBarWnd = FindWindowEx(hTrayWnd, NULL, L"ReBarWindow32", NULL);
            // 获取屏幕宽高参数
            pWidth = GetSystemMetrics(SM_CXSCREEN);
            pHeight = GetSystemMetrics(SM_CYSCREEN);
            // 获取ReBarWindow32窗口的客户区矩形
            GetClientRect(hReBarWnd, &rect);

            // 计算矩形高度,win10 默认任务栏客户区高度为40,小任务栏客户区高度为30
            ctHeight = (rect.bottom - rect.top);
            // 获得最终的高度参数
            lHeight = (pHeight - ctHeight);
            // 立即更新窗口大小
            SetWindowPos(ConsolehWnd, nullptr, 0, 0, pWidth, lHeight, SWP_FRAMECHANGED);
            ::SetWindowPos(ConsolehWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 置顶窗口

然后,就是窗口的处理部分,要排除消息框:

    HWND frhwnd;
	ULONG nProcessID;
    ULONG mProcessID;
    ConsolehWnd = FindWindow(NULL, L"EventCapturer");
    // HWND manhWnd = FindWindow(NULL, _T("EventCapturer"));
    msghWnd = FindWindow(NULL, L"EventCapturer-Event");
    frhwnd = GetForegroundWindow();// 使frhwnd代表最前端的窗口
    //id
	::GetWindowThreadProcessId(frhwnd, &nProcessID);
    ::GetWindowThreadProcessId(ConsolehWnd, &mProcessID);

    //  ShowWindow(msghWnd, SW_HIDE);
   
    if (nProcessID == mProcessID)
    {
            system("cls");
            std::cout << "前台窗口为EventCapturer窗口" << endl;
            SendMessage(msghWnd, 16, 0, 0);//关闭冗余消息窗口
            MsgIDCall = 0;//消息值状态清零
            Sleep(1000);
    }else if (frhwnd == msghWnd)
    {
            system("cls");
            std::cout << "请回复消息窗口" << endl;
            SetParent(msghWnd, ConsolehWnd);
            SwitchToThisWindow(msghWnd, TRUE);
            Sleep(2000);
    }
    else
    {
        system("cls");
        std::cout << "请回复消息窗口" << endl;
        // ShowWindow(msghWnd, SW_HIDE);
        ShowWindow(ConsolehWnd, SW_SHOW);
        SwitchToThisWindow(ConsolehWnd, TRUE);
        Sleep(100);
        MsgIDCall = MessageBoxA(ConsolehWnd,
            "禁止切换其他窗口!",
            "EventCapturer-Event",
            MB_OK | MB_ICONEXCLAMATION);
        if (MsgIDCall == IDOK)
        {
            // 为避免活动窗口为桌面时,ShowWindow错误判断
            // 导致程序窗口频繁重绘,使用User32的函数
            // SwitchToThisWindow重置窗口
            SwitchToThisWindow(ConsolehWnd, TRUE);
            /*以下函数用于计算具体的全屏大小,但不包括任务栏
             * 这是仅当任务栏位于下方时有效。
            调节高度lHeight,调节宽度pWidth
            */
            HWND hProgMan, hTrayWnd, hReBarWnd;
            int pWidth, pHeight, ctHeight, lHeight;
            RECT rect;
            // 取桌面窗口句柄,可以用GetDesktopWindow代替
            hProgMan = FindWindow(L"ProgMan", NULL);
            // 获取SHELL任务窗格(总窗口)
            hTrayWnd = FindWindow(L"Shell_TrayWnd", NULL);
            // 获取ReBarWindow32窗口句柄
            /*HWND WINAPI FindWindowExA(
            _In_opt_ HWND hWndParent,
            _In_opt_ HWND hWndChildAfter,
            _In_opt_ LPCSTR lpszClass,
            _In_opt_ LPCSTR lpszWindow);
            */
            hReBarWnd = FindWindowEx(hTrayWnd, NULL, L"ReBarWindow32", NULL);
            // 获取屏幕宽高参数
            pWidth = GetSystemMetrics(SM_CXSCREEN);
            pHeight = GetSystemMetrics(SM_CYSCREEN);
            // 获取ReBarWindow32窗口的客户区矩形
            GetClientRect(hReBarWnd, &rect);

            // 计算矩形高度,win10 默认任务栏客户区高度为40,小任务栏客户区高度为30
            ctHeight = (rect.bottom - rect.top);
            // 获得最终的高度参数
            lHeight = (pHeight - ctHeight);
            // 立即更新窗口大小
            SetWindowPos(ConsolehWnd, nullptr, 0, 0, pWidth, lHeight, SWP_FRAMECHANGED);
            ::SetWindowPos(ConsolehWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 置顶窗口
            return 0;//结束后退出,不执行下文
        }else
        {
            //ShowWindow(msghWnd, SW_HIDE);
            //SendMessage(msghWnd, 16, 0, 0);//关闭窗口
            Sleep(5000);
        }
        return 0;//结束后退出
    }

最后完整代码:

 1.EventHandle.h 头文件:

// EventHandle.h 头文件,保留备用
#pragma once
#include  
#include  
#include 
#include 
#include 
#include 
#include 

bool runing = true;
HANDLE m_hThreadHandle = NULL;

// 创建命名空间
using namespace std;
using std::endl;
HWND ConsolehWnd = nullptr;
HWND msghWnd = nullptr;
int MsgIDCall = 0;
unsigned __stdcall ThreadFunction(LPVOID pThreadData)
{

    while (runing)
    {
        std::cout << "@OnTime_Running" << endl;
        Sleep(1000);
    }
    return 0;
}

unsigned __stdcall CheckThreadFunction(LPVOID pThreadData)
{
    while (true)
    {
        Sleep(1000);
        DWORD dw = WaitForSingleObject(m_hThreadHandle, 10);
        if (dw == WAIT_TIMEOUT)
        {
            std::cout << "@TimeOut_ExitWait : " << dw << endl;
            Sleep(500);
        }
        else if (dw == WAIT_OBJECT_0)
        {
            std::cout << "@ObjectEvent : " << dw << endl;
            Sleep(500);
        }
        else if (WAIT_FAILED == dw)
        {
            std::cout << "@Failed : " << dw << endl;
            Sleep(500);
        }
        else
        {
            std::cout << "ThreadOtherEvent:" << dw << endl;
            Sleep(500);
        }
    }
    return 0;
}
void* BeginThreadEX(void* args)
{
    
    #include 

    m_hThreadHandle = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, NULL, 0, NULL);//

    HANDLE m_hThreadHandlecc = NULL;
    m_hThreadHandlecc = (HANDLE)_beginthreadex(NULL, 0, CheckThreadFunction, NULL, 0, NULL);//

    Sleep(5000);
    runing = false;
    Sleep(5000);
	
    return 0;

};
void* EventCapturerCMsgr(void* args)
{
	std::cout << "Msgr" << endl;

	HWND frhwnd;
	ULONG nProcessID;
    ULONG mProcessID;
    ConsolehWnd = FindWindow(NULL, L"EventCapturer");
    // HWND manhWnd = FindWindow(NULL, _T("EventCapturer"));
    msghWnd = FindWindow(NULL, L"EventCapturer-Event");
    frhwnd = GetForegroundWindow();// 使frhwnd代表最前端的窗口
    //id
	::GetWindowThreadProcessId(frhwnd, &nProcessID);
    ::GetWindowThreadProcessId(ConsolehWnd, &mProcessID);

    //  ShowWindow(msghWnd, SW_HIDE);
   
    if (nProcessID == mProcessID)
    {
            system("cls");
            std::cout << "前台窗口为EventCapturer窗口" << endl;
            SendMessage(msghWnd, 16, 0, 0);//关闭冗余消息窗口
            MsgIDCall = 0;//消息值状态清零
            Sleep(1000);
    }else if (frhwnd == msghWnd)
    {
            system("cls");
            std::cout << "请回复消息窗口" << endl;
            SetParent(msghWnd, ConsolehWnd);
            SwitchToThisWindow(msghWnd, TRUE);
            Sleep(2000);
    }
    else
    {
        system("cls");
        std::cout << "请回复消息窗口" << endl;
        // ShowWindow(msghWnd, SW_HIDE);
        ShowWindow(ConsolehWnd, SW_SHOW);
        SwitchToThisWindow(ConsolehWnd, TRUE);
        Sleep(100);
        MsgIDCall = MessageBoxA(ConsolehWnd,
            "禁止切换其他窗口!",
            "EventCapturer-Event",
            MB_OK | MB_ICONEXCLAMATION);
        if (MsgIDCall == IDOK)
        {
            // 为避免活动窗口为桌面时,ShowWindow错误判断
            // 导致程序窗口频繁重绘,使用User32的函数
            // SwitchToThisWindow重置窗口
            SwitchToThisWindow(ConsolehWnd, TRUE);
            /*以下函数用于计算具体的全屏大小,但不包括任务栏
             * 这是仅当任务栏位于下方时有效。
            调节高度lHeight,调节宽度pWidth
            */
            HWND hProgMan, hTrayWnd, hReBarWnd;
            int pWidth, pHeight, ctHeight, lHeight;
            RECT rect;
            // 取桌面窗口句柄,可以用GetDesktopWindow代替
            hProgMan = FindWindow(L"ProgMan", NULL);
            // 获取SHELL任务窗格(总窗口)
            hTrayWnd = FindWindow(L"Shell_TrayWnd", NULL);
            // 获取ReBarWindow32窗口句柄
            /*HWND WINAPI FindWindowExA(
            _In_opt_ HWND hWndParent,
            _In_opt_ HWND hWndChildAfter,
            _In_opt_ LPCSTR lpszClass,
            _In_opt_ LPCSTR lpszWindow);
            */
            hReBarWnd = FindWindowEx(hTrayWnd, NULL, L"ReBarWindow32", NULL);
            // 获取屏幕宽高参数
            pWidth = GetSystemMetrics(SM_CXSCREEN);
            pHeight = GetSystemMetrics(SM_CYSCREEN);
            // 获取ReBarWindow32窗口的客户区矩形
            GetClientRect(hReBarWnd, &rect);

            // 计算矩形高度,win10 默认任务栏客户区高度为40,小任务栏客户区高度为30
            ctHeight = (rect.bottom - rect.top);
            // 获得最终的高度参数
            lHeight = (pHeight - ctHeight);
            // 立即更新窗口大小
            SetWindowPos(ConsolehWnd, nullptr, 0, 0, pWidth, lHeight, SWP_FRAMECHANGED);
            ::SetWindowPos(ConsolehWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 置顶窗口
            return 0;//结束后退出,不执行下文
        }else
        {
            //ShowWindow(msghWnd, SW_HIDE);
            //SendMessage(msghWnd, 16, 0, 0);//关闭窗口
            Sleep(5000);
        }
        return 0;//结束后退出
    }
    return 0;//退出
};

 2.EventCapturer.cpp : 

// EventCapturer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include 
#include  
#include  
#include 
#include 
#include 
#include 
#include "EventHandle.h"

#pragma comment(lib, "pthreadVCE2.lib")

#define NUM_THREADS_1 1
#define NUM_THREADS_2 1
    using namespace std;
    using std::endl;
    HWND mhwnd = nullptr;
int main()
{
    SetConsoleTitle(_T("EventCapturer"));
    Sleep(1000);
    mhwnd = FindWindow(NULL, L"EventCapturer");
    HMENU hmenu = GetSystemMenu(mhwnd, false);// 取菜单窗口句柄
    RemoveMenu(hmenu, SC_CLOSE, MF_BYCOMMAND);// 移除菜单项标识符,关闭按钮、最小化按钮
    LONG style = GetWindowLong(mhwnd, GWL_STYLE);// 获取窗口风格
    //style &= ~(WS_MINIMIZEBOX);// 指定的风格为最小化按钮菜单
    SetWindowLong(mhwnd, GWL_STYLE, style);// 重设窗口风格
    SetWindowPos(mhwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);// 改变窗口的位置与状态,HWND_TOPMOST使窗口置顶
    //SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
    ShowWindow(mhwnd, SW_SHOWNORMAL);// 窗口以正常大小显示
    DestroyMenu(hmenu);// 该函数销毁命令行(hmenu)的菜单,并释放此菜单占用的存储器。
    ::ReleaseDC(mhwnd, NULL);// 刷新DC数据,更新窗口

    /*以下函数用于计算具体的全屏大小,但不包括任务栏
         * 这是仅当任务栏位于下方时有效。
        调节高度lHeight,调节宽度pWidth
        */
    HWND hProgMan, hTrayWnd, hReBarWnd;
    int pWidth, pHeight, ctHeight, lHeight;
    RECT rect;
    // 取桌面窗口句柄,可以用GetDesktopWindow代替
    hProgMan = FindWindow(L"ProgMan", NULL);
    // 获取SHELL任务窗格(总窗口)
    hTrayWnd = FindWindow(L"Shell_TrayWnd", NULL);
    // 获取ReBarWindow32窗口句柄
    /*HWND WINAPI FindWindowExA(
    _In_opt_ HWND hWndParent,
    _In_opt_ HWND hWndChildAfter,
    _In_opt_ LPCSTR lpszClass,
    _In_opt_ LPCSTR lpszWindow);
    */
    hReBarWnd = FindWindowEx(hTrayWnd, NULL, L"ReBarWindow32", NULL);
    // 获取屏幕宽高参数
    pWidth = GetSystemMetrics(SM_CXSCREEN);
    pHeight = GetSystemMetrics(SM_CYSCREEN);
    // 获取ReBarWindow32窗口的客户区矩形
    GetClientRect(hReBarWnd, &rect);

    // 计算矩形高度,win10 默认任务栏客户区高度为40,小任务栏客户区高度为30
    ctHeight = (rect.bottom - rect.top);
    // 获得最终的高度参数
    lHeight = (pHeight - ctHeight);
    // 立即更新窗口大小
    SetWindowPos(mhwnd, nullptr, 0, 0, pWidth, lHeight, SWP_FRAMECHANGED);
    ::SetWindowPos(mhwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 置顶窗口

	typedef void (WINAPI* PSWITCHTOTHISWINDOW) (HWND, BOOL);
    PSWITCHTOTHISWINDOW SwitchToThisWindow;
    HMODULE hUser32 = GetModuleHandle(_T("user32"));
    SwitchToThisWindow = (PSWITCHTOTHISWINDOW)GetProcAddress(hUser32, "SwitchToThisWindow");
	bool bEnable = true;
    while (bEnable) {
    //line2:
    if (GetForegroundWindow() == mhwnd)
    {
        system("cls");
        std::cout << "当前状态∶窗口已置顶" << endl;
		bEnable = true;
    }else
    {
        HWND msghWnd = FindWindow(NULL, _T("EventCapturer-Event"));
        HWND frhwnd;
        frhwnd = GetForegroundWindow();// 使frhwnd代表最前端的窗口
        if (frhwnd == mhwnd)
        {
            system("cls");
            std::cout << "当前状态∶用户返回了主窗口" << endl;
            // 使用User32的函数SwitchToThisWindow重置窗口
            SwitchToThisWindow(mhwnd, TRUE);
            SetWindowPos(mhwnd, nullptr, 0, 0, pWidth, lHeight, SWP_FRAMECHANGED);
            ::SetWindowPos(mhwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // 置顶窗口
            Sleep(2000);
        }else
        {
        ShowWindow(frhwnd, SW_MINIMIZE);
        ShowWindow(mhwnd, SW_SHOW);
        Sleep(100);
        // 定义线程的 id 变量,
        pthread_t tids1[NUM_THREADS_1];
            int ret1 = pthread_create(tids1, NULL, BeginThreadEX, NULL);// 线程
            if (ret1 != 0)
            {
            //  cout << "\n" << endl;
                cout << "PTHREAD_CREATE ERROR: Position: BeginThreadEX; \nError_Code=" << ret1 << endl;
                Sleep(2000);
            }
     
        }
        pthread_t tids2[NUM_THREADS_2];
        int ret2 = pthread_create(tids2, NULL, EventCapturerCMsgr, NULL);// 线程
        if (ret2 != 0)
        {
            
            cout << "PTHREAD_CREATE ERROR: Position: EventCapturerCMsgr; \nError_Code=" << ret2 << endl;
            Sleep(2000);
        }
        //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
        // pthread_exit(NULL);
		bEnable = true;
    }
        // 为避免活动窗口为桌面时,ShowWindow错误判断导致程序窗口频繁重绘,使用User32的函数SwitchToThisWindow重置窗口
        //SwitchToThisWindow(mhwnd, TRUE);
        Sleep(1000);
        //goto line2;
		bEnable = true;
        continue;
    };
    pthread_exit(NULL);
    Sleep(2000); 
}

要知道是否置顶,必须对窗口大小,窗口焦点进行监视。将消息框的处理作为子窗口,并写入了冗余消息提示的关闭处理,从而解决了之前存在的问题,切换其他窗口后窗口恢复不在延迟。
并且可以使用小任务栏存放子窗口(额,我承认这一点是为了完全模拟无限宝课后网,因为他的处理也差不多)

运行效果: 

Windows 自定义窗口锁定_第4张图片

好,就到这里。 

你可能感兴趣的:(c++,windows,visual,studio)