2020-11-17-c++-mfc-捕捉键盘组合键事件

走成华大道,到二仙桥,练腰马合一

[TOC]

纳闷啊,这基础的东西没找到写的比较清楚了,大家藏私?把自己用到的整理了一下。

MFC捕捉键盘组合键事件

PreTranslateMessage 捕获消息

在MFC中捕捉按键状态,我是通过实现PreTranslateMessage来实现的。如下通过重写这个方法:

头文件

class CXxxDialog : public CDialogEx {

public:
    virtual BOOL PreTranslateMessage(MSG* pMsg);

};

源文件

BOOL CXxxDialog::PreTranslateMessage(MSG* pMsg) {
    /* 在这里搞事情 */
    return FALSE;
}

参数MSG

主要关心的就是这个参数 MSG中的message和wParam参数。

typedef struct tagMSG {     // msg  
   HWND hwnd;		/* 接收消息的窗口 */
   UINT message;	/* 指示按键类型,比如是WM_KEYDOWN(按键按下),WM_SYSKEYDOWN(系统按键按下),WM_KEYUP(按键松开)等等,详细看参考。 *
   WPARAM wParam;	/* 就是按下的按键值,比如我们按下A,那它的值就是'A',我们按下9,它的按键值就是'9',按下F5就是VK_F5 详细看参考。 */
   LPARAM lParam;	/* 根据message的不同指示包含不同的内容,这个详细看某种类型,比如WM_KEYDOWN的详细介绍,里面就有说每个位是什么用。*/
   DWORD time;		/* 指定消息已传递的时间 */
   POINT pt;		/* 当前鼠标的坐标 */
} MSG;

返回值

如果返回TRUE则表示该消息已被处理了,不会再被传播下去。

如果返回FALSE则该消息会被继续传播下去(传递给TranslateMessage和DispatchMessage)。

单键触发事件demo

BOOL CXxxDialog::PreTranslateMessage(MSG* pMsg) {
    
    if (WM_KEYDOWN == pMsg->message) {  /* 键盘按下事件 */
    
        if ('F' == pMsg->wParam) {
            MessageBox("press F");
            return TRUE;                /* 如上所说,如果认为该按键事件已处理了,就返回true,不需要后面再传播了。 */
        }
        
        if ('U' == pMsg->wParam) {
            MessageBox("press U");
            return FALSE;               /* 如上所说,如果认为该按键事件没处理了,就返回false,事件继续传播 */
        }
        
        if ('C' == pMsg->wParam) {
            MessageBox("press C");
            return TRUE;
        }
        
        if ('K' == pMsg->wParam) {
            MessageBox("press K");
            return TRUE;
        }
        
        if (VK_F5 == pMsg->wParam) {
            MessageBox("press F5");
            return TRUE;
        }
        
        if (VK_RETURN == pMsg->wParam) {
            MessageBox("press enter");
            return TRUE;
        }
    }
    
#if 0
    /* 当关注多个事件的时候用switch比较好 */
    switch (pMsg->message) {
        case WM_KEYDOWN: {
            break;
        }
        case WM_SYSKEYDOWN: {
            break;
        }
        default: {
        }
    }
#endif
    
    return CDialogEx::PreTranslateMessage(pMsg);
}

GetKeyState() 获取CTRL/ALT/SHIFT按键的状态

前面是单键的,不过我们玩快捷键一般是组合键,如ctrl + ?之类的。要玩组合键,那就要知道CTRL/ALT/SHIFT这几个按键的状态,通过GetKeyState可以获取到这几个键的状态。

SHORT GetKeyState( 
 int nVirtKey 
);

参数 nVirtKey

根据文档此处支持下面几类按键的状态获取,包括该类按键整体(如VK_CONTROL)状态,左按键状态(如VK_LCONTROL),右按键状态(如VK_RCONTROL)的单独获取。

VK_CONTROL
VK_RCONTROL
VK_LCONTROL

VK_RMENU
VK_LMENU
VK_MENU			/* 这个是ALT键 */

VK_LSHIFT
VK_RSHIFT
VK_SHIFT

在Windows CE 1.0 到 2.01,,GetKeyState仅用于检测capital(大小写键)是否被激活,从WindowsCE 2.10之后,VK_NUMLOCK(数字锁键)也可以被检测了。

返回值

返回值是一个SHORT值:

  • 最高位为1时说明该键被按下,为0时则没有按下
  • 最低位为1说明该键被激活(老外叫toggle,我就叫激活了),以caps键为例(就是切换大小写的键),被激活时为大写(键盘大写灯亮了),没有激活时为小写

如下:判断当前是大写还是小写

if (0X01 & GetKeyState(VK_CAPITAL)) {       /* 激活状态,当前为大写 */
    MessageBox("it's upper-case now");
} else {                                    /* 激活状态,当前为小写 */
    MessageBox("it's lower-case now");
}

在按下caps键的时候判断是切换为大写或者小写

if (0X8000 & GetKeyState(VK_CAPITAL)) {         /* 按下caps键 */
    if (0X01 & GetKeyState(VK_CAPITAL)) {       /* 切换为大写 */
        MessageBox("switch to upper-case");
    } else {                                    /* 切换为小写 */
        MessageBox("switch to lower-case");
    }
}

同样的对于VK_CONTROL(万万没想到ctrl/alt还有激活状态,因为平常没用到吧~)以及VK_SHIFT也是同样操作的。

组合键demo1 - 愉快组合键

现在我们就可以愉快的玩组合键了

BOOL CXxxDialog::PreTranslateMessage(MSG* pMsg) {
    
    if (WM_KEYDOWN == pMsg->message) {          /* 键盘按下事件 */
    
        if ('F' == pMsg->wParam && (0X8000 & GetKeyState(VK_CAPITAL))) {
            MessageBox("press CTRL + F");       /* 捕获CTRL + F事件*/
            return TRUE;
        }
        
        if ('U' == pMsg->wParam && (0X8000 & GetKeyState(VK_CAPITAL)) {
            MessageBox("press CTRL + U");		/* 捕获CTRL + U事件*/
            return FALSE;               
        }
    }

    return FALSE;
}

组合键demo2 - 怎么 CTRL + ALT + ? 也触发了 CTRL + ?事件?

按照组合键demo1的方式,有个问题,下面几种按键方式都能触发弹框显示press CTRL + F,这好吗?这不好。(如果你只关注CTRL+?的键盘事件而不关心CTRL+ALT+?的键盘事件,其实也无伤大雅,不过软件看起来还是专业点的好 ) (~。~)

  • CTRL+F
  • CTRL + SHIFT + F
  • CTRL + ALT + F
  • CTRL+ SHIFT + ALT + F

头文件

class CXxxDialog : public CDialogEx {

public:
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    bool isAltDown(MSG* pMsg);
    bool isCtrlDown();
    bool isShiftDown();
    bool isAltDown();
    /* 有了上面3个基础的方法,就可以根据需要进行扩展,如下面这个我只要ctrl + ?的按键 */
    bool isCtrlWith(MSG* pMsg, const char * cKey);
    
};

源文件

bool CXxxDialog::isAltDown(MSG* pMsg) { return (pMsg->lParam & 0x20000000); }
bool CXxxDialog::isCtrlDown() { return (GetKeyState(VK_CONTROL) & 0X8000); }
bool CXxxDialog::isShiftDown() { return (GetKeyState(VK_SHIFT) & 0X8000); }
bool CXxxDialog::isAltDown() { return (GetKeyState(VK_MENU) & 0X8000); }
bool isCtrlWith(MSG* pMsg, const char * cKey) {
	return (cKey == pMsg->wParam && isCtrlDown() && !isAltDown() && !isShiftDown());
}

BOOL CXxxDialog::PreTranslateMessage(MSG* pMsg) {
    if (pMsg->message == WM_KEYDOWN) {

        if (isCtrlWith(pMsg, 'F')) {
            MessageBox("press ctrl + F");		/* 好了现在就只有CTRL + F能够触发这里了 */
            return TRUE;
        }
        
        if (isCtrlWith(pMsg, 'U')) {
            MessageBox("press ctrl + U"); 		/* 好了现在就只有CTRL + U能够触发这里了 */
            return TRUE;
        }
    }
    return CDialogEx::PreTranslateMessage(pMsg);
}

关于bool isAltDown(MSG* pMsg)方法

在上面这个例子中可以看到有两个判断是否按下ALT键的方法,这个是通过pMsg->lParam & 0x20000000获取的,另一个是使用GetKeyState获取的。

GetKeyState()获取的就不赘述了,网络上我看到有人是通过这种方法获取到Alt的状态,MSDN文档中说了这个位(从右往左数第29位)是 context code,这个位为1就是ALT按下,为0则反之。

The context code indicates whether the ALT key was down when the keystroke message was generated. The code is 1 if the ALT key was down and 0 if it was up.

/* lParam 32位
 *    2    0    0    0    0    0    0    0
 * 0010 0000 0000 0000 0000 0000 0000 0000
 */

这就是为什么通过这个,能够取到alt的状态。

bool CXxxDialog::isAltDown(MSG* pMsg) { return (pMsg->lParam & 0x20000000); }

组合键demo3 - 怎么捕捉不了 ALT+ ? 事件?

当尝试捕捉 ALT + ? 的快捷键的时候,发现在WM_KEYDOWN中抓不到了,这个时候就需要了解一下WM_KEYDOWN和WM_SYSKEYDOWN的区别。

在windows中ALT被认为是系统菜单键(我猜想这就是为啥它的宏定义是VK_MENU而不是VK_ALT,我刚开始还以为VK_MENU是那个和右键一样功能的菜单键),如我们切换程序使用 ALT + TAB。 如果想要使用 ALT + ? 的快捷键的时候,要捕捉message为WM_SYSKEYDOWN的事件。

如下:(头文件就不贴了,加个isAltWith()方法)

bool CXxxDialog::isAltWith(MSG* pMsg, const char * cKey) {
	return (cKey == pMsg->wParam && !isCtrlDown() && isAltDown() && !isShiftDown());
}

BOOL CXxxDialog::PreTranslateMessage(MSG* pMsg) {
	switch (pMsg->message) {}
		caes WM_KEYDOWN: {
            if (isCtrlWith(pMsg, 'F')) {
                MessageBox("press ctrl + F");
                return TRUE;
            }
            if (isCtrlWith(pMsg, 'U')) {
                MessageBox("press ctrl + U");
                return TRUE;
            }
            break;
        }
        case WM_SYSKEYDOWN: {	/* 捕捉ATL + 事件*/
        	if (isAltWith(pMsg, 'C')) {
                AlertInfo("system alt + C");
	            return TRUE;
            }
            if (isAltWith(pMsg, 'K')) {
                AlertInfo("system alt + K");
                return TRUE;
            }
            break;
        }
    }
    return CDialogEx::PreTranslateMessage(pMsg);
}

参考