6.2 击键消息

 摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P176

        当用户按下一个键时,Windows 将 WM_KEYDOWN 或 WM_SYSKEWDOWN 消息放入具有输入焦点的窗口的消息队列中。当该键被释放时,Windows 把 WM_KEYUP 或 WM_SYSKEYUP 消息放入相应的消息队列中。

 键按下键释放
 非系统键击 WM_KEYDOWN WM_KEYUP
 系统键击 WM_SYSKEYDOWN WM_SYSKEYUP

        通常键按下消息和键释放消息是成对出现的。但是如果你按下一个键不放时,则被认为发送了一次连续按键(自动重复)行为,Windows 将发送给窗口过程一连串的 WM_KEYDOWN(或 WM_SYSKEYDOWN)消息。当此键最终被释放时,Windows 发送给窗口过程一个 WM_KEYUP(或 WM_SYSKEYUP)消息。像所有的队列消息一样,击键消息是被可实时追踪的。你能通过调用 GetMessageTime 函数,得到键被按下或释放的相对时间

6.2.1  系统键击和非系统键击

        WM_SYSKEYDOWN 和 WM_SYSKEYUP 中的 “SYS”代表系统,它表明该击键对 Windows 比对 Windows 应用程序更加重要。当输入键和 Alt 键组合时通常产生的是 WM_SYSKEYDOWN 和 WM_SYSKEYUP 消息。这些按键调用程序菜单或系统菜单选项,被用来实现系统功能如转换活动窗口(Alt-Tab 键 Alt-Esc 键),或作为系统菜单快捷键(Alt 键和功能键的组合,如 Alt-F4 是用于关闭一个应用程序)。应用程序通常忽略 WM_SYSKEYUP 和 WM_SYSKEYDOWN 消息,将它们交付给 DefWindowProc 函数完成默认处理。因为Windows 关注所有的 Alt 键功能逻辑,应用程序就不必处理这些消息。你的窗口过程最终会接收到的是与击键产生结果相关的消息(如一个菜单被选中)。如果你在窗口过程中编码去捕获这些系统击键消息,则在处理完毕后,仍然需要发送这些消息给 DefWindowProc 函数,以便不影响 Windows 对它的处理。

        但是再仔细考虑一下。几乎所有影响程序窗口的消息都将先经过窗口过程。仅当应用程序传递给 DefWindowProc 函数时,Windows 才会处理这些消息。例如,如果你在窗口过程中增加下面几行:

[cpp]  view plain  copy
  1. case WM_SYSKEYDOWN:  
  2. case WM_SYSKEYUP:  
  3. case WM_SYCHAR:  
  4.     return 0;  
那么在你的程序主窗口具有输入焦点时,就可以有效地阻止所有 Alt 键的操作。这些操作包含 Alt-Tab 键、Alt-Esc 键和菜单操作。虽然你不一定想做这些,但我相信你能感觉到窗口过程内含的强大功能。

        不与 Alt 组合时按下和释放键会产生 WM_KEYDOWN 和 WM_KEYUP 消息。应用程序可以使用或者丢弃这些击键消息。Windows 也不处理它们。

        对所有四类击键消息,wParam 是虚拟键代码,用于标识哪个键被按下或被释放,而 lParam 包含属于本次击键的一些其他数据。

6.2.2  虚拟键代码

         虚拟键代码 存储在 WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN 和 WM_SYSKEYUP 消息的 wParam 参数中 此代码确定哪个键被按下或被释放

        啊哈,“虚拟”这个词无处不在,难道你不喜欢它吗?它原本是指存在于意念中而不是现实世界中的某物,但也只有使用 DOS 汇编语言编写应用程序的经验丰富的程序员才能领悟到,为什么对 Windows 键盘处理过程如此重要的键代码是虚拟的而不是真实的。

        对早期的程序员,真实的键码是由自然键盘硬件产生的Windows 文件称它们为扫描码。在 IBM 兼容键盘上,扫描码 16 为 Q 键,17 为 W 键,18 为 E 键,19 为 R 键,20 为 T 键,21 为 Y 键等。你会发现,扫描码基于键盘的自然布局。Windows 的程序开发者认为这些扫描码与键盘太相关了,所以他们通过定义所谓的虚拟键代码,试图使用与设备无关的方式来处理键盘。一些虚拟键代码没有用在 IBM 兼容键盘上,但可能能在其他制造厂商的键盘上找到,或者可能会用在未来的键盘上。

        你经常使用的大多数虚拟键代码命名是以 VK_ 开头的,它定义在 WINUSER.H 头文件中。下面这些表中列出了这些虚拟键代码的名称和数值(用十进制和十六进制)以及对应于虚拟键的 IBM 兼容键盘上的键。同时也指出了哪些键是 Windows 正常运转中所需要用到的。这些表以十进制顺序列出虚拟键代码。

        前四个虚拟键代码中的三个涉及鼠标按钮。

十进制十六进制WINUSER.H 中的标识符必需? IBM 兼容键盘
 1 01 VK_LBUTTON  鼠标左键
 2 02 VK_RBUTTON  鼠标右键
 3 03 VK_CANCEL √ Ctrl-Break
 4 04 VK_MBUTTON  鼠标中键

在键盘消息中你将永远不会得到鼠标按钮代码。鼠标按钮代码在鼠标消息中。VK_CANCEL 码是唯一的标识同时按下两个键(Ctrl+Break)的虚拟代码。Windows 应用程序通常不使用此键。

        以下表中的一些键,如退格键、Tab 键、回车键、Esc 键和空格键,经常被用于 Windows 程序中。但是 Windows 程序通常使用字符消息(而不是击键消息)来处理这些键。

十进制十六进制WINUSER.H是否必需IBM 兼容键盘
 8 08 VK_BACK √ 退格键
 9 09 VK_TAB √ Tab 键
 12 0C VK_CLEAR  数字锁定键关闭时的数字键 5
 13 0D VK_RETURN √ 回车键(任意一个)
 16 10 VK_SHIFT √ Shift 键(任意一个)
 17 11 VK_CONTROL √ Ctrl 键(任意一个)
 18 12 VK_MENU √ Alt 键(任意一个)
 19 13 VK_PAUSE  Pause 键
 20 14 VK_CAPITAL √ 大写锁定键
 27 1B VK_ESCAPE √ Esc 键
 32 20 VK_SPACE √ 空格键
同样,Windows 应用程序通常也不必去监视 Shift 键、Ctrl 键或 Alt 键的状态。

        下表中列出的前八个代码以及 VK_INSERT、VK_DELETE 码可能是最常使用的虚拟键代码:

十进制十六进制WINUSER.H 中的标识符是否必需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 CK_SNAPSHOT  Print Screen 键
 45 2D VK_INSERT √ Insert 键
 46 2E VK_DELETE √ Del 键
 47 2F VK_HELP  
注意,许多名称(如 VK_PRIOR 和 VK_NEXT)都和键上的标签不相同,也与滚动条上的标识符不一致。 Print Screen 键基本被 Windows 应用程序忽略了。Windows 通过把视频显示的点阵图副本复制到剪贴板来响应此键 。CK_SELECT、VK_PRINT、VK_EXECUTE 和 VK_HELP 为假想的键盘码,估计也很少有人看到过这样的键盘。

        Windows 也包含了主键盘上的字母键和数字键的虚拟键代码(数字键盘被单独处理)。

十进制十六进制WINUSER.H 中的标识符是否必需IBM 兼容键盘
 48—57 30—39 无 √ 主键盘上的 0 到 9
 65—90 41—5A 无 √ A 到 Z
注意,数字键和字母键的虚拟键代码就是 ASCII 码。 Windows 程序几乎从来不用这些虚拟键代码,相反这些程序依赖于 ASCII 字符表示的字符消息

        下面的键是由微软 Natural Keyboard 键盘及其兼容键盘产生的。

十进制十六进制WINUSER.H 中的标识符是否必需IBM 兼容键盘
 91 5B VK_LWIN  左 Windows 键
 92 5C VK_RWIN  右 Windows 键
 93 5D VK_APPS  Application 键
VK_LWIN 和 VK_RWIN 键被 Windows 用于打开开始菜单或(在较早的版本中)启动任务管理器。它们也能用于登录或注销 Windows(仅在 Microsoft Windows NT 中),或者是登录或注销网络(用于 Windows 的工作组版本)。应用程序能通过显示帮助信息或快捷键来处理 Application 键。

        下面的代码是和数字小键盘中的键相对应的代码(如果存在的话):

十进制十六进制WINUSER.H 中的标识符是否必需IBM 兼容键盘
 96—105 60—69 VK_NUMPAD0 到 
 VK_NUMPAD9
  数字锁定键打开时数字
 小键盘的 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 个数字标识符。此外,程序通常把功能键用作键盘快捷键,所以它们通常不处理下表的击键:

十进制十六进制WINUSER.H 中的标识符是否必需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  数字锁定键
 145 91 VK_SCROLL  Scroll Lock 键

        虽然还定义了其他一些虚拟键代码,但它们被保留为非标准键盘上的键或者主机终端上的键。

6.2.3  lparam 信息

        如前所述,在四个击键消息中(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN、WM_SYSKEYUP),wParam 消息参数包含了虚拟键代码,lParam 消息参数包含了帮助理解击键的其他有用消息。32 位的 lParam 消息被分成了 6 个字段,如图 6-1 所示。

      重复计数

        重复计数是消息所表示的击键的数目。大多数情况下,它被设置为 1。但是,如果你按下一个键不放,且窗口过程不足够快,跟不上输入速率(该项可在控制面板的【键盘】应用程序中设置)来处理击键消息,Windows 就会把一些 WM_KEYDOWN 和 WM_SYSKEYDOWN 消息合并成一个单独的消息,并相应增加重复计数字段。WM_KEYUP 和 WM_SYSKEYUP 消息的重复计数总是为 1。

        重复计数大于 1 表明此时连续击键的速度快于程序的处理能力,所以你可能想在处理键盘消息的时候忽略重复计数。由于额外的击键堆积,几乎每一个人都有过字处理文档或电子表格不停滚屏的经历。当程序要花费一段时间来处理每一个击键时,应用程序可以忽略重复计数来解决此问题。但是在其他情况下,你也许需要使用重复计数。你可能需要在这两种情况下执行程序,找到最合适的一种。

      OEM 扫描码

        OEM 扫描码是键盘硬件产生的代码。这对中年的汇编语言程序员来说是相当熟悉的,他们从 PC 兼容机的 ROM BIOS 服务中获得这些值(OEM 指的是个人计算机的原始设备制造厂商(Original Equipment Manufacturer),在这里是指“IBM 标准”)。我们不在需要这种东西了。 Windows 程序几乎可以做到忽略 OEM 扫描码,除非是它要依赖于键盘上键的分布

      扩展键标记

        如果击键结果来自于 IBM 加强型键盘的附加键,则扩展键标记为 1。(IBM 加强型键盘有 101 或 102 个键。键盘上部是功能键。光标移动键与数字小键盘分离,但数字小键盘保留有光标移动键的功能。)键盘右侧的 Alt 和 Ctrl 键、分离于数字小键盘的光标移动键(包含 Insert 键和 Delete 键)、数字小键盘的斜线和回车键,以及 Num Lock 键的这一标记位均设置为 1。 Windows 程序通常忽略扩展键标记

      内容代码

        如果在击键的同时也按下了 Alt 键,则内容代码为 1。WM_SYSKEYUP 和 WM_SYSKEYDOWN 消息的此位始终为 1,而 WM_KEYUP 和 WM_KEYDOWN 消息的此位始终为0。有两种情况例外。

  • 如果活动窗口最小化了,则它不具有输入焦点。所有的击键将产生 WM_SYSKEYUP 和 WM_SYSKEYDOWN 消息。如果 Alt 键未被按下,内容代码字段将被置为 0。Windows 处理 WM_SYSKEYUP 和 WM_SYSKEYDOWN 消息,使最小化的活动窗口不处理这些击键。
  • 在某些非英语的键盘上,一些字符是通过 Shift 键、Ctrl 键或 Alt 键同另一个键的组合产生的。在这些情况下,内容代码被设置为 1,但消息并不是系统击键消息。

      键的先前状态

         如果键以前是处于释放状态的,则键的先前状态为 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。

6.2.4  转义状态

        当处理击键消息时,你可能需要知道是否有转义键(Shift 键、Ctrl 键和 Alt 键)或切换键(Caps Lock 键、Num Lock 键和 Scroll Lock 键)被按下。你能通过调用 GetKeyState 函数获得此信息。例如:

[cpp]  view plain  copy
  1. iState = GetKeyState (VK_SHIFT);  
如果 Shift 键被按下,则 iState 变量为负(即高位置 1)。如果 Caps Lock 键打开,则从
[cpp]  view plain  copy
  1. iState = GetKeyState (VK_CAPITAL);  
返回的值是最低位置为 1。此位与键盘上的小灯保持一致。

        通常你会使用虚拟键代码 VK_SHIFT、VK_CONTROL 和 VK_MENU(你也许还记得指 Alt 键)来调用 GetKeyState 函数。你也能用 GetKeyState 函数通过标识符 VK_LSHIFT、VK_RSHIFT、VK_LCONTROL、VK_RCONTROL、VK_LMENU 或 VK_RMENU 来确定是左侧还是右侧的 Shift 键、Ctrl 键或 Alt 键被按下。这些标识符仅在 GetKeyState 函数和 GetAsyncKeyState 函数中使用。

        你也能使用虚拟键代码 VK_LBUTTON、VK_RBUTTON 和 VK_MBUTTON 来得到鼠标按钮的状态。但是,大多数需要监视鼠标按钮和击键的 Windows 程序通常使用另一种方法,即当 Windows 程序接收到鼠标消息时,才检查击键。实际上,转义状态信息被包含在鼠标消息中,我们将在下一章介绍。

        请注意 GetKeyState函数的用法。它并非实时的检查键盘状态。更准确地说,它反映了到目前为止的键盘状态,并包含了正在被处理的当前消息。大多数情况下,这正是你想要的。如果你需要确定用户是否按下了 Shift+Tab 键,可在处理 Tab 键的 WM_KEYDOWN 消息时,调用含 VK_SHIFT 参数的 GetKeyState 函数。如果 GetKeyState 函数的返回值是负的,你就知道在按下 Tab 键之前按下了 Shift 键。并且在你处理 Tab 键时,Shift 键是否已被释放没有什么影响。你只要知道在 Tab 键按下的时候,Shift 键是按下的。

        GetKeyState 函数无法让你获得独立于标准键盘消息的键盘消息。例如,你也许感到有必要暂停窗口过程的处理,直到用户按下 F1 功能键:

[cpp]  view plain  copy
  1. while (GetKeyState(VK_F1) >= 0);  // WRONG!!!  
这种做法是错误的!这一定会中止你的程序(当然,除非在执行该语句之前,你从消息队列中获得了 F1 功能键的 WM_KEYDOWN 消息)。如果你确实需要了解某个键的当前实时状态,可以使用 GetAsyncKeyState 函数。

6.2.5  使用击键消息

        Windows 程序能够获得程序运行时的每一个击键信息。这当然是有用的。但是,大部分的 Windows 程序几乎忽略所有的击键消息,只处理少数的一些击键消息。Windows 系统函数处理 WM_SYSKEYDOWN 和 WM_SYSKEYUP 消息,应用程序不必关心它们。如果应用程序处理 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 的目标之一就是提供不需要记忆或查询复杂命令表的用户界面

        因此,总结如下:大部分时间,你仅需要处理光标移动键的 WM_KEYDOWN 消息,有时处理 Insert 键和 Delete 键的 WM_KEYDOWN 消息。当使用这些键时,可以通过 GetKeyState 函数检查 Shift 键和 Ctrl 键的状态。例如,Windows 程序经常使用 Shift 键和光标键的组合来扩大字处理文档中的选中范围。Ctrl 键常用于改变光标键的意义。例如,Ctrl 键和右箭头键的组合用于将光标右移一个单词

        决定如何在你的应用程序中使用键盘的一种最好方法是,调查在当前流行的 Windows 程序中如何使用键盘。如果你不喜欢那些定义,也可以自由地做一些不同的事情。但是记住这样做不利于用户快速学习你的程序。

6.2.6  为 SYSMETS 加上键盘处理功能

        第 4 章 SYSMETS 程序的 3 个版本都是在不了解键盘的情况下写的。我们只能通过在滚动条上使用鼠标来滚动文本。现在我们知道怎样处理击键消息,就来给程序添加键盘接口。显然,这里的功能是处理光标移动键。垂直滚动中我们们会大量使用这些键(Home、End、Page Up、Page Down、上箭头和下箭头)。左箭头键和右箭头键用于不太重要的水平滚动。

        创建键盘接口的一个简单方法是在窗口过程中增加 WM_KEYDOWN 逻辑,它类似于或从本质上复制了所有的 WM_VSCROLL 和 WM_HSCROLL 逻辑。但是,这是不明智的。因为不管任何时候我们想修改滚动条逻辑,就不得不在 WM_KEYDOWN 消息上做同样的改变。

        简单地把每一个 WM_KEYDOWN 消息转换为等同的 WM_VSCROLL 或 WM_HSCROLL 消息,是不是会更好吗?然后我们可以通过给窗口过程发送假冒的消息欺骗 WndProc 函数,使它认为收到了滚动条消息。

        Windows 允许你这样做。函数命名为 SendMessage,它携带了传送给窗口过程的参数:

[cpp]  view plain  copy
  1. SendMessage (hwnd, message, wParam, lParam);  
当你调用 SendMessage 函数时,Windows 调用窗口句柄是 hwnd 的窗口过程,同时把四个函数变量传递给它。当窗口过程处理完此消息,Windows 把控制权交还给紧跟着 SendMessage 调用的下一条语句。 你向它发送消息的窗口过程可以是同一个窗口过程,也可以是同一程序的其他窗口过程,或者甚至是另一个应用程序的窗口过程

        下面将说明在 SYSMETS 程序中,我们怎样使用 SendMessage 函数处理 WM_KEYDOWN 消息:

[cpp]  view plain  copy
  1. case WM_KEYDOWN:  
  2.           switch (wParam)  
  3.           {  
  4.           case VK_HOME:  
  5.             SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);  
  6.             break;  
  7.   
  8.           case VK_END:  
  9.             SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);  
  10.             break;  
  11.   
  12.           case VK_PRIOR:  
  13.             SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);  
  14.             break;  

依次类推,大概意思已经清楚了。我们的目的是给滚动条增加键盘接口,并且也已经这么做了。实际上我们通过给窗口过程发送滚动条消息,实现了用光标移动键重复滚动条逻辑。现在你明白为什么我要在 SYSMETS3 程序的 WM_VSCROLL 消息中包含 SB_TOP 和 SB_BOTTOM 处理过程了吧。那时它没有用,现在它被用来处理 Home 和 End 键。

[cpp]  view plain  copy
  1. /*---------------------------------------------------- 
  2.    SYSMETS4.C -- System Metrics Display Program No. 4 
  3.                  (c) Charles Petzold, 1998 
  4.   ----------------------------------------------------*/  
  5.   
  6. #include <windows.h>  
  7. #include "sysmets.h"  
  8.   
  9. LRESULT CALLBACK WndProc (HWNDUINTWPARAMLPARAM) ;  
  10.   
  11. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  12.                     PSTR szCmdLine, int iCmdShow)  
  13. {  
  14.      static TCHAR szAppName[] = TEXT ("SysMets4") ;  
  15.      HWND         hwnd ;  
  16.      MSG          msg ;  
  17.      WNDCLASS     wndclass ;  
  18.   
  19.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;  
  20.      wndclass.lpfnWndProc   = WndProc ;  
  21.      wndclass.cbClsExtra    = 0 ;  
  22.      wndclass.cbWndExtra    = 0 ;  
  23.      wndclass.hInstance     = hInstance ;  
  24.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;  
  25.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;  
  26.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
  27.      wndclass.lpszMenuName  = NULL ;  
  28.      wndclass.lpszClassName = szAppName ;  
  29.   
  30.      if (!RegisterClass (&wndclass))  
  31.      {  
  32.           MessageBox (NULL, TEXT ("This program requires Windows NT!"),  
  33.                       szAppName, MB_ICONERROR) ;  
  34.           return 0 ;  
  35.      }  
  36.   
  37.      hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 4"),  
  38.                           WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,  
  39.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  40.                           CW_USEDEFAULT, CW_USEDEFAULT,  
  41.                           NULL, NULL, hInstance, NULL) ;  
  42.   
  43.      ShowWindow (hwnd, iCmdShow) ;  
  44.      UpdateWindow (hwnd) ;  
  45.   
  46.      while (GetMessage (&msg, NULL, 0, 0))  
  47.      {  
  48.           TranslateMessage (&msg) ;  
  49.           DispatchMessage (&msg) ;  
  50.      }  
  51.      return msg.wParam ;  
  52. }  
  53.   
  54. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  55. {  
  56.      static int  cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth ;  
  57.      HDC         hdc ;  
  58.      int         i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;  
  59.      PAINTSTRUCT ps ;  
  60.      SCROLLINFO  si ;  
  61.      TCHAR       szBuffer [10] ;  
  62.      TEXTMETRIC  tm ;  
  63.   
  64.      switch (message)  
  65.      {  
  66.      case WM_CREATE:  
  67.           hdc = GetDC (hwnd) ;  
  68.   
  69.           GetTextMetrics (hdc, &tm) ;  
  70.           cxChar = tm.tmAveCharWidth ;  
  71.           cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;  
  72.           cyChar = tm.tmHeight + tm.tmExternalLeading ;  
  73.   
  74.           ReleaseDC (hwnd, hdc) ;  
  75.   
  76.                 // Save the width of the three columns  
  77.   
  78.           iMaxWidth = 40 * cxChar + 22 * cxCaps;  
  79.           return 0 ;  
  80.   
  81.      case WM_SIZE:  
  82.           cxClient = LOWORD (lParam) ;  
  83.           cyClient = HIWORD (lParam) ;  
  84.   
  85.                 // Set Vertical scroll bar range and page size  
  86.   
  87.           si.cbSize     = sizeof (si) ;  
  88.           si.fMask      = SIF_RANGE | SIF_PAGE ;  
  89.   
  90.           si.nMin       = 0 ;  
  91.           si.nMax       = NUMLINES - 1 ;  
  92.           si.nPage      = cyClient / cyChar ;  
  93.           SetScrollInfo(hwnd, SB_VERT, &si, TRUE);  
  94.   
  95.                 // Set horizontal scroll bar range and page size  
  96.   
  97.           si.cbSize     = sizeof (si) ;  
  98.           si.fMask      = SIF_RANGE | SIF_PAGE ;  
  99.           si.nMin       = 0;  
  100.           si.nMax       = 2 + iMaxWidth / cxChar ;  
  101.           si.nPage      = cxClient / cxChar ;  
  102.           SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);  
  103.           return 0 ;  
  104.   
  105.      case WM_VSCROLL:  
  106.                 // Get all the vertical scroll bar information  
  107.   
  108.           si.cbSize     = sizeof (si);  
  109.           si.fMask      = SIF_ALL ;  
  110.           GetScrollInfo(hwnd, SB_VERT, &si);  
  111.   
  112.                 // Save the position for comparison later on  
  113.           iVertPos = si.nPos;  
  114.           switch (LOWORD (wParam))  
  115.           {  
  116.           case SB_TOP:  
  117.                si.nPos = si.nMin ;  
  118.                break ;  
  119.           case SB_BOTTOM:  
  120.                si.nPos = si.nMax ;  
  121.                break ;  
  122.           case SB_LINEUP:  
  123.                si.nPos -= 1 ;  
  124.                break ;  
  125.           case SB_LINEDOWN:  
  126.                si.nPos += 1 ;  
  127.                break ;  
  128.           case SB_PAGEUP:  
  129.                si.nPos -= si.nPage ;  
  130.                break ;  
  131.           case SB_PAGEDOWN:  
  132.                si.nPos += si.nPage ;  
  133.                break ;  
  134.           case SB_THUMBTRACK:  
  135.                si.nPos = si.nTrackPos ;  
  136.                break ;  
  137.           default:  
  138.                break ;  
  139.           }  
  140.                 // Set the position and then retrieve it. Due to adjustments  
  141.                 // by Windows it may not be the same as the value set.  
  142.   
  143.           si.fMask = SIF_POS ;  
  144.           SetScrollInfo(hwnd, SB_VERT, &si, TRUE);  
  145.           GetScrollInfo(hwnd, SB_VERT, &si);  
  146.   
  147.                 // If the position has changed, scroll the window and update it  
  148.           if (si.nPos != iVertPos)  
  149.           {  
  150.               ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos),  
  151.                                     NULL, NULL) ;  
  152.               UpdateWindow(hwnd) ;  
  153.           }  
  154.           return 0 ;  
  155.      case WM_HSCROLL:  
  156.                 // Get all the horizontal scroll bar information  
  157.   
  158.           si.cbSize = sizeof (si) ;  
  159.           si.fMask  = SIF_ALL ;  
  160.   
  161.                     // Save the position for comparison later on  
  162.   
  163.           GetScrollInfo(hwnd, SB_HORZ, &si);  
  164.           iHorzPos = si.nPos ;  
  165.   
  166.           switch (LOWORD (wParam))  
  167.           {  
  168.           case SB_LINELEFT:  
  169.                si.nPos -= 1 ;  
  170.                break ;  
  171.           case SB_LINERIGHT:  
  172.                si.nPos += 1 ;  
  173.                break ;  
  174.           case SB_PAGELEFT:  
  175.                si.nPos -= si.nPage ;  
  176.                break ;  
  177.           case SB_PAGERIGHT:  
  178.                si.nPos += si.nPage ;  
  179.                break ;  
  180.           case SB_THUMBPOSITION:  
  181.                si.nPos = si.nTrackPos ;  
  182.                break ;  
  183.           default:  
  184.                break ;  
  185.           }  
  186.                 // Set the position and then retrieve it. Due to adjustments  
  187.                 // by Windows it may not be the same as the value set.  
  188.   
  189.           si.fMask = SIF_POS ;  
  190.           SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);  
  191.           GetScrollInfo(hwnd, SB_HORZ, &si);  
  192.   
  193.                 // If the position has changed, scroll the window  
  194.           if (si.nPos != iHorzPos)  
  195.           {  
  196.               ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0,  
  197.                                     NULL, NULL) ;  
  198.           }  
  199.           return 0;  
  200.   
  201.      case WM_KEYDOWN:  
  202.           switch (wParam)  
  203.           {  
  204.           case VK_HOME:  
  205.             SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);  
  206.             break;  
  207.   
  208.           case VK_END:  
  209.             SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);  
  210.             break;  
  211.   
  212.           case VK_PRIOR:  
  213.             SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);  
  214.             break;  
  215.   
  216.           case VK_NEXT:  
  217.             SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);  
  218.             break;  
  219.   
  220.           case VK_UP:  
  221.             SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);  
  222.             break;  
  223.   
  224.           case VK_DOWN:  
  225.             SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);  
  226.             break;  
  227.   
  228.           case VK_LEFT:  
  229.             SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);  
  230.             break;  
  231.   
  232.           case VK_RIGHT:  
  233.             SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);  
  234.             break;  
  235.           }  
  236.           return 0;  
  237.   
  238.      case WM_PAINT :  
  239.           hdc = BeginPaint (hwnd, &ps) ;  
  240.   
  241.                 // Get vertical scroll bar position  
  242.   
  243.           si.cbSize = sizeof (si);  
  244.           si.fMask = SIF_POS;  
  245.           GetScrollInfo(hwnd, SB_VERT, &si);  
  246.           iVertPos = si.nPos;  
  247.   
  248.                 // Get horizontal scroll bar position  
  249.   
  250.           GetScrollInfo(hwnd, SB_HORZ, &si);  
  251.           iHorzPos = si.nPos;  
  252.   
  253.                 // Find painting limits  
  254.   
  255.           iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;  
  256.           iPaintEnd = min (NUMLINES - 1, iVertPos + ps.rcPaint.bottom / cyChar);  
  257.           for (i = iPaintBeg ; i <= iPaintEnd; i++)  
  258.           {  
  259.                x = cxChar * (1 - iHorzPos) ;  
  260.                y = cyChar * (i - iVertPos) ;  
  261.   
  262.                TextOut (hdc, x, y,  
  263.                         sysmetrics[i].szLabel,  
  264.                         lstrlen (sysmetrics[i].szLabel)) ;  
  265.   
  266.                TextOut (hdc, x + 22 * cxCaps, y,  
  267.                         sysmetrics[i].szDesc,  
  268.                         lstrlen (sysmetrics[i].szDesc)) ;  
  269.   
  270.                SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;  
  271.   
  272.                TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,  
  273.                         wsprintf (szBuffer, TEXT ("%5d"),  
  274.                              GetSystemMetrics (sysmetrics[i].iIndex))) ;  
  275.   
  276.                SetTextAlign (hdc, TA_LEFT | TA_TOP) ;  
  277.           }  
  278.           EndPaint (hwnd, &ps) ;  
  279.           return 0 ;  
  280.   
  281.      case WM_DESTROY :  
  282.           PostQuitMessage (0) ;  
  283.           return 0 ;  
  284.      }  
  285.      return DefWindowProc (hwnd, message, wParam, lParam) ;  
  286. }  
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值