分享

Windows消息处理

 刀下巴 2015-02-21

1,虚拟键(VK_*

键盘上每一个键对应一个扫描码,扫描码是OEM厂商制定的,不同的厂商生产的键盘同样一个按键的扫描码都有可能出现不一致的情况,为了摆脱由于系统设备不一致的情况,通过键盘驱动程序将扫描码映射为统一的虚拟键码表示,从而达到所有的设备都有一个统一的虚拟键,比如回车键的虚拟键是VK_RETURN

Windows定义的虚拟键都定义在WinUser.h这个头文件里面,都是以VK_作为前缀。

 

2,激活/关闭窗口对键盘的消息

激活/关闭消息:WM_SETFOCUS/WM_KILLFOCUS

创建光标:CreateCaret(...)

设置光标位置:SetCaretPos(…)

在窗口中显示光标:ShowCaret(…)

销毁光标:DestroyCaret()

 

3,键盘消息

1)字符消息

系统字符消息

WM_SYSCHAR:系统字符

WM_SYSDEADCHAR:系统死字符

非系统按键消息

WM_CHAR:非系统字符

WM_DEADCHAR:非系统死字符

2)按键消息

系统按键消息:与ALT键相组合的组合键(无论用户处理否,都需要最后调用DefWindowProc(hWnd,iMessage,wParam,lParam))

WM_SYSKEYDOWN

WM_SYSKEYUP

非系统按键消息:

WM_KEYDOWN

WM_KEYUP

注意:

a)  除Print键之外都有“按下”消息。

b) 所有键都存在“弹起”消息。

c)  根据MSDN说明,只有下面这些键才会产生字符消息:

  • 任何字符键
  • 回退键(BACKSPACE
  • 回车键(carriage return
  • ESC
  • SHIFT + ENTER linefeed 换行)
  • TAB

 

我们是怎么收到WM_CHAR的呢?就是因为我们在消息循环时调用了TranslateMessage对键盘消息进行翻译,

如果消息为WM_KEYDOWN或者WM_SYSKEYDOWN,并且按键与位移状态相组合产生一个字符,则TranslateMessage把字符消息放入消息队列中。此字符消息将是GetMessage从消息队列中得到的按键消息之后的下一个消息。

 

在我们处理这个消息时,对应的wParam不是虚拟键,而是ANSIUnicode字符代码,一般情况下我们可以这样用:  (TCHAR)wParam;

 

4,消息顺序

因为TranslateMessage函数从WM_KEYDOWN和WM_SYSKEYDOWN消息产生了字符消息,所以字符消息是夹在按键消息之间传递给窗口消息处理程序的。例如,如果Caps Lock未打开,而使用者按下再释放A键,则窗口消息处理程序将接收到如表6-10所示的三个消息:

 

表6-10

消息

按键或者代码

WM_KEYDOWN

「A」的虚拟键码(0x41)

WM_CHAR

「a」的字符代码(0x61)

WM_KEYUP

「A」的虚拟键码(0x41)

 

如果您按下Shift键,再按下A键,然后释放A键,再释放Shift键,就会输入大写的A,而窗口消息处理程序会接收到五个消息,如表6-11所示:

 

表6-11

消息

按键或者代码

WM_KEYDOWN

虚拟键码VK_SHIFT (0x10)

WM_KEYDOWN

「A」的虚拟键码(0x41)

WM_CHAR

「A」的字符代码(0x41)

WM_KEYUP

「A」的虚拟键码(0x41)

WM_KEYUP

虚拟键码VK_SHIFT(0x10)

 

Shift键本身不产生字符消息。

如果使用者按住A键,以使自动重复产生一系列的按键,那么对每条WM_KEYDOWN消息,都会得到一条字符消息,如表6-12所示:

 

表6-12

消息

按键或者代码

WM_KEYDOWN

「A」的虚拟键码(0x41)

WM_CHAR

「a」的字符代码(0x61)

WM_KEYDOWN

「A」的虚拟键码(0x41)

WM_CHAR

「a」的字符代码(0x61)

WM_KEYDOWN

「A」的虚拟键码(0x41)

WM_CHAR

「a」的字符代码(0x61)

WM_KEYDOWN

「A」的虚拟键码(0x41)

WM_CHAR

「a」的字符代码(0x61)

WM_KEYUP

「A」的虚拟键码(0x41)

 

如果某些WM_KEYDOWN消息的重复计数大于1,那么相应的WM_CHAR消息将具有同样的重复计数。

组合使用Ctrl键与字母键会产生从0x01(Ctrl-A)到0x1A(Ctrl-Z)的ASCII控制代码,其中的某些控制代码也可以由表6-13列出的键产生:

 

表6-13

按键

字符代码

产生方法

ANSI C控制字符

Backspace

0x08

Ctrl-H

\b

Tab

0x09

Ctrl-I

\t

Ctrl-Enter

0x0A

Ctrl-J

\n

Enter

0x0D

Ctrl-M

\r

Esc

0x1B

Ctrl-[

 

 

最右列给出了在ANSI C中定义的控制字符,它们用于描述这些键的字符代码。

 

我们一般可以这样处理WM_CARH消息:

case WM_CHAR:

{

    switch (wParam)

    {

        case 0x08:

            // Process a backspace.

            break;

 

        case 0x0A:

            // Process a linefeed.

            break;

 

        case 0x1B:

            // Process an escape.

            break;

 

        case 0x09:

            // Process a tab.

            break;

 

        case 0x0D:

            // Process a carriage return.

            break;

 

        default:

            // Process displayable characters.

            break;

    }

}

 

我们可以在WM_CHAR里面判断当前是否有指定的键被按下:

 

BOOL bIsCtrl = (::GetAsyncKeyState(VK_CONTROL) & 0x8000); MFC源码 afxcolordialog.cpp 460行)

BOOL bIsCtrl = (::GetKeyState(VK_CONTROL) & 0x8000);

 

下面我解释一下键盘消息的lParam参数,这个参数在MSDN上面都可以查到,只是英文,我这里作一些简单的说明:(以WM_KEYDOWN为例)

WPARAM:虚拟键值,VT_*等值。

LPARAM:根据其不同的位数表示的含义不同可以分以下几部分:

(1) 重复计数位(0 - 15 位):表示消息按键数据。一般情况下为1,当键一直按下,窗口过程就会连续收到W_KEYDOWN消息,但有可能窗口过程来不及处理这些按键消息,那么Windows就会把几个按键消息组合成一个,并增加重复计数。比如你处理WM_KEYDOWNSleep(200),那么得到的这个数字就可能大于1,一般可以这样来得到这个计数:

DWORD count = (((DWORD)lParam) & 0x0000FFFF);

(2) OEM扫描码(16~23位):OEM扫描码是键盘发送的码值,由于此域是设备相关的,因而此值往往被忽略。

(3) 扩展键标志(24位):扩展键标志在有Alt键(或Ctrl键)按下时为1,否则为0。

(4) 保留位(25~28位):保留位是系统缺省保留的,一般不用。

(5) 关联码(29位):关联码用来记录某键与Alt键的组合状态,若按下Alt,当WM_SYSKEYDOWN消息送到某个激活的窗口时,其值为1,否则为0。

(6) 键的先前状态(位30):键的先前状态用于记录先前某键的状态,对于WM_SYSKEYUP消息,其值始终为1。

(7) 转换状态(31位):转换状态的消息是始终按着某键所产生的消息,若某键原来是按下的,则其先前状态为0。转换状态指示键被按下还是被松开。当键被按下时,对应于者WM_SYSKEYDOWN消息,其值始终为0,当键被松开时,其转换状态为1,对应于WM_SYSKEYUP消息,其值始终为1。

 

5,死字符消息

Windows程序经常忽略WM_DEADCHAR和WM_SYSDEADCHAR消息,但您应该明确地知道死字符是什么,以及它们工作的方式。

在某些非U.S.英语键盘上,有些键用于给字母加上音调。因为它们本身不产生字符,所以称之为「死键」。例如,使用德语键盘时,对于U.S.键盘上的+/=键,德语键盘的对应位置就是一个死键,未按下Shift键时它用于标识锐音,按下Shift键时则用于标识抑音。

当使用者按下这个死键时,窗口消息处理程序接收到一个wParam等于音调本身的ASCII或者Unicode代码的WM_DEADCHAR消息。当使用者再按下可以带有此音调的字母键(例如A键)时,窗口消息处理程序会接收到WM_CHAR消息,其中wParam等于带有音调的字母「a」的ANSI代码。

因此,使用者程序不需要处理WM_DEADCHAR消息,原因是WM_CHAR消息已含有程序所需要的所有信息。Windows的做法甚至还设计了内部错误处理。如果在死键之后跟有不能带此音调符号的字母(例如「s」),那么窗口消息处理程序将在一行接收到两条WM_CHAR消息-前一个消息的wParam等于音调符号本身的ASCII代码(与传递到WM_DEADCHAR消息的wParam值相同),第二个消息的wParam等于字母s的ASCII代码。

当然,要感受这种做法的运作方式,最好的方法就是实际操作。您必须加载使用死键的外语键盘,例如前面讲过的德语键盘。您可以这样设定:在「控制台」中选择「键盘」,然后选择「语系」页面标签。然后您需要一个应用程序,该程序可以显示它接收的每一个键盘消息的详细信息。下面的KEYVIEW1就是这样的程序。



符号常量 十六进制值 指定的鼠标或键盘按键
  VK_LBUTTON 01 鼠标左键
  VK_RBUTTON 02 鼠标右键
  VK_CANCEL 03 Control-break 过程
  VK_MBUTTON 04 鼠标中键
  VK_BACK 08 BACKSPACE 键
  VK_TAB 09 TAB 键
  VK_CLEAR 0C CLEAR 键
  VK_RETURN 0D ENTER 键
  VK_SHIFT 10 SHIFT 键
  VK_CONTROL 11 CTRL 键
  VK_MENU 12 ALT 键
  VK_PAUSE 13 PAUSE 键
  VK_CAPITAL 14 CAPS LOCK 键
  VK_ESCAPE 1B ESC 键
  VK_SPACE 20 SPACEBAR
  VK_PRIOR 21 PAGE UP 键
  VK_NEXT 22 PAGE DOWN 键
  VK_END 23 END 键
  VK_HOME 24 HOME 键
  VK_LEFT 25 LEFT ARROW 键
  VK_UP 26 UP ARROW 键
  VK_RIGHT 27 RIGHT ARROW 键
  VK_DOWN 28 DOWN ARROW 键
  VK_SELECT 29 SELECT 键
  VK_EXECUTE 2B EXECUTE 键
  VK_SNAPSHOT 2C PRINT SCREEN键(用于Windows 3.0及以后版本)
  VK_INSERT 2D INS 键
  VK_DELETE 2E DEL 键
  VK_HELP 2F HELP 键
  ///////////////////////////////////////////////////
  对于字母键和非小键盘上的数字键,直接在单引号中加入该键就行.
  比如:a键:'A'
   1键:'1'
  //////////////////////////////////////////////

  VK_LWIN 5B Left Windows 键 (Microsoft自然键盘)
  VK_RWIN 5C Right Windows 键 (Microsoft自然键盘)
  VK_APPS 5D Applications 键 (Microsoft自然键盘)
  VK_NUMPAD0 60 数字小键盘上的 0 键
  VK_NUMPAD1 61 数字小键盘上的 1 键
  VK_NUMPAD2 62 数字小键盘上的 2 键
  VK_NUMPAD3 63 数字小键盘上的 3 键
  VK_NUMPAD4 64 数字小键盘上的 4 键

       VK_NUMPAD5 65 数字小键盘上的 5 键
  VK_NUMPAD6 66 数字小键盘上的 6 键
  VK_NUMPAD7 67 数字小键盘上的 7 键
  VK_NUMPAD8 68 数字小键盘上的 8 键
  VK_NUMPAD9 69 数字小键盘上的 9 键
  VK_MULTIPLY 6A Multiply 键
  VK_ADD 6B Add 键
  VK_SEPARATOR 6C Separator 键
  VK_SUBTRACT 6D Subtract 键
  VK_DECIMAL 6E Decimal 键
  VK_DIVIDE 6F Divide 键
  VK_F1 70 F1 键
  VK_F2 71 F2 键
  VK_F3 72 F3 键
  VK_F4 73 F4 键
  VK_F5 74 F5 键
  VK_F6 75 F6 键
  VK_F7 76 F7 键
  VK_F8 77 F8 键
  VK_F9 78 F9 键
  VK_F10 79 F10 键
  VK_F11 7A F11 键
  VK_F12 7B F12 键
  VK_F13 7C F13 键
  VK_F14 7D F14 键
  VK_F15 7E F15 键
  VK_F16 7F F16 键
  VK_F17 80H F17 键
  VK_F18 81H F18 键
  VK_F19 82H F19 键
  VK_F20 83H F20 键
  VK_F21 84H F21 键
  VK_F22 85H F22 键
  VK_F23 86H F23 键
  VK_F24 87H F24 键
  VK_NUMLOCK 90 NUM LOCK 键
  VK_SCROLL 91 SCROLL LOCK 键
  VK_ATTN F6 Attn 键
  VK_CRSEL F7 CrSel 键
  VK_EXSEL F8 ExSel 键
  VK_EREOF F9 Erase EOF 键

       VK_PLAY FA Play 键
  VK_ZOOM FB Zoom 键
  VK_OEM_CLEAR FE Clear 键
  
  举例:
  
  (一)响应单独的按键:
  先添加PreTranslateMessage()(响应WM_CHAR)也是同样的效果,因为本例只捕捉键盘)
  BOOL CMydilog::PreTranslateMessage(MSG* pMsg) 
  {

   // TODO: Add your specialized code here and/or call the base class
   if (pMsg->message == WM_KEYDOWN)
   {
   if(pMsg->wParam=='M')//直接用上面的虚码代替就可以响应所指键
   MessageBox("hello");//如果按下M键弹出消息.比如想当按下小键盘1时
   //弹出就用VK_NUMPAD1代替'M'
  }
   return CDialog::PreTranslateMessage(pMsg);
  }

  (二)组合键的用法:(本例响应Ctrl+X键)
  BOOL CMydilog::PreTranslateMessage(MSG* pMsg) 
  {
   // TODO: Add your specialized code here and/or call the base class
   
   if (pMsg->message == WM_KEYDOWN)
   {
   switch (pMsg->wParam)
   { 
   case VK_ESCAPE:
   SetFocus ();
   return TRUE;
   case 'X':
   if(::GetKeyState(VK_CONTROL) < 0)//如果是Shift+X这里就
   //改成VK_SHIFT
   MessageBox("hello");
   return TRUE;
   
   }
   }
   return CDialog::PreTranslateMessage(pMsg);
  }


SetCapture 函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获。一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。同一时刻只能有一个窗口捕获鼠标。如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向指定的窗口。

LRESULT CALLBACK TestProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_LBUTTONDOWN: //左键按下
        {
            SetCapture(hwnd);
        }
        break;
    case WM_MOUSEMOVE: //鼠标按下移动
        {
                if(GetCapture() ==  hwnd)
                {
                   //鼠标按下并移动...
                }
        }
        break;
    case WM_LBUTTONUP: //左键弹起
        {
            if(GetCapture()  ==  hwnd)
                ReleaseCapture(); //当前线程中的窗口释放鼠标捕获,并恢复通常的鼠标输入处理。
        }
        break;

        return 0;
    }

}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多