『Win32学习笔记』
作者: 姜学哲(netsail0@163.net)
教材: Windows程序设计(第五版)北京大学出版社
[美]Charles Petzold 著
北京博彦科技发展有限公司 译 ¥:160
环境: windows2000 Pro + Visual C++ 6.0
图们江计算机程序编制小组 版权所有,转载请说明出处
--------------------------------------------------------------------
【第六章 程序6-5 Typer】
看这本书越到后来越难。才第六章就这样了。书里面对程序没有太多的解释。全靠自己理解。好累啊。这个程序可费了我不少脑筋。所以我想给这个程序写个说明。刚学完就写,所以水平不限。有什么不妥的地方就骂我吧。
Typer程序使用了本章讨论的所有内容。我们可以认为TYPER是一个特别简单的文本编辑器。在窗口中可以输入字符,用光标移动键移动光标,按下Escape键擦除窗口内的内容。
WinMain部分就不用讲了。从第三章开始从来没有变过,我想您应该已经背得滚瓜烂熟了。What?还没有背?还等什么?赶紧背啊!
WM_INPUTLANGCHANGE:
书里面没有说明这个消息。但是我们一看就能明白,每当我们改变键盘布局的时候就会收到这个消息。因为里面有这么一句:
dwCharSet = wParam ;
dwCharSet里存储的是字符集ID --> DEFAULT_CHARSET。给该变量重新付值的唯一可能的原因就是键盘布局变了。而且我们可以看出该消息的wParam字段里有新的字符集ID。
WM_CREATE:
然后就是为已选定的键盘布局设置字体了。这是逻辑字体。
SelectObject(hdc, CreateFont(0, 0, 0, 0, 0, 0, 0, 0,dwCharSet, 0, 0, 0, FIXED_PITCH, NULL)) ;
然后就是获取字符的高度的宽度了,保存在 cxChar cyChar 里面。
这里请注意一个细节。从WM_INPUTLANGCHANGE消息开始一直往下直到WM_SIZE消息结束,都没有 break 或者 return 语句。这意味着即使程序收到的是WM_INPUTLANGCHANGE消息,它也会一直执行到WM_SIZE消息。
cxBuffer 和 cyBuffer 里面保存着客户区中可显示字符的行数和列数。
然后根据行数和列数创建 cxBuffer * cyBuffer 个 动态字符数组。
当客户区尺寸改变或者改变了键盘布局的时候程序会释放该动态数组,然后重新分配数组。但是这里有一个问题,因为
free(pBuffer)
在malloc函数前面,所以最终会导致该动态数组无法被释放。我听说过野指针,不知道是不是指这种情况。所以我在
WM_DESTROY
消息里加入了一句
free(pBuffer) ;
就在
PostQuitMessage(0) ;
后面。也可能是我错了,欢迎讨论。
程序最开始处有一个宏定义:
#define BUFFER(x, y) *(pBuffer + y * cxBuffer + x)
这个宏给出的是第 y 行 第 x 列的位置。
for (y = 0 ; y < cyBuffer ; y++)
for (x = 0 ; x < cxBuffer ; x++)
BUFFER(x,y) = ' ' ;
上面的语句把客户区用空格填满。我们可以把空格改成其它的什么,比如:
BUFFER(x,y) = 'A' ;
看看会有什么结果。
xCaret yCaret是光标的位置。函数GetFocus返回拥有输入焦点的窗口句柄。
SetCaretPos(xCaret*cxChar, yCaret*cyChar) ;
上面的语句把光标移到了客户区左上角。接着的函数就是刷新整个客户区了:
InvalidateRect(hwnd, NULL, TRUE) ;
return 0 ;
当我们改变了键盘布局(WM_INPUTLANGCHANGE),或是创建窗口(WM_CREATE),或是改变窗口尺寸(WM_SIZE)的时候我们对窗口客户区所做的改动都会消失。窗口会变成刚开始时候的样子,也就是一大片空格。
WM_SETFOCUS WM_KILLFOCUS消息在书中写得很明白。所以不说了。
WM_KEYDOWN 部分只有VK_DELETE要说一下。
这个DELETE跟记事本程序中DELETE的功能一样。即删除掉当前位置的字符,然后后继列左移一个单位,来填补空出来的位置。最后一列补一空格。
for (x = xCaret ; x < cxBuffer - 1 ; x++)
BUFFER (x, yCaret) = BUFFER (x + 1, yCaret) ;
BUFFER (cxBuffer - 1, yCaret) = ' ' ;
虽然已经改变了变量的内容,但是客户区的内容还是没有变,所以要用TextOut()显示出来。
TextOut (hdc, xCaret * cxChar, yCaret * cyChar,
& BUFFER (xCaret, yCaret),
cxBuffer - xCaret) ;
WM_CHAR:
for (i = 0 ; i < (int) LOWORD (lParam) ; i++)
LOWORD (lParam) 是按键的重复次数。
case '\b':
先把插入符左移一个单位,然后就是 WM_KEYDOWS 的 VK_DELETE部分了。
case '\t':
do
{
SendMessage (hwnd, WM_CHAR, ' ', 1) ;
}while (xCaret % 8 != 0) ;
break ;
现在,打开程序Typer.exe。刚打开的时候插入符的位置是 (0,0)。当我们按了一次 tab 键后位置变成了 (8,0),也就是向右移动了8个字符位。这么说 tab 键的功能是使用插入符向右移动8个字符位吗?末必。
我们现在使用光标移动键把插入符移到(2,0),现在再按一次 tab 键,这个时候插入符的位置还是(8,0)。按下 tab 键后插入符可能的位置是:(i*8,0)。
SendMessage (hwnd, WM_CHAR, ' ', 1) ;
使得程序执行 WM_CHAR 的default部分。
BUFFER(xCaret, yCaret) = (TCHAR)wParam ;
SendMessage()传递的是一个空格,所以 yCaret 行 xCaret 列的内容变成了空格。然后就是用TextOut()把变化后的内容显示出来了。
TextOut (hdc, xCaret * cxChar, yCaret * cyChar, & BUFFER (xCaret, yCaret), 1) ;
直到现在只改变了一个字符位。
然后插入符前进一位了。
++xCaret
这个 ++xCaret 包含在一个 if 里面:
if (++xCaret == cxBuffer)
{
xCaret = 0 ;
if (++yCaret == cyBuffer)
yCaret = 0 ;
}
++xCaret == cxBuffer 的作用是当插入符已经到达了客户区的最右边不能再右移的时候把插入符移到下一行首列。本程序使用两个语句做到了这一点:
xCaret = 0 ;
++yCaret
这个 ++yCaret 又包含在一个 if 里面。当插入符已经到达了最后一行的时候,程序会把插入符移到(0,0)。
WM_PAINT:
for (y = 0 ; y < cyBuffer ; y++)
TextOut (hdc, 0, y * cyChar, & BUFFER(0,y), cxBuffer) ;
上面的代码把所有动态数组中的内容都输出到客户区上。如果没有这一段代码,WM_SIZE 期间的
for (y = 0 ; y < cyBuffer ; y++)
for (x = 0 ; x < cxBuffer ; x++)
BUFFER(x,y) = ' ' ;
就没有用了。不过我实在是不明白上面的两层 for 语句到底有什么用。是不是多此一举啊?或者是我没有看明白?