Win32学习笔记 第四章 输出文本_2

类别:VC语言 点击:0 评论:0 推荐:

Win32学习笔记

作者: 姜学哲([email protected])

教材: Windows程序设计(第五版)北京大学出版社
 [美]Charles Petzold 著
 北京博彦科技发展有限公司 译  ¥:160

环境: windows2000 Pro + Internet Explorer 6.0 + DirectX8.1 + Visual C++ 6.0

图们江计算机程序编制小组(http://chulsoft.xiloo.com)版权所有,转载请说明出处
--------------------------------------------------------------------
【第四章 输出文本-2】

第四章太长了,所以我只能分成两段来写。这是第二段。而且难度也加大了。很多东东我都不能解释清楚。所以只能尽我所能了。书上写得很好了。本章的学习笔记大多都是照搬书上的内容。我有点怀疑写学习笔记的意义。

我们的程序急需一个垂直滚动条,而暂时还不需要水平滚动条。加入一个垂直滚动条很容易。CreateWindow()有一个参数是WS_OVERLAPPEDWINDOW,我们只要把它改成:

WS_OVERLAPPEDWINDOW |WS_VSCROLL

就可以了。

在调用CreateWindow()的时候窗口过程会收到一个WM_CREATE消息。第二个程序的WM_CREATE部分跟第一个程序差不多,只是第二个程序多了两个函数:

SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - 1, FALSE);
SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);

因为iVscrollPos是静态变量,所以在它的初始值为0。第一行的函数功能是确定滚动条的范围,第二行的函数是确定滚动条的位置。这个时候iVscrollPos的值是0。所以滚动条的位置当然也是0了。第一个函数中有一个NUMLINES。这个东东是在SYSMETS.H头文件中定义的。找一找看吧。

为了有助于处理WM_VSCROLL消息,窗口过程定义了一个静态变量iVscrollPos,这个变量是滚动框的当前位置。对于SB_LINEUP, SB_LINEDOWN只需要将滚动框调整一个单位的位置。对于SB_PAGEUP,            SB_PAGEDOWN,移动cyClient/cyChar个单位的位置。对于SB_THUMBPOSITION新的滚动框位置是wParam的高位字。SB_ENDSCROLL, SB_THUMBTRACK消息被忽略。

case WM_VSCROLL:
    switch(LOWORD(wParam))
   {
    case SB_LINEUP:
        iVscrollPos -= 1;
        break;
   
    case SB_LINEDOWN:
        iVscrollPos += 1;
        break;

    case SB_PAGEUP:
        iVscrollPos -= cyClient/cyChar;
        break;

    case SB_PAGEDOWN:
        iVscrollPos += cyClient/cyChar;
        break;

    case SB_THUMBPOSITION:
        iVscrollPos = HIWORD(wParam);
        break;

    default:
        break;
    }

我想上面这段代码再简单不过了。首先用LOWORD()宏获取通知码。然后对各种通知码做出不同的动作。通知码为SB_LINEUP时滚动框的位置iVscrollPos上升一位,也就是减一。你问我为什么减一?因为越是上面越接近0嘛!最上面是0,最下面是NUMLINES-1。然后就是

iVscrollPos = max(0, min(iVscrollPos, NUMLINES-1));

我们为什么需要这条语句呢?假设,当前iVscrollPos的值是0,这个时候程序接到了一个SB_LINEUP通知码,这样以来iVscrollPos就成了-1。问题是滚动框的位置不可能是-1,滚动框的位置是有一个范围的,也就是从0到NUMLINES-1,所以我们使用上面的语句。

当iVscrollPos比NUMLINES-1大的时候上面语句的值就是NUMLINES-1了。当iVscrollPos比0小的时候上面语句的值就是0。这样以来iVscrollPos的值就严格控制在0和NUMLINE-1之间了。

if(iVscrollPos != GetScrollPos(hwnd, SB_VERT))
{
    SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
    InvalidateRect(hwnd, NULL, TRUE);
}

虽然刚才说得那么热闹,滚动条的位置其实根本就没有改变。我们改变的只是整型变量iVscrollPos。下面我们就要用SetScrollPos()改变滚动框的位置。

SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);

这下子滚动框的位置真正变成了iVscrollPos。然后调用InvalidateRect()使得整个客户区无效,这样以来就产生一个WM_PAINT消息。慢慢来,上面还有一个if语句没有解释呢。有的时候虽然我们对滚动框进行了一些操作,但是滚动条的最终位置没有改变。这个时候根本就没有必要重画滚动条,但是如果没有这个if,程序就会重画滚动条。这是效率问题。如果您的机器配置很高档,就可以不用这个if语句了。我的机子不行。

第二个程序的WM_PAINT部分跟第一个程序唯一不同的一点是

TextOut()的第三个参数 y。

y = cyChar * (i - iVscrollPos);

当滚动框位置为0时上面的语句跟本章第一个程序没什么区别,都是:

y = cyChar * i;

但是!!!当滚动框位置为 1的时候上面的语句变成了:

y = cyChar * (-1)

也就是说第一行文本的显示位置是 cyChar * (-1)。这个时候程序仍然显示出NUMLINE行文本,只不过第一行文本显示位置是客户区上面。所以我们根本看不到第一行文本。第二行文本取代了第一行文本原来的位置。

这很合理。我们把滚动条位置下移了一个单位,于是第一行文本看不见了,第二行文本取代原来第一行文本所占的位置,显示在客户区第一行。所以,我们只要理解

y = cyChar * (i - iVscrollPos);

这个语句就可以了。这个语句是第二个程序的重点!!!!我猜~~~~~~~~~~

书上说这些程序的效率都是很低的。这些垃圾代码只是为了让我们学得容易。以后真正写软件的时候千万不要使用这些方法,很失败啊!下面就是本章的最后一个程序代码了。先要重新考虑WM_VSCROLL消息之后刷新客户区的方法。

处理完滚动条消息后SYSMETS2不刷新客户区,它调用InvalidateRect()使客户区无效。这样会产生一个WM_PAINT消息。

程序在接到WM_PAINT消息时刷新整个客户区,所以最好是在WM_PAINT消息期间完成所有的客户区绘制功能。如果在程序的其它部分也绘制的话,将很可能使代码重复。

只是把窗口客户区标记为无效以此产生WM_PAINT消息,对于某些应用程序来说也许不是完全令人满意的选择。在调用InvalidateRect()之后,Windows将WM_PAINT消息放入消息队列中,最后由窗口过程处理它。但是WM_PAINT的优先级很低。如果系统在这个时候有其它的动作,这个WM_PAINT消息得等一段时间,这样的话,当对话框消失后将会出现空白的“洞”。程序仍然等待刷新它的窗口。

如果希望立即刷新无效区域,可以在调用InvalidateRect()之后调用UpdateWindow()。

如果客户区任一部分无效,则UpdateWindow()将导致WM_PAINT消息调用窗口过程。这个WM_PAINT不进入消息队列,直接由窗口过程处理。窗口过程完成刷新后立即退出。

SYSMETS2工作得很好,但它只是模仿其它程序中的滚动条,并且效率很低。很快我将展示一个新的版本来改进它的不足。新版本不使用目前所讨论的4个滚动条函数,它将使用WIN32 API中才有的新函数。

MSDN的滚动条文档指出SetScrollRange(), SetScrollPos(), GetScrollRange(), GetScrollPos()是过时的。

Win32 API介绍的两个滚动条函数称作SetScrollInfo(), GetScrollInfo()。这两个函数可以完成以前函数的全部功能,并增加了两个新特性。

第一个特性涉及到滚动框的大小。在SYSMETS2程序中滚动框的尺寸是不变的。然而很多Windows程序中的滚动框的大小与窗口显示的文档大小成比例。

可以使用SetScrollInfo()来设置页面大小,从而设置了滚动框的大小,比如将要看到的SYSMETS3程序所示。

GetScrollInfo()函数增加了第二个重要功能,或者说它改进了了目前API的不足。假设我们想要使用65536或更大单位的范围,这在16位Windows中是不可能的。当然在WIN32中,函数被定义为可接受32位参数,所以滚动条的表示范围是很大的。

当程序收到包含SB_THUMBTRACK,SB_THUMBPOSITION通知码的WM_VSCROLL和WM_HSCROLL消息时只提供了16位数据来指出滚动框的当前位置。通过GetScrollInfo()函数可以获取真实的32位值。

SetScrollInfo()和GetScrollInfo()的语法是

SetScrollInfo(hwnd, iBar, &si, bRedraw);
GetScrollInfo(hwnd, iBar, &si);

像在其它滚动条函数中一样,iBar的参数是SB_VERT OR SB_HORZ,它还可以是用于滚动条控制的SB_CTL。SetScrollInfo()最后一个参数可以是TRUE或FALSE,指出是否要Windows重新绘制计算了新信息后的滚动条。

两个函数的第三个参数是SCROLLINFO结构,定义为:

typedef struct tagSCROLLINFO
{
    UINT cbSize;  //sizeof(SCROLLINFO)
    UINT fMask;  //参数选项
    int nMin;  //滚动条范围最小值
    int nMax;  //滚动条范围最大值
    UINT nPage;  //页面大小
    int nPos;  //滚动框当前位置
    int nTrackPos;  //si.cbMask设置为SIF_TRACKPOS,滚动框当前位置
}
SCROLLINFO, *PSCROLLINFO;

在调用SetScrollInfo()或GetScrollInfo()之前必须将cbSize域设置为SCROLLINFO结构的大小:

si.cbSize = sizeof(si);

si.cbSize = sizeof(SCROLLINFO);

逐渐熟悉Windows后,我们就会发现另外几个结构也像这样SCROLLINFO结构一样,第一个域指出了结构的尺寸。这个域使将来的Windows版本可以扩充结构并添加新的功能,并且仍然与以前编译的版本兼容。

把fMask域设置为以SIF前缀开头的一个或多个标志。当通过SetScrollInfo()使用SIF_RANGE时必须把nMin和nMax域设置为所需的滚动条范围。当通过GetScrollInfo()使用SIF_RANGE时函数返回滚动条的范围。

当SetScrollInfo()使用SIF_POS标志时,函数用来设置滚动条的位置。通过GetScrollInfo()使用该标志时,函数将返回滚动条的当前位置。

当SetScrollInfo()使用SIF_PAGE标志时,函数用来设置页面大小。通过GetScrollInfo()使用该标志时,函数将获取当前页面的大小。

当处理带有SB_THUMBTRACK, SBTHUMBPOSITION通知码的滚动条消息时,通过GetScrollInfo()只使用SIF_TRACKPOS标志,nTrackPos域将指出当前32位滚动框位置。

SIF_ALL标志是SIF_RANGE, SIF_POS, SIF_PAGE, SIF_TRACKPOS的组合。在WM_SIZE消息期间设置滚动条参数时这是很方便的。

在SYSMETS2程序中滚动范围设置最小为0,最大为NUMLINES-1。当滚动条位置是0时,第一行信息显示在客户区顶部,当滚动条的位置是NUMLINES-1时,最后一行显示在客户区的顶部,并且看不见其它行。

SYSMETS2滚动范围太大了,事实上只需要把最后一行信息显示在客户区的底部就可以了,用不着把最后一行显示到顶部。我们可以对SYSMETS2做出一些修改以达到这个目的。当处理WM_CREATE消息时不设置滚动条范围,而是等到接收到WM_SIZE消息后再做此工作:

iVscrollMax = max(0, NUMLINES-cyClient/cyChar);
SetScrollRange(hwnd, SB_VERT, 0, iVscrollMax, TRUE);

NUMLINES-cyClient/cyChar,这是关键。假定NUMLINE等于75(即滚动条范围为0 ~~ 74),并假定特定窗口大小是50(cyClient除以cyChar)。也就是说我们有75行信息,却一次只能显示50行可以显示在客户区中。在这种情况下还有25行是我们看不到的。当我们想看到最后一行信息的时候必须滚动25个单位。这个时候25行信息会显示在客户区顶部,74行信息显示在客户区底部。使用上面的两行代码,把范围设置最小0,最大25。当滚动条位置等于0时,程序显示0到49行。当滚动条位置等于1时,程序显示1到50行,当滚动条位置等于25时,程序显示25到74行信息。

si.cbSize = sizeof(SCROLLINFO);
si.cbMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = NUMLINES - 1;
si.nPage = cyClient/cyChar;

SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

这个时候Windows会把最大的滚动条范围限制为

si.nMax - si.nPage + 1

而不是NUMLINES-1。这样以来就很方便了。像前面说的那样,假设NUMLINES等于75,那么最后滚动条的最大范围是74-50+1,也就是25了。这正是我们想要的。这些都是系统自动设置的。虽然上面的代码中si.nMax被设置为NUMLINES-1,但是系统会自动把它设置成 si.nMax - si.nPage + 1。教材中有很多明显的翻译错误,所以我不知道自己的理解对不对。这个破书!也不整出个勘误表什么的。如果书上的翻译正确的话,上面我所说的都是正确的。如果这本书是侯SIR翻译的,一定会有一个勘误表。

发了一堆牢骚,也只能怪自己不懂英语。好了该看第四章的最后一个程序SYSMETS3了。

WinMain()部分,CreateWindow()的第三个参数变成了

WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL

WM_CREATE部分跟前面的程序没什么两样,只是多了一个

iMaxWidth = 40 * cxChar + 22 * cxCaps;

这是客户区最大宽度。是为水平滚动条准备的。

WM_SIZE部分

cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);

为了确定页面大小,所以先用上面两行语句获取客户区尺寸。接下来就要设置垂直滚动条的滚动范围和页面大小了。

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);

下面是设置水平滚动条的滚动范围和页面大小了。

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);

前面已经讲过,虽然我们把最大滚动范围设置为‘NUMLINES-1’和‘2+iMaxWidth/cxChar',但是Windows会自动把这两个值改成:

NUMLINES-1 - cyClient/cyChar + 1

2+iMaxWidth/cxChar - cxClient/cxChar + 1

这样以来对于垂直滚动条来讲,当用户把滚动框拖到最下面时,最后一行总是出现在客户区的底部,对于水平滚动条也是同样道理,不过因为iMaxWidth/cxChar之前有一个 +2 ,所以当用户把水平滚动条拉到最右边的时候客户区右边框与最后一列文本之间会有两格空间。

WM_VSCROLL消息部分,该项处理垂直滚动条消息了。首先使用GetScrollInfo()获取滚动条的信息,

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

然后就是switch判断部分了,这部分很好理解,根据具体的操作做出相应的反应,把结果保存到si.Pos中。最后使用SetScrollInfo()将滚动条的位置设置为si.Pos。

在switch语句之前有一个

iVertPos = si.Pos;

程序使用iVertPos保存si.Pos变化前的值。

if (si.nPos != iVertPos)

经过switch语句之后,如果si.nPos的值发生了改变,上面的if语句的条件就成了TRUE,然后执行里面的

ScrollWindow(hwnd, 0, cyChar*(iVertPos-si.nPos), NULL, NULL);
UpdateWindow(hwnd);

ScrollWindow()的功能是滚动客户区内容。假设滚动条接收到的是SB_LINEDOWN,si.nPos就会比iVertPos大,也就是说

iVertPos - si.nPos

的值是 -1,

在这个时候ScrollWindow()会把客户区的内容向上移动一行。当ScrollWindow()的第三个参数为正数时,该函数会把客户区的内容下移。在一开始谈滚动条的时候就说过当我们下拉滚动条的时候客户区的内容是向上移动的,一定要注意这一点。

UpdateWindow()使得立即刷新客户区。

水平滚动条部分也是同样道理。

WM_PAINT部分,

把垂直滚动条和水平滚动条的当前位置分别保存到iVertPos和iHorzPos中。

现在假设,一共有75行信息(0行到74行)需要显示,客户区能显示50行(0行到49行)。滚动条原来的位置是0。

用户把滚动条向下移动了两行,也就是说客户区的信息要向上移动两行,这个时候第0行,第1行已经看不见了,客户区顶部显示的是第2行的信息,而原来显示在客户区最后一行的第49行升到第47行了。这个时候第48行,第49行变成了空白区域

ps.rcPaint.top

是该空白区域的左上角坐标。

ps.rcPaint.top/cyChar

就成了空白区域最上面一行的行数,跟据上面的假设,这是第48行。客户区内容上移了两行,所以原来48行的位置上显示第50行的内容。然后在第49行显示的是第51行的内容。现在客户区显示了

第2行 ~~~~~~ 第51行

的内容。

ps.rcPaint.bottom/cyChar

是空白区域最后一行的显示位置。

iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
iPaintEnd = min (NUMLINES - 1, iVertPos + ps.rcPaint.bottom / cyChar) ;

但是我们可以看到在

ps.rcPaint.top / cyChar

前面还要加上iVertPos,这是为什么呢?

虽然是第48行,但是要显示的内容是原来第50行的。在第48行显示第50行的内容,所以要加上移动的行数iVertPos,这里iVertPos是2,所以正好能在第48行显示50行的内容。

进入for循环:

这个时候iPaintBeg的值是50,iPaintEnd的值是52。

y = cyChar * (i-iVertPos);

y的值变成了48,第50行信息的显示位置是48,

然后就是使用TextOut()显示文本串了。

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

要显示的是第i行的内容,也就是第50行的内容,显示坐标y是第48行的坐标,所以程序将第50行的内容显示到第48行上,以此类推。

加快WM_PAINT处理的一个方法由SYSMETS3演示,WM_PAINT代码确定无效区域中的行,并仅仅重画这些行。代码复杂但速度快。

本文地址:http://com.8s8s.com/it/it1757.htm