见招拆招Windows程序设计(五)
相关的例子:下载>>> 作者:Zoologist 于2008-1-17上传 

    在Microsoft Windows中,键盘和鼠标是两个标准的使用者输入来源,在一些连贯操作中常产生互补作用。当然,鼠标在今天的应用程序中比十年前使用得更为广泛。甚至在一些应用程序中,我们更习惯于使用鼠标,例如在游戏、画图程序、音乐程序以及Web浏览器等程序中就是这样。我曾经在PS上玩即时战略游戏《红色警戒》,还有在PSP上玩FPS的《荣誉勋章》,很快就发现这是一场噩梦。我们可以不使用鼠标,但绝对不能从一般的PC中把键盘拆掉。你完全可以在没有鼠标的情况下操作标准的Windows程序,这也是操作系统设计上注意到的细节。

    相对于个人计算机的其它组件,键盘有非常久远的历史,它起源于1874年的第一台Remington打字机。早期的计算机程序员用键盘在Hollerith卡片上打孔,后来在终端机上用键盘直接与大型主机沟通。PC上的键盘在某些方面进行了扩充,加上了功能键、光标移动键和单独的数字键盘,但它们的输入原理基本相同。
有兴趣的读者不妨查阅一下现在的标准键盘“QWERT”是怎么来的,不合理的键位设计居然是特意为了“降低打字速度”……

键盘基础

    您大概已经猜到Windows程序是如何获得键盘输入的:键盘输入以消息的形式传递给程序的窗口消息处理程序。实际上,第一次学习消息时,键盘事件就是一个消息如何将不同型态信息传递给应用程序的显例。

    Windows用八种不同的消息来传递不同的键盘事件。这好像太多了,但是(就像我们所看到的一样)程序可以忽略其中至少一半的消息而不会有任何问题。并且,在大多数情况下,这些消息中包含的键盘信息会多于程序所需要的。处理键盘的部分工作就是识别出哪些消息是重要的,哪些是不重要的。

忽略键盘

    虽然键盘是Windows程序中使用者输入的主要来源,但是程序不必对它接收的所有消息都作出响应。Windows本身也能处理许多键盘功能。

    例如,您可以忽略那些属于系统功能的按键,它们通常用到Alt键。程序不必监视这些按键,因为Windows会将按键的作用通知程序(当然,如果程序想这么做,它也能监视这些按键)。虽然呼叫程序菜单的按键将通过窗口的窗口消息处理程序,但通常内定的处理方式是将按键传递给DefWindowProc。最终,窗口消息处理程序将获得一个消息,表示一个菜单项被选择了。通常,这是所有窗口消息处理程序需要知道的(在后面一期介绍菜单)。

    有些Windows程序使用「键盘快捷键」来启动通用菜单项。快捷键通常是功能键或字母同Ctrl键的组合(例如,Ctrl-S用于保存文件)。这些键盘快捷键与程序菜单一起在程序的资源描述文件中定义。Windows将这些键盘快捷键转换为菜单命令消息,您不必自己去进行转换。

    对话框也有键盘接口,但是当对话框处于活动状态时,应用程序通常不必监视键盘。键盘接口由Windows处理,Windows把关于按键作用的消息发送给程序。对话框可以包含用于输入文字的编辑控件。它们一般是小方框,使用者可以在框中键入字符串。Windows处理所有编辑控件逻辑,并在输入完毕后,将编辑控件的最终内容传送给程序。

    编辑控件不必局限于单独一行,而且也不限于只在对话框中。一个在程序主窗口内的多行编辑控件就能够作为一个简单的文字编辑器了(后面我们会设计一个POPPAD程序)。Windows甚至有一个Rich Text文字编辑控件,允许您编辑和显示格式化的文字(Rich Edit Controls)。

    您将会发现,在开发Windows程序时,可以使用处理键盘和鼠标输入的子窗口控件来将较高层的信息传递回父窗口。只要这样的控件用得够多,您就不会因处理键盘消息而烦恼了。

谁获得了焦点

   与所有的个人计算机硬件一样,键盘必须由在Windows下执行的所有应用程序共享。有些应用程序可能有多个窗口,键盘必须由该应用程序内的所有窗口共享。

    回想一下,程序用来从消息队列中检索消息的MSG结构包括hwnd字段。此字段指出接收消息的窗口控件码。消息循环中的DispatchMessage函数向窗口消息处理程序发送该消息,此窗口消息处理程序与需要消息的窗口相联系。在按下键盘上的键时,只有一个窗口消息处理程序接收键盘消息,并且此消息包括接收消息的窗口控件码。

    接收特定键盘事件的窗口具有输入焦点。输入焦点的概念与活动窗口的概念很相近。有输入焦点的窗口是活动窗口或活动窗口的衍生窗口(活动窗口的子窗口,或者活动窗口子窗口的子窗口等等)。

    通常很容易辨别活动窗口。它通常是顶层窗口-也就是说,它的父窗口句柄是NULL。如果活动窗口有标题列,Windows将突出显示标题列。如果活动窗口具有对话框架(对话框中很常见的格式)而不是标题列,Windows将突出显示框架。如果活动窗口目前是最小化的,Windows将在工作列中突出显示该项,其显示就像一个按下的按钮。

    如果活动窗口有子窗口,那么有输入焦点的窗口既可以是活动窗口也可以是其子窗口。最常见的子窗口有类似以下控件:出现在对话框中的下压按钮、单选钮、复选框、滚动条、编辑方块和清单方块。子窗口不能自己成为活动窗口。只有当它是活动窗口的衍生窗口时,子窗口才能有输入焦点。子窗口控件一般通过显示一个闪烁的插入符号或虚线来表示它具有输入焦点。

    有时输入焦点不在任何窗口中。这种情况发生在所有程序都是最小化的时候。这时,Windows将继续向活动窗口发送键盘消息,但是这些消息与发送给非最小化的活动窗口的键盘消息有不同的形式。

    窗口消息处理程序通过拦截WM_SETFOCUS和WM_KILLFOCUS消息来判定它的窗口何时拥有输入焦点。WM_SETFOCUS指示窗口正在得到输入焦点,WM_KILLFOCUS表示窗口正在失去输入焦点。我将在本期的后面详细说明这些消息。

队列和同步

    当使用者按下并释放键盘上的键时,Windows和键盘驱动程序将硬件扫描码转换为格式消息。然而,这些消息并不保存在消息队列中。实际上,Windows在所谓的「系统消息队列」中保存这些消息。系统消息队列是独立的消息队列,它由Windows维护,用于初步保存使用者从键盘和鼠标输入的信息。只有当Windows应用程序处理完前一个使用者输入消息时,Windows才会从系统消息队列中取出下一个消息,并将其放入应用程序的消息队列中。

    此过程分为两步:首先在系统消息队列中保存消息,然后将它们放入应用程序的消息队列,其原因是需要同步。就像我们刚才所学的,假定接收键盘输入的窗口就是有输入焦点的窗口。使用者的输入速度可能比应用程序处理按键的速度快,并且特定的按键可能会使焦点从一个窗口切换到另一个窗口,后来的按键就输入到了另一个窗口。但如果后来的按键已经记下了目标窗口的地址,并放入了应用程序消息队列,那么后来的按键就不能输入到另一个窗口。

按键和字符

    应用程序从Windows接收的关于键盘事件的消息可以分为按键和字符两类,这与您看待键盘的两种方式一致。

    首先,您可以将键盘看作是键的集合。键盘只有唯一的A键,按下该键是一次按键,释放该键也是一次按键。但是键盘也是能产生可显示字符或控制字符的输入设备。根据Ctrl、 Shift和Caps Lock键的状态,A键能产生几个字符。通常情况下,此字符为小写a。如果按下Shift键或者打开了Caps Lock,则该字符就变成大写A。如果按下了Ctrl,则该字符为Ctrl-A(它在ASCII中有意义,但在Windows中可能是某事件的键盘快捷键)。在一些键盘上,A按键之前可能有「死字符键(dead-character key)」或者Shift、Ctrl或者Alt的不同组合,这些组合可以产生带有音调标记的小写或者大写,例如,àá等等......

    对产生可显示字符的按键组合,Windows不仅给程序发送按键消息,而且还发送字符消息。有些键不产生字符,这些键包括shift键、功能键、光标移动键和特殊字符键如Insert和Delete。对于这些键,Windows只产生按键消息。

按键消息

    当您按下一个键时,Windows把WM_KEYDOWN或者WM_SYSKEYDOWN消息放入有输入焦点的窗口的消息队列;当您释放一个键时,Windows把WM_KEYUP或者WM_SYSKEYUP消息放入消息队列中。

表6-1

 

键按下

键释放

非系统键 WM_KEYDOWN WM_KEYUP
系统键 WM_SYSKEYDOWN WM_SYSKEYUP

    通常「down(按下)」和「up(放开)」消息是成对出现的。不过,如果您按住一个键使得自动重复功能生效,那么当该键最后被释放时,Windows会给窗口消息处理程序发送一系列WM_KEYDOWN(或者WM_SYSKEYDOWN)消息和一个WM_KEYUP(或者WM_SYSKEYUP)消息。像所有放入队列的消息一样,按键消息也有时间信息。通过呼叫GetMessageTime,您可以获得按下或者释放键的相对时间。

系统按键与非系统按键

    WM_SYSKEYDOWN和WM_SYSKEYUP中的「SYS」代表「系统」,它表示该按键对Windows比对Windows应用程序更加重要。WM_SYSKEYDOWN和WM_SYSKEYUP消息经常由与Alt相组合的按键产生,这些按键启动程序菜单或者系统菜单上的选项,或者用于切换活动窗口等系统功能(Alt-Tab或者Alt-Esc),也可以用作系统菜单快捷键(Alt键与一个功能键相结合,例如Alt-F4用于关闭应用程序)。程序通常忽略WM_SYSKEYUP和WM_SYSKEYDOWN消息,并将它们传送到DefWindowProc。由于Windows要处理所有Alt键的功能,所以您无需拦截这些消息。您的窗口消息处理程序将最后收到关于这些按键结果(如菜单选择)的其它消息。如果您想在自己的窗口消息处理程序中加上拦截系统按键的程序代码(如本期后面的KEYVIEW1和KEYVIEW2程序所作的那样),那么在处理这些消息之后再传送到DefWindowProc,Windows就仍然可以将它们用于通常的目的。

    但是,请再考虑一下,几乎所有会影响使用者程序窗口的消息都会先通过使用者窗口消息处理程序。只有使用者把消息传送到DefWindowProc,Windows才会对消息进行处理。例如,如果您将下面几行叙述:

case WM_SYSKEYDOWN:

case WM_SYSKEYUP:

caseWM_SYSCHAR:

return 0 ;

    加入到一个窗口消息处理程序中,那么当您的程序主窗口拥有输入焦点时,就可以有效地阻止所有Alt键操作(我将在本章的后面讨论WM_SYSCHAR),其中包括Alt-Tab、Alt-Esc以及菜单操作。虽然我怀疑您会这么做,但是,我相信您会感到窗口消息处理程序的强大功能。

    WM_KEYDOWN和WM_KEYUP消息通常是在按下或者释放不带Alt键的键时产生的,您的程序可以使用或者忽略这些消息,Windows本身并不处理这些消息。

    对所有四类按键消息,wParam是虚拟键代码,表示按下或释放的键,而lParam则包含属于按键的其它数据。

虚拟键码

    虚拟键码保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息的wParam参数中。此代码标识按下或释放的键。

    哈,又是「虚拟」,您喜欢这个词吗?虚拟指的是假定存在于思想中而不是现实世界中的一些事物,也只有熟练使用DOS汇编语言编写应用程序的程序写作者才有可能指出,为什么对Windows键盘处理如此基本的键码是虚拟的而不是真实的。

   对于早期的程序写作者来说,真实的键码由实际键盘硬件产生。在Windows文件中将这些键码称为「扫描码(scan codes)」。在IBM兼容机种上,扫描码16是Q键,17是W键,18是E、19是R,20是T,21是Y等等。这时您会发现,扫描码是依据键盘的实际布局的。Windows开发者认为这些代码过于与设备相关了,于是他们试图通过定义所谓的虚拟键码,以便经由与设备无关的方式处理键盘。其中一些虚拟键码不能在IBM兼容机种上产生,但可能会在其它制造商生产的键盘中找到,或者在未来的键盘上找到。

   您使用的大多数虚拟键码的名称在WINUSER.H表头文件中都定义为以VK_开头。表6-2列出了这些名称和数值(十进制和十六进制),以及与虚拟键相对应的IBM兼容机种键盘上的键。下表也标出了Windows执行时是否需要这些键。下表还按数字顺序列出了虚拟键码。

前四个虚拟键码中有三个指的是鼠标键:

表6-2

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

1 01 VK_LBUTTON   鼠标左键
2 02 VK_RBUTTON   鼠标右键
3 03 VK_CANCEL ˇ Ctrl-Break
4 04 VK_MBUTTON   鼠标中键

      您永远都不会从键盘消息中获得这些鼠标键代码。在后一期可以看到,我们能够从鼠标消息中获得它们。VK_CANCEL代码是一个虚拟键码,它包括同时按下两个键(Ctrl-Break)。Windows应用程序通常不使用此键。

    表6-3中的键--Backspace、Tab、Enter、Escape和Spacebar-通常用于Windows程序。不过,Windows一般用字符消息(而不是键盘消息)来处理这些键。

表6-3

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

8 08 VK_BACK ˇ Backspace
9 09 VK_TAB ˇ Tab
12 0C VK_CLEAR   Num Lock关闭时的数字键盘5
13 0D VK_RETURN ˇ Enter (或者另一个)
16 10 VK_SHIFT ˇ Shift (或者另一个)
17 11 VK_CONTROL ˇ Ctrl (或者另一个)
18 12 VK_MENU ˇ Alt (或者另一个)
19 13 VK_PAUSE   Pause
20 14 VK_CAPITAL ˇ Caps Lock
27 1B VK_ESCAPE ˇ Esc
32 20 VK_SPACE ˇ Spacebar

    另外,Windows程序通常不需要监视Shift、Ctrl或Alt键的状态。

    表6-4列出的前八个码可能是与VK_INSERT和VK_DELETE一起最常用的虚拟键码:

表6-4

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

33 21 VK_PRIOR ˇ Page Up
34 22 VK_NEXT ˇ Page Down
35 23 VK_END ˇ End
36 24 VK_HOME ˇ Home
37 25 VK_LEFT ˇ 左箭头
38 26 VK_UP ˇ 上箭头
39 27 VK_RIGHT ˇ 右箭头
40 28 VK_DOWN ˇ 下箭头
41 29 VK_SELECT    
42 2A VK_PRINT    
43 2B VK_EXECUTE    
44 2C VK_SNAPSHOT   Print Screen
45 2D VK_INSERT ˇ Insert
46 2E VK_DELETE ˇ Delete
47 2F VK_HELP    

     注意,许多名称(例如VK_PRIOR和VK_NEXT)都与键上的标志不同,而且也与滚动条中的标识符不统一。Print Screen键在平时都被Windows应用程序所忽略。Windows本身响应此键时会将视频显示的位图拷贝存放到剪贴板中。假使有键盘提供了VK_SELECT、VK_PRINT、VK_EXECUTE和VK_HELP,大概也没几个人看过那样的键盘。

     Windows也包括在主键盘上的字母和数字键的虚拟键码(数字键盘将单独处理)。

表6-5

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

48-57 30-39 ˇ 主键盘上的0到9
65-90 41-5A ˇ A到Z

    注意,数字和字母的虚拟键码是ASCII码。Windows程序几乎从不使用这些虚拟键码;实际上,程序使用的是ASCII码字符的字符消息。

    表6-6所示的代码是由Microsoft Natural Keyboard及其兼容键盘产生的:

表6-6

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

91 5B VK_LWIN   左Windows键
92 5C VK_RWIN   右Windows键
93 5D VK_APPS   Applications键

    Windows用VK_LWIN和VK_RWIN键打开「开始」菜单。这两个都可以用于登录或注销Windows(只在Microsoft Windows NT中有效),或者登录或注销网络(在Windows for Applications中)。应用程序能够通过显示辅助信息或者当成快捷方式键看待来处理application键。

    表6-7所示的代码用于数字键盘上的键(如果有的话):

表6-7

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

96-105 60-69 VK_NUMPAD0到VK_ NUMPAD9   NumLock打开时数字键盘上的0到9
106 6A VK_MULTIPLY   数字键盘上的*
107 6B VK_ADD   数字键盘上的+
108 6C VK_SEPARATOR    
109 6D VK_SUBTRACT   数字键盘上的-
110 6E VK_DECIMAL   数字键盘上的.
111 6F VK_DIVIDE   数字键盘上的/

    最后,虽然多数的键盘都有12个功能键,但Windows只需要10个,而位旗标却有24个。另外,程序通常用功能键作为键盘快捷键,这样,它们通常不处理表6-8所示的按键:

表6-8

十进制

十六进制

Windows.inc标识符

必需?

IBM兼容键盘

112-121 70-79 VK_F1到VK_F10 ˇ 功能键F1到F10
122-135 7A-87 VK_F11到VK_F24   功能键F11到F24
144 90 VK_NUMLOCK   Num Lock
145 91 VK_SCROLL   Scroll Lock

    另外,还定义了一些其它虚拟键码,但它们只用于非标准键盘上的键,或者通常在大型主机终端机上使用的键。

lParam信息

    在四个按键消息(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP)中,wParam消息参数含有上面所讨论的虚拟键码,而lParam消息参数则含有对了解按键非常有用的其它信息。lParam的32位分为6个字段,如图6-1所示。


 

重复计数

    重复计数是该消息所表示的按键次数,大多数情况下,重复计数设定为1。不过,如果按下一个键之后,您的窗口消息处理程序不够快,以致不能处理自动重复速率(您可以在「控制面板」的「键盘」中进行设定)下的按键消息,Windows就把几个WM_KEYDOWN或者WM_SYSKEYDOWN消息组合到单个消息中,并相应地增加重复计数。WM_KEYUP或WM_SYSKEYUP消息的重复计数总是为1。

    因为重复计数大于1指示按键速率大于您程序的处理能力,所以您也可能想在处理键盘消息时忽略重复计数。几乎每个人都有文字处理或执行电子表格时画面卷过头的经验,因为多余的按键堆满了键盘缓冲区,所以当程序用一些时间来处理每一次按键时,如果忽略您程序中的重复计数,就能够解决此问题。不过,有时可能也会用到重复计数,您应该尝试使用两种方法执行程序,并从中找出一种较好的方法。

OEM扫描码

    OEM扫描码是由硬件(键盘)产生的代码。这对中古时代的汇编程序写作者来说应该很熟悉,它是从PC相容机种的ROM BIOS服务中所获得的值(OEM指的是PC的原始设备制造商(Original Equipment Manufacturer)及其与「IBM标准」同步的内容)。在此我们不需要更多的信息。除非需要依赖实际键盘布局的样貌,不然Windows程序可以忽略掉几乎所有的OEM扫描码信。

扩充键旗标

    如果按键结果来自IBM增强键盘的附加键之一,那么扩充键旗标为1(IBM增强型键盘有101或102个键。功能键在键盘顶端,光标移动键从数字键盘中分离出来,但在数字键盘上还保留有光标移动键的功能)。对键盘右端的Alt和Ctrl键,以及不是数字键盘那部分的光标移动键(包括Insert和Delete键)、数字键盘上的斜线(/)和Enter键以及Num Lock键等,此旗标均被设定为1。Windows程序通常忽略扩充键旗标。

内容代码

右按键时,假如同时压下ALT键,那么内容代码为1。对WM_SYSKEYUP与WM_SYSKEYDOWN而言,此位总视为1;而对WM_SYSKEYUP与WM_KEYDOW消息而言,此位为0。除了两个之外:

键的先前状态

    如果在此之前键是释放的,则键的先前状态为0,否则为1。对WM_KEYUP或者WM_SYSKEYUP消息,它总是设定为1;但是对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以为0,也可以为1。如果为1,则表示该键是自动重复功能所产生的第二个或者后续消息。

转换状态

    如果键正被按下,则转换状态为0;如果键正被释放,则转换状态为1。对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此字段为0;对WM_KEYUP或者WM_SYSKEYUP消息,此字段为1。

位移状态

    在处理按键消息时,您可能需要知道是否按下了位移键(Shift、Ctrl和Alt)或开关键(Caps Lock、Num Lock和Scroll Lock)。通过调用GetKeyState函数,您就能获得此信息。例如:

iState = GetKeyState (VK_SHIFT) ;
        

   如果按下了Shift,则iState值为负(即设定了最高位置位)。如果Caps Lock键打开,则从

iState = GetKeyState (VK_CAPITAL) ;
        

   传回的值低位被设为1。此位与键盘上的小灯保持一致。

    通常,您在使用GetKeyState时,会带有虚拟键码VK_SHIFT、VK_CONTROL和VK_MENU(在说明Alt键时呼叫)。使用GetKeyState时,您也可以用下面的标识符来确定按下的Shift、Ctrl或Alt键是左边的还是右边的:VK_LSHIFT、VK_RSHIFT、VK_LCONTROL、VK_RCONTROL、VK_LMENU、VK_RMENU。这些标识符只用于GetKeyState和GetAsyncKeyState(下面将详细说明)。

    使用虚拟键码VK_LBUTTON、VK_RBUTTON和VK_MBUTTON,您也可以获得鼠标键的状态。不过,大多数需要监视鼠标键与按键相组合的Windows应用程序都使用其它方法来做到这一点-即在接收到鼠标消息时检查按键。实际上,位移状态信息包含在鼠标信息中。

    请注意GetKeyState的使用,它并非实时检查键盘状态,而只是检查直到目前为止正在处理的消息的键盘状态。多数情况下,这正符合您的要求。如果您需要确定使用者是否按下了Shift-Tab,请在处理Tab键的WM_KEYDOWN消息时呼叫GetKeyState,带有参数VK_SHIFT。如果GetKeyState传回的值为负,那么您就知道在按下Tab键之前按下了Shift键。并且,如果在您开始处理Tab键之前,已经释放了Shift键也没有关系。您知道,在按下Tab键的时候Shift键是按下的。

    GetKeyState不会让您获得独立于普通键盘消息的键盘信息。例如,您或许想暂停窗口消息处理程序的处理,直到您按下F1功能键为止:

while (GetKeyState (VK_F1) >= 0) ;    // WRONG !!!
        

    不要这么做!这将让程序当死(除非在执行此叙述之前早就从消息队列中接收到了F1的WM_KEYDOWN)。如果您确实需要知道目前某键的状态,那么您可以使用GetAsyncKeyState。

使用按键消息

    如果程序能够获得每个按键的信息,这当然很理想,但是大多数Windows程序忽略了几乎所有的按键,而只处理部分的按键消息。WM_SYSKEYDOWN和WM_SYSKEYUP消息是由Windows系统函数使用的,您不必为此费心,就算您要处理WM_KEYDOWN消息,通常也可以忽略WM_KEYUP消息。

    Windows程序通常为不产生字符的按键使用WM_KEYDOWN消息。虽然您可能认为借助按键消息和位移键状态信息能将按键消息转换为字符消息,但是不要这么做,因为您将遇到国际键盘间的差异所带来的问题。例如,如果您得到wParam等于0x33的WM_KEYDOWN消息,您就可以知道使用者按下了键3,到此为止一切正常。这时,如果用GetKeyState发现Shift键被按下,您就可能会认为使用者输入了#号,这可不一定。比如英国使用者就是在输入£。

    对于光标移动键、功能键、Insert和Delete键,WM_KEYDOWN消息是最有用的。不过, Insert、Delete和功能键经常作为菜单快捷键。因为Windows能把菜单快捷键翻译为菜单命令消息,所以您就不必自己来处理按键。

    在Windows之前的MS-DOS应用程序中大量使用功能键与Shift、Ctrl和Alt键的组合,同样地,您也可以在Windows程序中使用(实际上,Microsoft Word将大量的功能键用作命令快捷方式),但并不推荐这样做。如果您确实希望使用功能键,那么这些键应该是重复菜单命令。Windows的目标之一就是提供不需要记忆或者使用复杂命令流程的使用者接口。

    因此,可以归纳如下:多数情况下,您将只为光标移动键(有时也为Insert和Delete键)处理WM_KEYDOWN消息。在使用这些键的时候,您可以通过GetKeyState来检查Shift键和Ctrl键的状态。例如,Windows程序经常使用Shift与光标键的组合键来扩大文书处理里选中的范围。Ctrl键常用于修改光标键的意义。例如,Ctrl与右箭头键相组合可以表示光标右移一个字。

    决定您的程序中使用键盘方式的最佳方法之一是了解现有的Windows程序使用键盘的方式。如果您不喜欢那些定义,当然可以对其加以修改,但是这样做不利于其它人很快地学会使用您的程序。

为SYSMETS加上键盘处理功能

    在编写前面三个版本的SYSMETS程序时,我们还不了解键盘,只能使用滚动条和鼠标来卷动文字。现在我们知道了处理键盘消息的方法,那么不妨在程序中加入键盘接口。显然,这是处理光标移动键的工作。我们将大多数光标键(Home、End、Page Up、Page Down、Up Arrow和Down Arrow)用于垂直卷动,左箭头键和右箭头键用于不太重要的水平卷动。

    建立键盘接口的一种简单方法是在窗口消息处理程序中加入与WM_VSCROLL和WM_HSCROLL处理方式相仿,而且本质上相同的WM_KEYDOWN处理方法。不过这样子做是不聪明的,因为如果要修改滚动条的做法,就必须相对应地修改WM_KEYDOWN。

    为什么不简单地将每一种WM_KEYDOWN消息都翻译成同等效用的WM_VSCROLL或者WM_HSCROLL消息呢?通过向窗口消息处理程序发送假冒消息,我们可能会让WndProc认为它获得了卷动信息。

    在Windows中,这种方法是可行的。发送消息的函数叫做SendMessage,它所用的参数与传递到窗口消息处理程序的参数是相同的:

SendMessage (hwnd, message, wParam, lParam) ;

    在呼叫SendMessage时,Windows呼叫窗口句柄为hwnd的窗口消息处理程序,并把这四个参数传给它。当窗口消息处理程序完成消息处理之后,Windows把控制传回到SendMessage呼叫之后的下一道叙述。您发送消息过去的窗口消息处理程序,可以是同一个窗口消息处理程序、同一程序中的其它窗口消息处理程序或者其它应用程序,中的窗口消息处理程序。

下面说明在SYSMETS程序中使用SendMessage处理WM_KEYDOWN代码的方法:

caseWM_KEYDOWN:
        
    switch (wParam)
        
   {
        
    case   VK_HOME:
        
            SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0) ;
        
            break ;
        
    case   VK_END:
        
            SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0) ;
        
            break ;
        

    case   VK_PRIOR:
        
            SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0) ;
        
            break ;
        

    至此,您已经有了大概观念了吧。我们的目标是为滚动条添加键盘接口,并且也正在这么做。通过把卷动消息发送到窗口消息处理程序,我们实作了用光标移动键进行卷动列的功能。现在您知道在SYSMETS3中为WM_VSCROLL消息加上SB_TOP和SB_BOTTOM处理码的原因了吧。在那里并没有用到它,但是现在处理Home和End键时就有用了。如程序6-1所示的SYSENTS4就加上了这些变化。

程序6-1 SYSMETS4

        
SYSMETS4.ASM
        
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
Include winmm.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib winmm.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
	
	NUMLINES		equ	(sysmetricsEnd - sysmetrics) / 4 /3

.DATA
	szAppName   db "SysMets4",0
	
	szShow01		db	"SM_CXSCREEN",0
	szShow02		db	"Screen width in pixels",0	
	
	szShow03		db	"SM_CYSCREEN",0	
	szShow04		db	"Screen height in pixels",0	
	
	szShow05		db	"SM_CXVSCROLL",0	
	szShow06		db	"Vertical scroll width",0	

	szShow07		db	"SM_CYHSCROLL",0	
	szShow08		db	"Horizontal scroll height",0	
	
	szShow09		db	"SM_CYCAPTION",0		
	szShow10		db	"Caption bar height",0	

	szShow11		db	"SM_CXBORDER",0		
	szShow12		db	"Window border width",0	   
	
	szShow13		db	"SM_CYBORDER",0		
	szShow14		db	"Window border height",0	

	szShow15		db	"SM_CXFIXEDFRAME",0		
	szShow16		db	"Dialog window frame width",0	
	
	szShow17		db	"SM_CYFIXEDFRAME",0		
	szShow18		db	"Dialog window frame height",0	
	
	szShow19		db	"SM_CYVTHUMB",0		
	szShow20		db	"Vertical scroll thumb height",0	
	
	szShow21		db	"SM_CXHTHUMB",0		
	szShow22		db	"Horizontal scroll thumb width",0	
	szShow23		db	"SM_CXICON",0		
	szShow24		db	"Icon width",0	
	szShow25		db	"SM_CYICON",0		
	szShow26		db	"Icon height",0	

	szShow27		db	"SM_CXCURSOR",0		
	szShow28		db	"Cursor width",0		

	szShow29		db	"SM_CYCURSOR",0		
	szShow30		db	"Cursor height",0		
	
	szShow31		db	"SM_CYMENU",0		
	szShow32		db	"Menu bar height",0		
	
	szShow33		db	"SM_CXFULLSCREEN",0		
	szShow34		db	"Full screen client area width",0		
	
	szShow35		db	"SM_CYFULLSCREEN",0		
	szShow36		db	"Full screen client area height",0		
	
	szShow37		db	"SM_CYKANJIWINDOW",0		
	szShow38		db	"Kanji window height",0		
	
	szShow39		db	"SM_MOUSEPRESENT",0		
	szShow40		db	"Mouse present flag",0		
	
	szShow41		db	"SM_CYVSCROLL",0		
	szShow42		db	"Vertical scroll arrow height",0		

	szShow43		db	"SM_CXHSCROLL",0		
	szShow44		db	"Horizontal scroll arrow width",0		
	
	szShow45		db	"SM_DEBUG",0		
	szShow46		db	"Debug version flag",0		
	
	szShow47		db	"SM_SWAPBUTTON",0		
	szShow48		db	"Mouse buttons swapped flag",0		
	
	szShow49		db	"SM_CXMIN",0		
	szShow50		db	"Minimum window width",0		
	
	szShow51		db	"SM_CYMIN",0		
	szShow52		db	"Minimum window height",0		
	
	szShow53		db	"SM_CXSIZE",0		
	szShow54		db	"Min/Max/Close button width",0		
	
	szShow55		db	"SM_CYSIZE",0		
	szShow56		db	"Min/Max/Close button height",0	                           
	
	szShow57		db	"SM_CXSIZEFRAME",0		
	szShow58		db	"Window sizing frame width",0	                           
	
	szShow59		db	"SM_CYSIZEFRAME",0		
	szShow60		db	"Window sizing frame height",0	                           
	
	szShow61		db	"SM_CXMINTRACK",0		
	szShow62		db	"Minimum window tracking width",0	                           
	
	szShow63		db	"SM_CYMINTRACK",0		
	szShow64		db	"Minimum window tracking height",0	                           
	
	szShow65		db	"SM_CXDOUBLECLK",0		
	szShow66		db	"Double click x tolerance",0	                           
	
	szShow67		db	"SM_CYDOUBLECLK",0		
	szShow68		db	"Double click y tolerance",0	                           

	szShow69		db	"SM_CXICONSPACING",0		
	szShow70		db	"Horizontal icon spacing",0	                           
	
	szShow71		db	"SM_CYICONSPACING",0		
	szShow72		db	"Vertical icon spacing",0	                           
	
	szShow73		db	"SM_MENUDROPALIGNMENT",0		
	szShow74		db	"Left or right menu drop",0	                           
	
	szShow75		db	"SM_PENWINDOWS",0		
	szShow76		db	"Pen extensions installed",0	                           
	
	szShow77		db	"SM_DBCSENABLED",0		
	szShow78		db	"Double-Byte Char Set enabled",0	                           
                                  
	szShow79		db	"SM_CMOUSEBUTTONS",0		
	szShow80		db	"Number of mouse buttons",0	                           
	
	szShow81		db	"SM_SECURE",0		
	szShow82		db	"Security present flag",0	                           
	
	szShow83		db	"SM_CXEDGE",0		
	szShow84		db	"3-D border width",0	                           
	
	szShow85		db	"SM_CYEDGE",0		
	szShow86		db	"3-D border height",0	                           
	
	szShow87		db	"SM_CXMINSPACING",0		
	szShow88		db	"Minimized window spacing width",0	
	
	szShow89		db	"SM_CYMINSPACING",0		
	szShow90		db	"Minimized window spacing height",0	                           
	
	szShow91		db	"SM_CXSMICON",0		
	szShow92		db	"Small icon width",0	                           
	
	szShow93		db	"SM_CYSMICON",0		
	szShow94		db	"Small icon height",0	                           
	
	szShow95		db	"SM_CYSMCAPTION",0		
	szShow96		db	"Small caption height",0	                           

	szShow97		db	"SM_CXSMSIZE",0		
	szShow98		db	"Small caption button width",0	
	
	szShow99		db	"SM_CYSMSIZE",0		
	szShow100	db	"Small caption button height",0	                           

	szShow101	db	"SM_CXMENUSIZE",0		
	szShow102	db	"Menu bar button width",0	                           
	
	szShow103	db	"SM_CYMENUSIZE",0		
	szShow104	db	"Menu bar button height",0	                           
	
	szShow105		db	"SM_ARRANGE",0		
	szShow106	db	"How minimized windows arranged",0	                           
	
	szShow107	db	"SM_CXMINIMIZED",0		
	szShow108	db	"Minimized window width",0		

	szShow109	db	"SM_CYMINIMIZED",0		
	szShow110	db	"Minimized window height",0	                           
	
	szShow111	db	"SM_CXMAXTRACK",0		
	szShow112		db	"Maximum draggable width",0	                           
	
	szShow113		db	"SM_CYMAXTRACK",0		
	szShow114		db	"Maximum draggable height",0	

	szShow115	db	"SM_CXMAXIMIZED",0		
	szShow116		db	"Width of maximized window",0	                           
	
	szShow117	db	"SM_CYMAXIMIZED",0		
	szShow118	db	"Height of maximized window",0	                           
	
	szShow119	db	"SM_NETWORK",0		
	szShow120	db	"Network present flag",0	

	szShow121	db	"SM_CLEANBOOT",0		
	szShow122	db	"How system was booted",0	                           
	
	szShow123	db	"SM_CXDRAG",0		
	szShow124	db	"Avoid drag x tolerance",0	                           
	
	szShow125	db	"SM_CYDRAG",0		
	szShow126	db	"Avoid drag y tolerance",0		
	
	szShow127	db	"SM_SHOWSOUNDS",0		
	szShow128	db	"Present sounds visually",0	                           
	
	szShow129	db	"SM_CXMENUCHECK",0		
	szShow130	db	"Menu check-mark width",0	                           
	
	szShow131	db	"SM_CYMENUCHECK",0		
	szShow132	db	"Menu check-mark height",0	
	
	szShow133	db	"SM_SLOWMACHINE",0		
	szShow134	db	"Slow processor flag",0	                           

	szShow135	db	"SM_MIDEASTENABLED",0		
	szShow136	db	"Hebrew and Arabic enabled flag",0	                           
	
	szShow137	db	"SM_MOUSEWHEELPRESENT",0		
	szShow138	db	"Mouse wheel present flag",0		
	
	szShow139	db	"SM_XVIRTUALSCREEN",0		
	szShow140	db	"Virtual screen x origin",0	                           
	
	szShow141	db	"SM_YVIRTUALSCREEN",0		
	szShow142	db	"Virtual screen y origin",0	
	
	szShow143		db	"SM_CXVIRTUALSCREEN",0		
	szShow144	db	"Virtual screen width",0	                           
	
	szShow145	db	"SM_CYVIRTUALSCREEN",0		
	szShow146	db	"Virtual screen height",0		
	
	szShow147	db	"SM_CMONITORS",0		
	szShow148	db	"Number of monitors",0	                           
	
	szShow149	db	"SM_SAMEDISPLAYFORMAT",0		
	szShow150	db	"Same color format flag",0	

sysmetrics	dd		SM_CXSCREEN, offset szShow01, offset szShow02
				dd		SM_CYSCREEN, szShow03,offset szShow04
				dd		SM_CXVSCROLL,offset szShow05,offset szShow06
				dd		SM_CYHSCROLL,offset szShow07,offset szShow08		
				dd		SM_CYCAPTION,offset szShow09,offset szShow10				
				dd		SM_CXBORDER,offset szShow11,offset szShow12				
				dd		SM_CYBORDER,offset szShow13,offset szShow14				
        		dd		SM_CXFIXEDFRAME,offset szShow15,offset szShow16				
				dd		SM_CYVTHUMB,offset szShow17,offset szShow18				
				dd		SM_CYFIXEDFRAME,offset szShow19,offset szShow20	        
        		dd		SM_CXHTHUMB,offset szShow21,offset szShow22	
        		dd		SM_CXICON,offset szShow23,offset szShow24
        		dd		SM_CYICON,offset szShow25,offset szShow26        		
        		dd		SM_CXCURSOR,offset szShow27,offset szShow28	
        		dd		SM_CYCURSOR,offset szShow29,offset szShow30
        		dd		SM_CYMENU,offset szShow31,offset szShow32        		
        		dd		SM_CXFULLSCREEN,offset szShow33,offset szShow34 
       		dd		SM_CYFULLSCREEN,offset szShow35,offset szShow36 
        		dd		SM_CYKANJIWINDOW,offset szShow37,offset szShow38 
       		dd		SM_MOUSEPRESENT,offset szShow39,offset szShow40 
        		dd		SM_CYVSCROLL,offset szShow41,offset szShow42 
       		dd		SM_CXHSCROLL,offset szShow43,offset szShow44 
        		dd		SM_DEBUG,offset szShow45,offset szShow46 
       		dd		SM_SWAPBUTTON,offset szShow47,offset szShow48 
        		dd		SM_CXMIN,offset szShow49,offset szShow50 
       		dd		SM_CYMIN,offset szShow51,offset szShow52 
        		dd		SM_CXSIZE,offset szShow53,offset szShow54 
       		dd		SM_CYSIZE,offset szShow55,offset szShow56        		
       		dd		SM_CXSIZEFRAME,offset szShow57,offset szShow58 
       		dd		SM_CYSIZEFRAME,offset szShow59,offset szShow60        		
       		dd		SM_CXMINTRACK,offset szShow61,offset szShow62        		
       		dd		SM_CYMINTRACK,offset szShow63,offset szShow64 
       		dd		SM_CXDOUBLECLK,offset szShow65,offset szShow66        		
        		dd		SM_CYDOUBLECLK,offset szShow67,offset szShow68        		
       		dd		SM_CXICONSPACING,offset szShow69,offset szShow70 
       		dd		SM_CYICONSPACING,offset szShow71,offset szShow72        		
        		dd		SM_MENUDROPALIGNMENT,offset szShow73,offset szShow74        		
       		dd		SM_PENWINDOWS,offset szShow75,offset szShow76 
       		dd		SM_DBCSENABLED,offset szShow77,offset szShow78        		
        		dd		SM_CMOUSEBUTTONS,offset szShow79,offset szShow80        		
       		dd		SM_SECURE,offset szShow81,offset szShow82 
       		dd		SM_CXEDGE,offset szShow83,offset szShow84        		
        		dd		SM_CYEDGE,offset szShow85,offset szShow86        		
       		dd		SM_CXMINSPACING,offset szShow87,offset szShow88 
       		dd		SM_CYMINSPACING,offset szShow89,offset szShow90        		
       		dd		SM_CXSMICON,offset szShow91,offset szShow92 
       		dd		SM_CYSMICON,offset szShow93,offset szShow94  
       		dd		SM_CYSMCAPTION,offset szShow95,offset szShow96 
       		dd		SM_CXSMSIZE,offset szShow97,offset szShow98        		
       		dd		SM_CYSMSIZE,offset szShow99,offset szShow100 
       		dd		SM_CXMENUSIZE,offset szShow101,offset szShow102  
       		dd		SM_CYMENUSIZE,offset szShow103,offset szShow104 
       		dd		SM_ARRANGE,offset szShow105,offset szShow106        		
       		dd		SM_CXMINIMIZED,offset szShow107,offset szShow108 
       		dd		SM_CYMINIMIZED,offset szShow109,offset szShow110  
       		dd		SM_CXMAXTRACK,offset szShow111,offset szShow112 
       		dd		SM_CYMAXTRACK,offset szShow113,offset szShow114        		
       		dd		SM_CXMAXIMIZED,offset szShow115,offset szShow116 
       		dd		SM_CYMAXIMIZED,offset szShow117,offset szShow118  
       		dd		SM_NETWORK,offset szShow119,offset szShow120 
       		dd		SM_CLEANBOOT,offset szShow121,offset szShow122  
       		dd		SM_CXDRAG,offset szShow123,offset szShow124 
       		dd		SM_CYDRAG,offset szShow125,offset szShow126  
       		dd		SM_SHOWSOUNDS,offset szShow127,offset szShow18 
       		dd		SM_CXMENUCHECK,offset szShow129,offset szShow130  
       		dd		SM_CYMENUCHECK,offset szShow131,offset szShow132 
       		dd		SM_SLOWMACHINE,offset szShow133,offset szShow134         		
       		dd		SM_MIDEASTENABLED,offset szShow135,offset szShow136 
       		dd		SM_MOUSEWHEELPRESENT,offset szShow137,offset szShow138 
       		dd		SM_XVIRTUALSCREEN,offset szShow139,offset szShow140 
       		dd		SM_YVIRTUALSCREEN,offset szShow141,offset szShow142 
       		dd		SM_CXVIRTUALSCREEN,offset szShow143,offset szShow144 
       		dd		SM_CYVIRTUALSCREEN,offset szShow145,offset szShow146 
       		dd		SM_CMONITORS,offset szShow147,offset szShow148 
       		dd		SM_SAMEDISPLAYFORMAT,offset szShow149,offset szShow150        		
sysmetricsEnd	label	DWORD

.DATA?
	hInstance	dd ?
	cxChar		dd	?
	cxCaps		dd	?
	cyChar		dd	?
	cxClient		dd ?
	cyClient		dd	?
	iMaxWidth	dd	?
.CODE
START:   ;从这里开始执行

	invoke   GetModuleHandle,NULL
	mov 		hInstance,eax
	invoke   WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
	invoke   ExitProcess,0

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
	LOCAL wndclass :WNDCLASSEX
	LOCAL msg  		:MSG
	local hWnd 		:HWND
	mov wndclass.cbSize,sizeof WNDCLASSEX	
	mov wndclass.style,CS_HREDRAW or CS_VREDRAW	
	mov wndclass.lpfnWndProc,offset WndProc

	mov wndclass.cbClsExtra,0
	mov wndclass.cbWndExtra,0
	
	push hInst
	pop wndclass.hInstance
	
	invoke LoadIcon,NULL,IDI_APPLICATION
	mov wndclass.hIcon,eax	
	
	invoke LoadCursor,NULL,IDC_ARROW
	mov wndclass.hCursor,eax	
	
	invoke GetStockObject,WHITE_BRUSH
	mov wndclass.hbrBackground,EAX
	
	mov wndclass.lpszMenuName,NULL
	mov wndclass.lpszClassName,offset szAppName

	mov wndclass.hIconSm,0
	
	invoke RegisterClassEx, ADDR wndclass
	.if (EAX==0)
		 invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr szAppName,MB_ICONERROR 		
		 ret
	.endif
        
	invoke CreateWindowEx,
					NULL,
					ADDR szAppName, 						;window class name
					CTXT("Get System Metrics No. 4"), ;window caption
					WS_OVERLAPPEDWINDOW,					;window style
					CW_USEDEFAULT,							;initial x position
					CW_USEDEFAULT,							;initial y position
					CW_USEDEFAULT, 						;initial x size
					CW_USEDEFAULT,							;initial y size
					NULL,										;parent window handle
					NULL,										;window menu handle
					hInstance,								;program instance handle
					NULL										;creation parameters
	mov hWnd,eax
	
	invoke ShowWindow,hWnd,iCmdShow
	invoke UpdateWindow,hWnd
	
	StartLoop:
		invoke GetMessage,ADDR msg,NULL,0,0
			cmp eax, 0
			je ExitLoop
				invoke TranslateMessage, ADDR msg
				invoke DispatchMessage,  ADDR msg
			jmp StartLoop
	ExitLoop:
	
	mov eax,msg.wParam
	ret
WinMain endp

WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD

	LOCAL hdc 				:HDC
	LOCAL i,x,y,iVertPos,iHorzPos, iPaintBeg, iPaintEnd :DWORD
	LOCAL ps  				:PAINTSTRUCT 
	LOCAL ssi 				:SCROLLINFO
	LOCAL szBuffer[10]	:BYTE
	LOCAL tm					:TEXTMETRIC
	LOCAL y_Pos,x_Caps 	:DWORD
	
	.if message==WM_CREATE
		
		invoke	GetDC,hwnd
		mov		hdc,eax
		
		invoke	GetTextMetrics,hdc,addr	tm
		mov		eax,tm.tmAveCharWidth		
		mov		cxChar,eax								;cxChar = tm.tmAveCharWidth 
		mov		eax,2										;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2
		test		DWORD ptr tm.tmPitchAndFamily,1
		jz		@f
		inc		eax
		@@:
		push		eax
		mov		eax,cxChar
		pop		ebx
		mul		ebx
		shr		eax,1
		mov		cxCaps,eax
 
				
		mov		eax,tm.tmHeight						;cyChar = tm.tmHeight + tm.tmExternalLeading 
		add		eax,DWORD ptr tm.tmExternalLeading
		mov		cyChar,eax

      invoke   ReleaseDC,hwnd, hdc
      
      ;Save the width of the three columns
    	mov		eax,cxChar			;iMaxWidth = 40 * cxChar + 22 * cxCaps
    	mov		ecx,40
    	mul		ecx
    	mov		ebx,eax
    	mov		eax,cxCaps
    	mov		ecx,22
    	mul		ecx
    	add		eax,ebx
    	mov		iMaxWidth,eax
    	
      ret
		.elseif message == WM_SIZE
      		mov		eax,lParam			;cxClient = LOWORD (lParam)
      		and		eax,0FFFFh
      		mov		cxClient,eax
      		
      		mov		eax,lParam
      		shr		eax,16
      		mov		cyClient,eax		;cyClient = HIWORD (lParam)
      		
      		mov		eax,sizeof ssi
      		mov		ssi.cbSize,eax
      		mov		DWORD ptr [ssi.fMask],SIF_RANGE or SIF_PAGE
      		mov		DWORD ptr [ssi.nMin],0
      		mov		DWORD ptr [ssi.nMax],NUMLINES - 1
      		xor		edx,edx				;si.nPage = cyClient / cyChar
      		mov		eax,cyClient
      		mov		ecx,cyChar
      		div		ecx
      		mov		DWORD ptr [ssi.nPage],eax       
      		;Set horizontal scroll bar range and page size
      		invoke	SetScrollInfo,hwnd,SB_VERT,addr ssi,TRUE      		
	  
      		mov		eax,sizeof ssi
      		mov		ssi.cbSize,eax
      		mov		DWORD ptr [ssi.fMask],SIF_RANGE or SIF_PAGE
      		mov		DWORD ptr [ssi.nMin],0
      		xor		edx,edx					;    si.nMax       = 2 + iMaxWidth / cxChar
      		mov		eax,iMaxWidth
      		mov		ecx,cxChar
      		div		ecx      
      		add		eax,2
      		mov		DWORD ptr [ssi.nMax],eax
      		
      		xor		edx,edx
      		mov		eax,cxClient
      		mov		ecx,cxChar
      		div		ecx
      		mov		DWORD ptr [ssi.nPage],eax    		
      		
      		;Set horizontal scroll bar range and page size
      		invoke	SetScrollInfo,hwnd,SB_HORZ,addr ssi,TRUE
  		ret
      .elseif message == WM_VSCROLL
      		;Get all the vertical scroll bar information
      		mov		eax,sizeof ssi
      		mov		ssi.cbSize,eax
      		mov		DWORD ptr [ssi.fMask],SIF_ALL
      		invoke	GetScrollInfo,hwnd,SB_VERT,addr ssi
      		;Save the position for comparison later on
      		mov		eax,ssi.nPos
      		mov		DWORD ptr iVertPos,eax
      
      			mov	eax,wParam		
      			and	eax,0FFFFh			;LOWORD (wParam)
      		.if	eax ==SB_TOP
      			mov	eax,ssi.nMin
      			mov	ssi.nPos,eax
      		.elseif	eax==SB_BOTTOM
      			mov	eax,ssi.nMax
      			mov	ssi.nPos,eax      		
      		.elseif	eax==SB_LINEUP
      			mov	eax,ssi.nPos
      			dec	eax
      			mov	ssi.nPos,eax
     			
     			.elseif eax==SB_LINEDOWN
     			
      			mov	eax,ssi.nPos
      			inc	eax
      			mov	ssi.nPos,eax
 			 
    			
				.elseif eax==SB_PAGEUP		
      			mov	eax,ssi.nPos
      			sub	eax,ssi.nPage
      			mov	ssi.nPos,eax
      			
      		.elseif eax==SB_PAGEDOWN            
      			mov	eax,ssi.nPos
      			add	eax,ssi.nPage
      			mov	ssi.nPos,eax
      			
      		.elseif eax==SB_THUMBTRACK
      			mov	eax,ssi.nTrackPos
      			mov	ssi.nPos,eax
 		      .endif 
	   		;Set the position and then retrieve it.  Due to adjustments
            ;by Windows it may not be the same as the value set.

				mov		DWORD ptr [ssi.fMask],SIF_POS
				invoke	SetScrollInfo,hwnd,SB_VERT,Addr ssi,TRUE
				invoke	GetScrollInfo,hwnd,SB_VERT,Addr ssi
				

				;If the position has changed, scroll the window and update 
				mov		eax,ssi.nPos
				.if		eax!=iVertPos
				mov		eax,iVertPos
				sub		eax,ssi.nPos
				mov		ecx,cyChar
				mul		ecx
				invoke	ScrollWindow,hwnd,0,eax,NULL,NULL
				invoke	UpdateWindow,hwnd
				.endif
		ret      
      .elseif message == WM_HSCROLL
      		mov		eax,sizeof ssi
      		mov		ssi.cbSize,eax
      		mov		DWORD ptr [ssi.fMask],SIF_ALL
      		invoke	GetScrollInfo,hwnd,SB_HORZ,addr ssi
      
      		mov		eax,ssi.nPos
				mov  DWORD ptr iHorzPos,eax ; 此处原为 iVertPos 
      
      
      
      			mov	eax,wParam		
      			and	eax,0FFFFh			;LOWORD (wParam)
      		.if	eax ==SB_LINELEFT
      			mov	eax,ssi.nPos
      			dec	eax
      			mov	ssi.nPos,eax
      		.elseif	eax==SB_LINERIGHT
      			mov	eax,ssi.nPos
      			inc	eax
      			mov	ssi.nPos,eax
      		.elseif	eax==SB_PAGELEFT
      			mov	eax,ssi.nPos
      			sub	eax,DWORD ptr ssi.nPage
      			mov	ssi.nPos,eax
    		.elseif eax==SB_PAGERIGHT
      			mov	eax,ssi.nPos
      			add	eax,DWORD ptr ssi.nPage
      			mov	ssi.nPos,eax
     		.elseif ax==SB_THUMBTRACK
      			mov	eax,ssi.nTrackPos
      			mov	ssi.nPos,eax
 		      .endif 
		   
				mov		DWORD ptr [ssi.fMask],SIF_POS
				invoke	SetScrollInfo,hwnd,SB_HORZ,Addr ssi,TRUE
				invoke	GetScrollInfo,hwnd,SB_HORZ,Addr ssi
				
			  mov eax,DWORD ptr [ssi.nPos]
		  .if eax != iHorzPos
				   mov  eax,iHorzPos
				   sub  eax,DWORD ptr [ ssi.nPos]
				   mov  ecx,cxChar
				   mul  ecx
				   invoke ScrollWindow,hwnd,eax,0,NULL,NULL
				   invoke UpdateWindow,hwnd
		  .endif
	  ret   		

     .elseif message == WM_KEYDOWN
      			mov	eax,wParam		
      			and	eax,0FFFFh			;LOWORD (wParam)
      		.if		eax == VK_HOME
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_TOP,0
      		.elseif	eax == VK_END
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_BOTTOM,0      		
      		.elseif	eax == VK_PRIOR
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_PAGEUP,0       
      		.elseif	eax == VK_NEXT
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_PAGEDOWN,0         			
      		.elseif	eax == VK_UP
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_LINEUP,0  
      		.elseif	eax == VK_DOWN
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_LINEDOWN,0        			
      		.elseif	eax == VK_LEFT
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_PAGEUP,0  
      		.elseif	eax == VK_RIGHT
      			invoke	SendMessage,hwnd,WM_VSCROLL,SB_PAGEDOWN,0         			
				.endif
      ret
      
		.elseif message == WM_PAINT
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax
		
		;Get vertical scroll bar position
		mov		eax,sizeof ssi
      mov		ssi.cbSize,eax
      mov		DWORD ptr [ssi.fMask],SIF_POS
      invoke	GetScrollInfo,hwnd,SB_VERT,addr ssi
      mov		eax,ssi.nPos
      mov		iVertPos,eax
      
 		;Get horizontal scroll bar position
 		invoke	GetScrollInfo,hwnd,SB_HORZ,addr ssi
      mov		eax,ssi.nPos
      mov		iHorzPos ,eax
      
     	;Find painting limits
		xor		edx,edx			;iPaintEnd=min(NUMLINES - 1,iVertPos + ps.rcPaint.bottom / cyChar)
      mov		eax,ps.rcPaint.bottom
      mov		ecx,cyChar
      div		ecx
      add		eax,iVertPos
      mov		ecx,(NUMLINES-1)
      .if		eax>ecx
      mov		eax,NUMLINES-1
      .endif
      mov		iPaintEnd,eax      
      
		xor		edx,edx			;iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar)
      mov		eax,ps.rcPaint.top
      mov		ecx,cyChar
      div		ecx
      add		eax,iVertPos
		cmp		eax,0						;iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1))
		jg			@f
		xor		eax,eax	
	@@:      
      mov		iPaintBeg,eax
      mov		i,eax						;i=iPaintBeg
		mov		eax,iPaintBeg			;ESI point to sysmetrics[i]
		shl		eax,2
		lea		esi,sysmetrics
		add		esi,4
		add		esi,eax
		add		esi,eax
		add		esi,eax
		
		;invoke	wsprintf,addr szBuffer,CTXT("%d %d"),iPaintBeg,iPaintEnd
		;invoke	MessageBox,hwnd,addr szBuffer,NULL,NULL 
@@:
		
		mov		eax,1				; x = cxChar * (1 - iHorzPos)
		sub		eax,iHorzPos
		mov		ecx,cxChar
		mul		ecx
		mov		x,eax

		mov		eax,i				; y = cyChar * (i - iVertPos)
		sub		eax,iVertPos
		mov		ecx,cyChar
		mul		ecx
		mov		y,eax
		

   	mov		edi,[esi]		;esi指向字符串的地址
   									;edi指向字符串
   	invoke	lstrlen,edi		;取字符串长度
   	mov		ebx,eax

;TextOut (hdc, 0, cyChar * i,sysmetrics[i].szLabel,lstrlen (sysmetrics[i].szLabel))
		invoke	TextOut,hdc,x,y,edi,ebx

		add		esi,4
		mov		edi,[esi]			;指向一个字符串地址
		
		mov		eax,cxCaps
		mov		ecx,22
		mul		ecx
		mov		ecx,DWORD ptr x
		add		ecx,eax
		
		
		push		ecx
		invoke	lstrlen,edi
   	mov		ebx,eax
   	pop		ecx

		invoke	TextOut,hdc,ecx,y,edi,ebx
		invoke	SetTextAlign,hdc,TA_RIGHT or TA_TOP	

		sub		esi,8					
		
		mov		edi,[esi]			;edi=sysmetrics[i].iIndex
		
		mov		eax,cxCaps			;x_Caps=22 * cxCaps + 40 * cxChar
		mov		ecx,22
		mul		ecx
		mov		ebx,eax
		mov		eax,cxChar
		mov		ecx,40
		mul		ecx
		add		eax,ebx
		add		eax,DWORD ptr	x
		mov		x_Caps,eax
		
		invoke	GetSystemMetrics,edi
		invoke	wsprintf,addr szBuffer,CTXT("%5d"),eax		;wsprintf格式化后的长度会
																			;作为返回值放在EAX中
		mov		ebx,eax
		
		invoke	TextOut,hdc,x_Caps,y,addr	szBuffer,ebx
		invoke	SetTextAlign,hdc,TA_LEFT or TA_TOP
   	
   	inc		i
   	add		esi,16
   	mov		eax,iPaintEnd
   	cmp		DWORD ptr i,eax
		jbe		@b		
		invoke	EndPaint,hwnd,addr ps

		ret	  		
	.elseif message == WM_DESTROY
		
		invoke PostQuitMessage,NULL		
		
	.endif
	
	invoke DefWindowProc,hwnd, message, wParam, lParam

	ret
WndProc endp
END START

      
    	

        

字符消息

    前面讨论了利用位移状态信息把按键消息翻译为字符消息的方法,并且提到,仅利用转换状态信息还不够,因为还需要知道与国家/地区有关的键盘配置。由于这个原因,您不应该试图把按键消息翻译为字符代码。Windows会为您完成这一工作,在前面我们曾看到过以下的程序代码:

while       (GetMessage (&msg, NULL, 0, 0))
        
{
        
    TranslateMessage (&msg) ;
        
    DispatchMessage (&msg) ;
        
}
        

    这是WinMain中典型的消息循环。GetMessage函数用队列中的下一个消息填入msg结构的字段。DispatchMessage以此消息为参数呼叫适当的窗口消息处理程序。

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

四类字符消息

字符消息可以分为四类,如表6-9所示。

表6-9

 

字符

死字符

非系统字符 WM_CHAR WM_DEADCHAR
系统字符 WM_SYSCHAR WM_SYSDEADCHAR

    WM_CHAR和WM_DEADCHAR消息是从WM_KEYDOWN得到的;而WM_SYSCHAR和WM_SYSDEADCHAR消息是从WM_SYSKEYDOWN     消息得到的(我将简要地讨论一下什么是死字符)。

    有一个好消息:在大多数情况下,Windows程序会忽略除WM_CHAR之外的任何消息。伴随四个字符消息的lParam参数与产生字符代码消息的按键消息之lParam参数相同。不过,参数wParam不是虚拟键码。实际上,它是ANSI或Unicode字符代码。

    这些字符消息是我们将文字传递给窗口消息处理程序时遇到的第一个消息。它们不是唯一的消息,其它消息伴随以0结尾的整个字符串。窗口消息处理程序是如何知道该字符是8位的ANSI字符还是16位的Unicode宽字符呢?很简单:任何与您用RegisterClassA(RegisterClass的ANSI版)注册的窗口类别相联系的窗口消息处理程序,都会获得含有ANSI字符代码的消息。如果窗口消息处理程序用RegisterClassW(RegisterClass的宽字符版)注册,那么传递给窗口消息处理程序的消息就带有Unicode字符代码。如果程序用RegisterClass注册窗口类别,那么在UNICODE标识符被定义时就呼叫RegisterClassW,否则呼叫RegisterClassA。

    除非在程序写作的时候混合了ANSI和Unicode的函数与窗口消息处理程序,用WM_CHAR消息(及其它三种字符消息)说明的字符代码将是:

(TCHAR) wParam
        

    同一个窗口消息处理程序可能会用到两个窗口类别,一个用RegisterClassA注册,而另一个用RegisterClassW注册。也就是说,窗口消息处理程序可能会获得一些ANSI字符代码消息和一些Unicode字符代码消息。如果您的窗口消息处理程序需要晓得目前窗口是否处理Unicode消息,则它可以呼叫:

fUnicode = IsWindowUnicode (hwnd) ;
        

    如果hwnd的窗口消息处理程序获得Unicode消息,那么变量fUnicode将为TRUE,这表示窗口是用RegisterClassW注册的窗口类别。

消息顺序

    因为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中定义的控制字符,它们用于描述这些键的字符代码。

    有时Windows程序将Ctrl与字母键的组合用作菜单快捷键(我将后面一期讨论),此时,不会将字母键转换成字符消息。

处理控制字符

    处理按键和字符消息的基本规则是:如果需要读取输入到窗口的键盘字符,那么您可以处理WM_CHAR消息。如果需要读取光标键、功能键、Delete、Insert、Shift、Ctrl以及Alt键,那么您可以处理WM_KEYDOWN消息。

    但是Tab键怎么办?Enter、Backspace和Escape键又怎么办?传统上,这些键都产生表6-13列出的ASCII控制字符。但是在Windows中,它们也产生虚拟键码。这些键应该在处理WM_CHAR或者在处理WM_KEYDOWN期间处理吗?

    经过10年的考虑(回顾这些年来我写过的Windows程序代码),我更喜欢将Tab、Enter、Backspace和Escape键处理成控制字符,而不是虚拟键。我通常这样处理WM_CHAR:

case WM_CHAR:
        
            //其它行程序
        
    switch (wParam)
        
    {
        
    case '\b':            // backspace
        
            //其它行程序
        
            break ;
        
    case '\t':            // tab
        
            //其它行程序
        
            break ;
        
    case '\n':            // linefeed
        
            //其它行程序
        
            break ;
        

    case '\r':            // carriage return
        
            //其它行程序
        
            break ;
        

    default:                      // character codes
        
            //其它行程序
        
            break ;
        
    }
        
    return 0 ;
        

死字符消息

    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就是这样的程序。

键盘消息和字符集

    本章剩下的范例程序有缺陷。它们不能在所有版本的Windows下都正常执行。这些缺陷不是特意引过程序代码中的;事实上,您也许永远不会遇到这些缺陷。只有在不同的键盘语言和键盘布局间切换,以及在多字节字符集的远东版Windows下执行程序时,这些问题才会出现-所以我不愿将它们称为「错误」。

   不过,如果程序使用Unicode编译并在Windows NT下执行,那么程序会执行得更好。

KEYVIEW1程序

    了解键盘国际化问题的第一步,就是检查Windows传递给窗口消息处理程序的键盘内容和字符消息。程序6-2所示的KEYVIEW1会对此有所帮助。该程序在显示区域显示Windows向窗口消息处理程序发送的8种不同键盘消息的全部信息。

程序6-2 KEYVIEW1

        
KEYVIEW1.ASM
        
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
Include libc.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib msvcrt.lib
include macro.asm
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.DATA
	szAppName   db "KeyView1",0
	
   szTop 		DB "Message         Key       Char    Repeat  Scan Ext   ALT Prev Tran",0
   szUnd 		DB "_______         ___       ____    ______  ____ ___   ___ ____ ____",0
        
	szFormat0	DB "%-13s %3d %-15s",0
	szFormat1	db "%-13s            0x%04X%1s%c",0
        
   szYes  		DB "  Yes",0
	szNo   		DB "  No ",0
	szDown 		DB " Down",0
	szUp   		DB	" Up  ",0

	szMessage 	DB	"WM_KEYDOWN    ",0,
						"WM_KEYUP      ",0,
						"WM_CHAR       ",0,
						"WM_DEADCHAR   ",0,
						"WM_SYSKEYDOWN ",0,
						"WM_SYSKEYUP   ",0,
						"WM_SYSCHAR    ",0,
						"WM_SYSDEADCHAR",0
.DATA?
   cLinesMax	DD	?
   cLines 		DD ?
   pmsg 			DD ?
   rectScroll	RECT  <>           
	hInstance	dd ?
   cxClientMax	dd	?
   cyClientMax	dd	?
   cxClient		dd	?
   cyClient		dd	?
   cxChar		dd	?
   cyChar 		dd	?
.CODE

START:   ;从这里开始执行
	invoke   GetModuleHandle,NULL
	mov 		hInstance,eax
	invoke   WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
	invoke   ExitProcess,0

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
	LOCAL wndclass :WNDCLASSEX
	LOCAL msg  		:MSG
	local hWnd 		:HWND
	mov wndclass.cbSize,sizeof WNDCLASSEX	
	mov wndclass.style,CS_HREDRAW or CS_VREDRAW	
	mov wndclass.lpfnWndProc,offset WndProc

	mov wndclass.cbClsExtra,0
	mov wndclass.cbWndExtra,0
	
	push hInst
	pop wndclass.hInstance
	
	invoke LoadIcon,NULL,IDI_APPLICATION
	mov wndclass.hIcon,eax	
	
	invoke LoadCursor,NULL,IDC_ARROW
	mov wndclass.hCursor,eax	
	
	invoke GetStockObject,WHITE_BRUSH
	mov wndclass.hbrBackground,EAX
	
	mov wndclass.lpszMenuName,NULL
	mov wndclass.lpszClassName,offset szAppName

	mov wndclass.hIconSm,0
	
	invoke RegisterClassEx, ADDR wndclass
	.if (EAX==0)
		 invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr szAppName,MB_ICONERROR 		
		 ret
	.endif
        
	invoke CreateWindowEx,
					NULL,
					ADDR szAppName, 						;window class name
					CTXT("Keyboard Message Viewer #1"), ;window caption
					WS_OVERLAPPEDWINDOW,					;window style
					CW_USEDEFAULT,							;initial x position
					CW_USEDEFAULT,							;initial y position
					CW_USEDEFAULT, 						;initial x size
					CW_USEDEFAULT,							;initial y size
					NULL,										;parent window handle
					NULL,										;window menu handle
					hInstance,								;program instance handle
					NULL										;creation parameters
	mov hWnd,eax
	
	invoke ShowWindow,hWnd,iCmdShow
	invoke UpdateWindow,hWnd
	
	StartLoop:
		invoke GetMessage,ADDR msg,NULL,0,0
			cmp eax, 0
			je ExitLoop
				invoke TranslateMessage, ADDR msg
				invoke DispatchMessage,  ADDR msg
			jmp StartLoop
	ExitLoop:
	
	mov eax,msg.wParam
	ret
WinMain endp

WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL hdc 				:HDC
   LOCAL i, iType			:DWORD
	LOCAL ps  				:PAINTSTRUCT 
	LOCAL szBuffer[256]	:BYTE        
	LOCAL szTmpbuf[128]	:BYTE 
	LOCAL szKeyName[32]	:BYTE	
	LOCAL tm					:TEXTMETRIC

	.if (message==WM_CREATE) || (message==WM_DISPLAYCHANGE)
		
		; Get maximum size of client area
      invoke	GetSystemMetrics,SM_CXMAXIMIZED
        mov		cxClientMax,eax
      invoke	GetSystemMetrics,SM_CYMAXIMIZED
        mov		cyClientMax,eax      
		
		; Get character size for fixed-pitch font
		invoke	GetDC,hwnd
		mov		hdc,eax

		;SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
		invoke	GetStockObject,SYSTEM_FIXED_FONT
		invoke	SelectObject,hdc,eax
		        
      ;GetTextMetrics (hdc, &tm) 
      invoke	GetTextMetrics,hdc, addr tm

	   ;cxChar = tm.tmAveCharWidth ;
   	;cyChar = tm.tmHeight ;
      mov		eax,tm.tmAveCharWidth
      mov		cxChar,eax
      
      mov		eax,tm.tmHeight
      mov		cyChar,eax

      invoke   ReleaseDC,hwnd, hdc
      
      ;Allocate memory for display lines
      .if		(pmsg!=NULL)
	      invoke	free,addr pmsg
      .endif
      xor		edx,edx
      mov		eax,cyClientMax
      mov		ecx,cyChar
      div		ecx
      mov		cLinesMax,eax
      mov		ecx,sizeof MSG
      mul		ecx
      invoke	malloc,eax
      mov		pmsg,eax
      mov		cLines,0
      
      jmp		@f		;原文是case语句,这个地方处理很特殊
		.elseif message == WM_SIZE
		@@:
		
			.if	(message == WM_SIZE)
      		mov		eax,lParam			;cxClient = LOWORD (lParam)
      		and		eax,0FFFFh
      		mov		cxClient,eax
      		
      		mov		eax,lParam
      		shr		eax,16
      		mov		cyClient,eax		;cyClient = HIWORD (lParam)
      	.endif
      	
            ;Calculate scrolling rectangle
				xor		eax,eax        
            mov		rectScroll.left,eax
        
        		mov		eax,cxClient
            mov		rectScroll.right,eax
        
        		mov		eax,cyChar
            mov		rectScroll.top,eax 
        
        		xor		edx,edx
        		mov		eax,cyClient
        		mov		ecx,cyChar
        		div		ecx
        		mov		ecx,cyChar
        		mul		ecx
        		mov		rectScroll.bottom,eax
        
            invoke	InvalidateRect,hwnd, NULL, TRUE
        
   		ret
   			
   	.elseif ((message==WM_KEYDOWN) || \
   				(message==WM_KEYUP) || \
   			   (message==WM_CHAR) || \
   				(message==WM_DEADCHAR) || \
   				(message==WM_SYSKEYDOWN) || \
   				(message==WM_SYSKEYUP) || \
   				(message==WM_SYSCHAR) || \
   				(message==WM_SYSDEADCHAR))  	   	

        ;Rearrange storage array
        mov		ecx,cLinesMax
        dec		ecx
        mov		edi,pmsg
        mov		eax,ecx
        mov		ebx,sizeof(MSG)
        mul		ebx
        add		edi,eax
        @@:
        mov		eax,[edi-28]		;这个地方写的复杂,功能很简单就是把前面保存的搬到后面
        mov		[edi],eax			;需要注意的是 sizeof(MSG)=28bytes
        mov		eax,[edi-24]
        mov		[edi+4],eax
        mov		eax,[edi-20]
        mov		[edi+8],eax
        mov		eax,[edi-16]
        mov		[edi+12],eax
        mov		eax,[edi-12]
        mov		[edi+16],eax
        mov		eax,[edi-8]
        mov		[edi+20],eax
        mov		eax,[edi-4]
        mov		[edi+24],eax
        sub		edi,sizeof(MSG)
		  loop	@b
		 
       ;Store new message
        mov		edi,pmsg
        assume	edi:PTR MSG
        mov		eax,hwnd
        mov		[edi].hwnd,eax
        mov		eax,message
        mov		[edi].message,eax  
        mov		eax,wParam
        mov		[edi].wParam,eax
        mov		eax,lParam
        mov		[edi].lParam,eax
        assume edi:nothing
        
        mov		eax,cLines
        inc		eax
        .if		(eax>cLinesMax)
        			mov		eax,cLinesMax
        .endif
        mov		cLines,eax
        
        mov		ebx,cyChar
        neg		ebx		;-cyChar
        ; Scroll up the display
        invoke ScrollWindow,hwnd, 0, ebx, addr rectScroll, addr rectScroll
        
        jmp	UseDefWindowProc ; i.e., call DefWindowProc so Sys messages work
        
		.elseif message == WM_PAINT
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax

		invoke	GetStockObject,SYSTEM_FIXED_FONT
		invoke	SelectObject,hdc,eax

		invoke	SetBkMode,hdc, TRANSPARENT
		
		invoke	lstrlen,addr szTop
		mov		ebx,eax
		invoke	TextOut,hdc, 0, 0, addr szTop, ebx
		
		invoke	lstrlen,addr szUnd
		mov		ebx,eax		
      invoke	TextOut,hdc, 0, 0, addr szUnd, ebx
      
		mov		esi,pmsg
      assume	esi:PTR MSG
      mov		i,0
iLoop:      
      xor		edx,edx
      mov		eax,cyClient
      mov		ecx,cyChar
      div		ecx
      dec		eax
      .if		(cLines<eax)
      mov		eax,cLines
      .endif

      cmp		i,eax
      jae		@f

     .if (([esi].message == WM_CHAR) || \
       	 ([esi].message == WM_SYSCHAR) || \
          ([esi].message == WM_DEADCHAR) || \
          ([esi].message == WM_SYSDEADCHAR)) 
          mov		iType,1		;TRUE=1
     .elseif
        	 mov		iType,0		;FALSE=0
     .endif
    	invoke	GetKeyNameText,[esi].lParam,addr szKeyName,32

        			lea	edi,szMessage
        			mov	eax,[esi].message
        			sub	eax,WM_KEYFIRST
        			mov	ecx,15
        			mul	ecx
        			add	edi,eax

             .if	(iType==1)
							invoke	wsprintf,addr szBuffer,
														addr szFormat1,
														edi,
														[esi].wParam,
														CTXT(' '),
														[esi].wParam
		      .elseif
							invoke	wsprintf,addr szBuffer,
														addr szFormat0,
														edi,
														[esi].wParam,
														addr szKeyName
 		      .endif
 		      
						  mov		ebx,[esi].lParam				  
						  and		ebx,0FFFFh
						  invoke	wsprintf,addr szTmpbuf,CTXT("%6u"),ebx
		              invoke	lstrcat,addr szBuffer,addr szTmpbuf
		              
						  mov		ebx,[esi].lParam				  
						  shr		ebx,16
						  and		ebx,0FFh
						  invoke	wsprintf,addr szTmpbuf,CTXT("  %4d"),ebx
		              invoke	lstrcat,addr szBuffer,addr szTmpbuf
		              
		              mov		eax,[esi].lParam
		              .if		(eax & 01000000h)
		              			invoke	lstrcat,addr szBuffer,addr szYes
		              .else	
		              			invoke	lstrcat,addr szBuffer,addr szNo              
		              .endif
		              
		              mov		eax,[esi].lParam		
		              .if		(eax & 20000000h)
		              			invoke	lstrcat,addr szBuffer,addr szYes
		              .else	
		              			invoke	lstrcat,addr szBuffer,addr  szNo             
		              .endif
		              
		              mov		eax,[esi].lParam		
		              .if		(eax & 40000000h)
		              			invoke	lstrcat,addr szBuffer,addr szDown 
		              .else	
		              			invoke	lstrcat,addr szBuffer,addr szUp             
		              .endif
		              
		              mov		eax,[esi].lParam		              
		              .if		(eax & 80000000h)
		              			invoke	lstrcat,addr szBuffer,addr szUp
		              .else	
		              			invoke	lstrcat,addr szBuffer,addr szDown              
		              .endif     
		              
            invoke lstrlen,addr szBuffer
             push	eax
             xor	edx,edx
             mov	eax,cyClient
             mov	ecx,cyChar
             div	ecx
             dec	eax
             sub	eax,i
             mov	ecx,cyChar
             mul	ecx
             mov	ecx,eax
				pop	ebx
            invoke TextOut,hdc, 0, ecx,addr szBuffer,ebx
      inc		i
      add		esi,sizeof(MSG)
      jmp		iLoop
@@:      
     assume esi:nothing
     
     	invoke	EndPaint,hwnd,addr ps
		ret	  	
        
	.elseif message == WM_DESTROY
		invoke PostQuitMessage,NULL		
	.endif
UseDefWindowProc:
	invoke DefWindowProc,hwnd, message, wParam, lParam
	ret
WndProc endp

END START

        

KEYVIEW1显示窗口消息处理程序接收到的每次按键和字符消息的内容,并将这些消息储存在一个MSG结构的数组中。该数组的大小依据最大化窗口的大小和等宽的系统字体。如果使用者在程序执行时调整了视讯显示的大小(在这种情况下KEYVIEW1接收WM_DISPLAYCHANGE消息),将重新分配此数组。KEYVIEW1使用标准C的malloc函数为数组配置内存。

图6-2给出了在键入「Windows」之后KEYVIEW1的屏幕显示。第一列显示了键盘消息;第二列在键名称的前面显示了按键消息的虚拟键代码,此代码是经由GetKeyNameText函数取得的;第三列(标注为「Char」)在字符本身的后面显示字符消息的十六进制字符代码。其余六列显示了lParam消息参数中六个字段的状态。


 

图6-2 KEYVIEW1的屏幕显示

    为便于以分行的方式显示此信息,KEYVIEW1使用了等宽字体。与前面所讨论的一样,这需要调用GetStockObject和SelectObject:

SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
        

    KEYVIEW1在显示区域上部画了一个标题以确定分成九行。此列文字带有底线。虽然可以建立一种带底线的字体,但这里使用了另一种方法。我定义了两个字符串变量szTop(有文字)和szUnd(有底线),并在WM_PAINT消息处理期间将它们同时显示在窗口顶部的同一位置。通常,Windows以一种「不透明」的方式显示文字,也就是说显示字符时Windows将擦除字符背景区。这将导致第二个字符串(szUnd)擦除掉前一个(szTop)。要防止这一现象的发生,可将设备内容切换到「透明」模式:

SetBkMode (hdc, TRANSPARENT) ;
        

这种加底线的方法只有在使用等宽字体时才可行。否则,底线字符将无法与显现在底线上面的字符等宽。

内容很多,继续看下一节>>>>

<<<上一篇
欢迎访问AoGo汇编小站:http://www.aogosoft.com
下一篇>>>