深度学习训练图片收集器——C++截图程序的实现2(键鼠钩子篇)

版权声明:本文为博主原创文章,如需转载请注明出处。因博主水平有限,如有疏忽遗漏,敬请指出。 https://blog.csdn.net/ShadowN1ght/article/details/78344270

在上一节《深度学习训练图片收集器——C++截图程序的实现1(需求分析篇)》中,我们分析了设计一个独立的截图程序的必要性。

在本节中,我们将给出VisualStudio C++的键鼠钩子设置代码,以使程序能够监听用户按下Alt+A组合键的事件,以及鼠标拖拽截图矩形的事件。

本程序的运行需要搭配DebugView工具以查看日志输出。DebugView是一个轻量级的日志查看工具,免安装,使用方便,可以在百度搜索下载源。

我使用的VS版本是VS2013。你也可以使用其它版本的VS。

现在开始演示实现步骤。

首先打开VS,点“新建项目...”,在弹出的界面中选择“MFC”→“MFC应用程序”,在名称中输入“ScreenshotForML”,如下图所示:


点击“确定”,再选“下一步”,在弹出的选项页中勾选“基于对话框”:


点击“下一步”,在弹出的界面中取消“关于框”,选中“最小化框”,再点击“下一步”,直至完成。

设置完成后,VS跳转到代码编辑器界面。我们需要在编辑器界面的菜单工具栏中将debug改为release,如下图:


这时候按F7,或者点击菜单栏中的“生成---生成解决方案”,再打开硬盘的C:\ScreenshotForML\ScreenshotForML\Release目录,会看到生成的exe文件,打开exe,界面如下:


界面上有多余的按钮和静态文本。目前我们暂时不需要这些控件,可以进入VS资源视图,把这些按钮和文本删掉,再把整个对话框拉小一点,如下图所示:


按F7重新生成,启动后界面如下:


看起来干净清爽很多。当后面有需要时,我们会加入一些控件。

接下来我们需要加入用于安装键鼠钩子和监听键鼠事件的dll工程。

右键解决方案“ScreenshotForML”,选择“添加---新建项目”,在弹出窗口中选择“MFC---MFC DLL”,名称输入“kb_mouse_listener”,如下图所示:


然后点击“确定”——“下一步”——“完成”。

现在可以看到解决方案视图中多出了一个项目kb_mouse_listener:


双击kb_mouse_listener.def文件,在最后一行输入“Init_KeyboardListening”,如下图:


然后双击kb_mouse_listener.cpp文件,在最后面加上以下代码:

#define WM_START_SCREENSHOT                  WM_USER + 111
#define WM_MOUSE_LEFT_DOWN                   WM_USER + 113
#define WM_MOUSE_LEFT_UP                          WM_USER + 114
#define WM_MOUSE_MOVE                                WM_USER + 115
#define WM_MOUSE_DBCLICK                          WM_USER + 116
#define WM_ESC_PRESSED                               WM_USER + 117

#define DOUBLE_CLICK_INTERVAL                   350                                 // 鼠标左键双击时间间隔阈值

HHOOK hHook_kb = NULL;
HHOOK hHook_mouse = NULL;

bool       g_bIsComboKey_AltA_pressed = false;       // 记录Alt+a组合键是否被按下
bool       g_bStartDrawingRect = false;                        // 记录是否开始截图
bool       g_bHasDrawnValidRect = false;                    // 记录是否已绘制有效矩形

int          g_nLastMouseUpTime = 0;

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    MOUSEHOOKSTRUCT mouse_info = *((PMOUSEHOOKSTRUCT)lParam);   // 将lParam指向的内容拷贝到mouse_info中

    char msg[200] = "11111 ";
    HWND hwnd = NULL;

    switch (wParam)
    {
    case WM_LBUTTONDOWN:
        if (g_bIsComboKey_AltA_pressed) {
            g_bStartDrawingRect = true;

            OutputDebugStringA("11111 鼠标左键被按下");

            // 鼠标左键被按下,发送绘制截图区域的消息
            hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning (full screen)");
            int ret = PostMessage(hwnd, WM_MOUSE_LEFT_DOWN, NULL, NULL);
        }
        break;

    case WM_LBUTTONUP:
        if (g_bStartDrawingRect) {
            hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning (full screen)");

            // 如果两次左键弹起的时间间隔小于阈值,就视为发生了双击事件
            int   nClickInterval = GetTickCount() - g_nLastMouseUpTime;
            if ((nClickInterval <= DOUBLE_CLICK_INTERVAL) && (g_bHasDrawnValidRect)) {
                PostMessage(hwnd, WM_MOUSE_DBCLICK, NULL, NULL);
                g_bIsComboKey_AltA_pressed = false;
                g_bStartDrawingRect = false;
                g_bHasDrawnValidRect = false;
                g_nLastMouseUpTime = 0;

                OutputDebugStringA("11111 发生了双击事件");

                // 这里sleep 100毫秒是为了防止本次鼠标点击事件传递到截图之前的前景窗口,但后来加了return 1似乎就不会再传递
                Sleep(100);
                return 1;
            }
            g_nLastMouseUpTime = GetTickCount();

            // 鼠标左键弹起,结束当前截图区域的绘制,截图区域的左上顶点和右下顶点由CScreenShotView类负责保存
            g_bStartDrawingRect = false;
            PostMessage(hwnd, WM_MOUSE_LEFT_UP, NULL, NULL);
        }
        break;

    case WM_MOUSEMOVE:
        if (g_bStartDrawingRect) {
            g_bHasDrawnValidRect = true;

            OutputDebugStringA("11111 鼠标移动");

            // 截图区域随着鼠标移动而变化
            hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning (full screen)");
            PostMessage(hwnd, WM_MOUSE_MOVE, NULL, NULL);
        }
        break;

    default:
        break;
    }

    return CallNextHookEx(0, nCode, wParam, lParam);
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    char szKey[2] = "";
    char msg[200] = "11111 ";
    int nProcID = 0;
    HWND hwnd = NULL;

    KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT*)lParam;

    // alt + a 组合键按下,代表开始截图
    if ((pkbhs->vkCode == 65) && ((pkbhs->flags == 32) || (pkbhs->flags == 160))) {
        if (!g_bIsComboKey_AltA_pressed) {
            g_bIsComboKey_AltA_pressed = true;

            OutputDebugStringA("11111 alt + a 组合键按下");

            // 发送截图请求给主程序
            hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning");
            PostMessage(hwnd, WM_START_SCREENSHOT, NULL, NULL);
        }
    }

    // esc键按下,代表截图取消
    if ((pkbhs->vkCode == 27) && (pkbhs->flags == 0)) {
        if (g_bIsComboKey_AltA_pressed) {
            g_bIsComboKey_AltA_pressed = false;

            OutputDebugStringA("11111 esc键按下");

            // 发送该事件给主程序
            hwnd = ::FindWindowA(NULL, "ScreenShot for Machine Learning (full screen)");
            PostMessage(hwnd, WM_ESC_PRESSED, NULL, NULL);

            return 1;
        }
    }

    return CallNextHookEx(0, nCode, wParam, lParam);
}

void Init_KeyboardListening()
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // 安装底层键盘钩子
    hHook_kb = ::SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandleA("mouse_kb_listener.dll"), 0);
    hHook_mouse = ::SetWindowsHookEx(WH_MOUSE_LL, MouseProc, GetModuleHandleA("mouse_kb_listener.dll"), 0);
}

其中,各段代码的作用已经在注释中进行了说明。

代码的整体思路是先调用SetWindowsHookEx()函数安装低级键鼠钩子,设置回调函数,当发生键鼠事件时,回调函数KeyboardProc()和MouseProc()会被执行。在KeyboardProc()中,根据KBDLLHOOKSTRUCT结构体的vkCode和flags判断Alt+a组合键和esc键是否被按下。在MouseProc()中,根据wParam判断鼠标事件类型,监听鼠标的单击、双击、以及移动事件。

在kb_mouse_listener.cpp文件中添加以上代码后,按F7,VS会在C:\ScreenshotForML\ScreenshotForML\Release目录生成kb_mouse_listener项目的lib和dll文件,如下图:

现在将kb_mouse_listener.lib文件拷贝到C:\ScreenshotForML\ScreenshotForML\ScreenshotForML目录下,以便ScreenshotForML项目可以调用kb_mouse_listener项目的函数Init_KeyboardListening()

然后,右键ScreenshotForML项目,选择“属性”,在弹出的属性窗口中依次选择“链接器”→“输入”→“附加依赖项”,在附加依赖项中输入“kb_mouse_listener.lib”,如下图:


点确定,再点确定。

然后打开ScreenshotForMLDlg.cpp文件,在头部的空白处加入以下引用声明:

_declspec (dllimport) void Init_KeyboardListening();

并在OnInitDialog()函数中加入以下代码:

// 注册键盘鼠标消息钩子
Init_KeyboardListening();

现在,按F7,重新生成exe。启动exe,程序开始进入监听键鼠事件的状态,这时候如果打开DebugView,并且按alt+A,再移动鼠标,会看到以下日志输出:



至此,我们实现了截图操作相关键盘鼠标事件的监听。下一节将介绍主程序ScreenshotForML如何响应键鼠事件进行截图操作,详见《深度学习训练图片收集器——C++截图程序的实现3(主对话框响应键鼠消息进行截图) 》


猜你喜欢

转载自blog.csdn.net/ShadowN1ght/article/details/78344270