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 才会处理这些消息。例如,如果你在窗口过程中增加下面几行:

case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYCHAR:
    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 函数获得此信息。例如:

iState = GetKeyState (VK_SHIFT);
如果 Shift 键被按下,则 iState 变量为负(即高位置 1)。如果 Caps Lock 键打开,则从
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 功能键:

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,它携带了传送给窗口过程的参数:

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

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

case WM_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 键。

/*----------------------------------------------------
   SYSMETS4.C -- System Metrics Display Program No. 4
                 (c) Charles Petzold, 1998
  ----------------------------------------------------*/

#include <windows.h>
#include "sysmets.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("SysMets4") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }

     hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 4"),
                          WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

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

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static int  cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth ;
     HDC         hdc ;
     int         i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
     PAINTSTRUCT ps ;
     SCROLLINFO  si ;
     TCHAR       szBuffer [10] ;
     TEXTMETRIC  tm ;

     switch (message)
     {
     case WM_CREATE:
          hdc = GetDC (hwnd) ;

          GetTextMetrics (hdc, &tm) ;
          cxChar = tm.tmAveCharWidth ;
          cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
          cyChar = tm.tmHeight + tm.tmExternalLeading ;

          ReleaseDC (hwnd, hdc) ;

                // Save the width of the three columns

          iMaxWidth = 40 * cxChar + 22 * cxCaps;
          return 0 ;

     case WM_SIZE:
          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;

                // Set Vertical scroll bar range and page size

          si.cbSize     = sizeof (si) ;
          si.fMask      = SIF_RANGE | SIF_PAGE ;

          si.nMin       = 0 ;
          si.nMax       = NUMLINES - 1 ;
          si.nPage      = cyClient / cyChar ;
          SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

                // Set horizontal scroll bar range and page size

          si.cbSize     = sizeof (si) ;
          si.fMask      = SIF_RANGE | SIF_PAGE ;
          si.nMin       = 0;
          si.nMax       = 2 + iMaxWidth / cxChar ;
          si.nPage      = cxClient / cxChar ;
          SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
          return 0 ;

     case WM_VSCROLL:
                // Get all the vertical scroll bar information

          si.cbSize     = sizeof (si);
          si.fMask      = SIF_ALL ;
          GetScrollInfo(hwnd, SB_VERT, &si);

                // Save the position for comparison later on
          iVertPos = si.nPos;
          switch (LOWORD (wParam))
          {
          case SB_TOP:
               si.nPos = si.nMin ;
               break ;
          case SB_BOTTOM:
               si.nPos = si.nMax ;
               break ;
          case SB_LINEUP:
               si.nPos -= 1 ;
               break ;
          case SB_LINEDOWN:
               si.nPos += 1 ;
               break ;
          case SB_PAGEUP:
               si.nPos -= si.nPage ;
               break ;
          case SB_PAGEDOWN:
               si.nPos += si.nPage ;
               break ;
          case SB_THUMBTRACK:
               si.nPos = si.nTrackPos ;
               break ;
          default:
               break ;
          }
                // Set the position and then retrieve it. Due to adjustments
                // by Windows it may not be the same as the value set.

          si.fMask = SIF_POS ;
          SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
          GetScrollInfo(hwnd, SB_VERT, &si);

                // If the position has changed, scroll the window and update it
          if (si.nPos != iVertPos)
          {
              ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos),
                                    NULL, NULL) ;
              UpdateWindow(hwnd) ;
          }
          return 0 ;
     case WM_HSCROLL:
                // Get all the horizontal scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;

                    // Save the position for comparison later on

          GetScrollInfo(hwnd, SB_HORZ, &si);
          iHorzPos = si.nPos ;

          switch (LOWORD (wParam))
          {
          case SB_LINELEFT:
               si.nPos -= 1 ;
               break ;
          case SB_LINERIGHT:
               si.nPos += 1 ;
               break ;
          case SB_PAGELEFT:
               si.nPos -= si.nPage ;
               break ;
          case SB_PAGERIGHT:
               si.nPos += si.nPage ;
               break ;
          case SB_THUMBPOSITION:
               si.nPos = si.nTrackPos ;
               break ;
          default:
               break ;
          }
                // Set the position and then retrieve it. Due to adjustments
                // by Windows it may not be the same as the value set.

          si.fMask = SIF_POS ;
          SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
          GetScrollInfo(hwnd, SB_HORZ, &si);

                // If the position has changed, scroll the window
          if (si.nPos != iHorzPos)
          {
              ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0,
                                    NULL, NULL) ;
          }
          return 0;

     case WM_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;

          case VK_NEXT:
            SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
            break;

          case VK_UP:
            SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
            break;

          case VK_DOWN:
            SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
            break;

          case VK_LEFT:
            SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
            break;

          case VK_RIGHT:
            SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
            break;
          }
          return 0;

     case WM_PAINT :
          hdc = BeginPaint (hwnd, &ps) ;

                // Get vertical scroll bar position

          si.cbSize = sizeof (si);
          si.fMask = SIF_POS;
          GetScrollInfo(hwnd, SB_VERT, &si);
          iVertPos = si.nPos;

                // Get horizontal scroll bar position

          GetScrollInfo(hwnd, SB_HORZ, &si);
          iHorzPos = si.nPos;

                // Find painting limits

          iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
          iPaintEnd = min (NUMLINES - 1, iVertPos + ps.rcPaint.bottom / cyChar);
          for (i = iPaintBeg ; i <= iPaintEnd; i++)
          {
               x = cxChar * (1 - iHorzPos) ;
               y = cyChar * (i - iVertPos) ;

               TextOut (hdc, x, y,
                        sysmetrics[i].szLabel,
                        lstrlen (sysmetrics[i].szLabel)) ;

               TextOut (hdc, x + 22 * cxCaps, y,
                        sysmetrics[i].szDesc,
                        lstrlen (sysmetrics[i].szDesc)) ;

               SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

               TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
                        wsprintf (szBuffer, TEXT ("%5d"),
                             GetSystemMetrics (sysmetrics[i].iIndex))) ;

               SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
          }
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY :
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值