摘录于《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 | √ | 空格键 |
下表中列出的前八个代码以及 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 |
Windows 也包含了主键盘上的字母键和数字键的虚拟键代码(数字键盘被单独处理)。
十进制 | 十六进制 | WINUSER.H 中的标识符 | 是否必需 | IBM 兼容键盘 |
---|---|---|---|---|
48—57 | 30—39 | 无 | √ | 主键盘上的 0 到 9 |
65—90 | 41—5A | 无 | √ | A 到 Z |
下面的键是由微软 Natural Keyboard 键盘及其兼容键盘产生的。
十进制 | 十六进制 | WINUSER.H 中的标识符 | 是否必需 | IBM 兼容键盘 |
---|---|---|---|---|
91 | 5B | VK_LWIN | 左 Windows 键 | |
92 | 5C | VK_RWIN | 右 Windows 键 | |
93 | 5D | VK_APPS | 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) ;
}