第4章,[标签 Win32] :SysMets3 程序讲解04,垂直滚屏重绘

张开发
2026/5/3 10:53:33 15 分钟阅读
第4章,[标签 Win32] :SysMets3 程序讲解04,垂直滚屏重绘
专栏导航上一篇第4章[标签 Win32] SysMets3 程序讲解03垂直滚屏原理回到目录下一篇第4章[标签 Win32] SysMets3 程序讲解05水平滚动本节前言对于本节所讲解的知识有可能你会需要时不时地参考本专栏的其它文章。真的遇到了需要参考之前的文章的知识点请你自行查阅。我呢也会提到一部分的参考课节。但是呢你不应该依赖于我的主动提及。最好呢你自己能够多去了解和查看本专栏目录。本节我们需要结合着 SysMets3 的代码来讲解知识。关于 SysMets3 的代码请参考如下课节。参考课节SysMets3待修改我们还需要以上一节的 “垂直滚屏原理” 作为先修知识。关于垂直滚屏原理的课节链接如下所示。参考课节垂直滚屏原理待修改本节我们要去讲解 WM_PAINT 消息处理的部分代码。具体地我们要去讲解的是关于垂直滚动条处理的部分代码。我们开始。一. iVertPos 的设置与滚屏原理讲述我们来看一看 WM_PAINT 消息处理的开头几行的代码。图1在开头几行最主要的是做了两件事那就是通过调用 GetScrollInfo 函数获取垂直滚动条和水平滚动条的当前位置值分别将这两个位置值保存在变量 iVertPos 和 iHorzPos 中。如图1 的227 行与 232 行所示。此时所获得的滚动条位置它是滚动条的最新的位置。在这里我以垂直滚动条为例来说明这个位置值的含义。一般地我们在使用一个程序比如一个文本编辑器或者是记事本或者是 Notepad或者是 VS Code当我们在使用滚动条来翻看文本的时候在我们拖动滚动条的同时文本就跟着卷动了。而在程序的运行的时候程序的运行机理与我们所看到的可能不是一致的。在这里我以逻辑的思路将其暂且分解开来。这种分解不见得对但是呢暂时这么来分解着还是可以有助于我们的理解。当我们单击滑块下方的滚动条区域的时候在程序的 WM_VSCROLL 消息的处理代码中已经是设置好了滑块的新位置值并且已经通过调用 SetScrollInfo 函数并在其中的最后一个参数中指定 TRUE使得滚动条已经被重绘了。当滚动条重绘好了以后滚动条滑块已经被绘制在新位置上了。在这个时候滚动条滑块已经处在新位置上了但是文本内容尚未完成滚屏工作。所以呢对于单击滑块下方的滚动条空白位置这一操作其运行效果要分为两部分。第一阶段滚动条滑块要变到新位置上而客户区内容尚未进行滚屏工作。第二阶段客户区内容进行滚屏活动。滚屏工作有一部分是在 WM_VSCROLL 消息代码中进行的也就是在 ScrollWindow 函数中进行的另一部分则是在 WM_PAINT 消息处理代码中进行。对于滚屏工作根据上一节的讲解我们也是将其分为了两个阶段。第一阶段客户区的某些可见内容进行滚屏滚屏后依然显示在客户区中。滚屏中还会出现新行新行显示为空白行。第二阶段对空白行进行重绘。在我们的理解中我们可以认为在 WM_VSCROLL 消息处理代码中滚屏操作完成了第一阶段的任务即客户区中原来的部分可见内容的滚屏与产生新的空白行。第一阶段的工作是在调用 ScrollWindow 函数时进行并完成的。而滚屏的第二阶段的工作需要在 WM_PAINT 消息处理代码中进行。当程序运行到 WM_PAINT 的处理代码刚刚运行完图1 所示的 232 行代码行的时候此时iVertPos 获取了滚动条滑块的位置值。这个位置值它不是单击滑块下方的滚动条空白区域之前的旧位置值而是单击了滑块下方的滚动条空白区域以后已经重绘了滚动条之后的滑块的新位置值。只是在这个时候滑块已经是变动到新位置了而由滚屏动作所产生的空白行的部分尚未进行重绘。二. 确定重绘位置iPaintBeg 与 iPaintEnd在图1 往下是对 iPaintBeg 与 iPaintEnd 变量的设置。iPaintBeg 中的 i它是表示变量类型的前缀是 int 的意思。Beg是 Begin 的意思。所以这个变量名分解开来是 intPaint 和 Begin 。意思就是此变量用来设定绘制的开始也就是重绘的起始位置。相对应地iPaintEnd它分解开来是 intPaint 和 End。意思就是此变量用来设定绘制的结束也就是重绘的结束位置。我们来看一看程序中是如何来设置这两个变量的。图2通过 max 和 min 宏程序让 iPaintBeg iPaintEnd 限制在滚动条的范围最小值 0 和 范围最大值 NUMLINE - 1 之间。对于 iPaintBeg程序主要是将其设定为 iVertPos ps.rcPaint.top / cyChar 。关于 iVertPos ps.rcPaint.top / cyChar我们还真得是花点时间去讲解它。ps是绘制结构 PAINTSTRUCT 的结构体变量它里面包含了绘制结构的一些个信息包含了本次绘制的一些个信息。很多时候重绘的部分是一个矩形区域。而这个需要重绘的矩形区域的上边缘就是 ps.rcPaint.top需要重绘的矩形区域的下边缘是图2 的行号 238 行所示的 ps.rcPaint.bottom 。iVertPos它是滚动条新位置值它也表明在滚屏操作完成时需要绘制在客户区顶行的内容是文档全部内容的哪一个行号。这个行号以 0 为起始行号。SysMets3 的头文件中结构体变量数组 sysmetrics 中共有 75 个数组元素因此文档中共有 75 行内容。iVertPos 中设定的新位置值对应着文档中的行号对应着 SysMets.h 中 sysmetrics 数组的索引号。所以呢iVertPos 中保存的滚动条新位置值它是表明sysmetrics 数组中哪一个索引号数组元素的内容会被显示在客户区的顶行。这么说名字太长了不容易记忆。我们给它一个简略的称呼我们将其称作文档滚动条行号。ps.rcPaint.top上面我们已经说过了它是需要重绘的矩形区域的上边缘的 y 坐标值。cyChar它是当前设备环境里字体的一行的高度。ps.rcPaint.top / cyChar表示重绘矩形的上边缘位于从客户区顶端向向下数的文字行号。也就是说原本客户区的 y 坐标值是以像素来标识的。现在呢我们将客户区的每一个 y 坐标值转换为文字行号则 ps.rcPaint.top / cyChar 所表示的就是重绘矩形的上边缘处于客户区的哪个文字行号的位置。我们假定某一设备环境之下一行文本的高度是 20 像素则客户区的 y 坐标中0 到 19 位于客户区文本 0 行20 到 39 位于客户区文本 1 行40 到 59 位于客户区文本 2 行其余的以此类推。小结一下ps.rcPaint.top / cyChar 所表示的就是重绘矩形的上边缘所在的客户区文本行号位置。我们也给它一个简称叫做客户区文本行号。客户区文本行号是 ps.rcPaint.top / cyChar 。而客户区顶行也就是客户区文本 0 行对应着的文档行号为文档滚动条行号为 iVertPos 。客户区文本 0 行所对应的文档行号是 iVertPos而重绘区域的上边缘对应的客户区文本行号是 ps.rcPaint.top / cyChar所以呢重绘区域的上边缘所对应的文档行号为 iVertPos ps.rcPaint.top / cyChar 。所以呢iPaintBeg在经过赋值以后它所表示的就是重绘区域的上边缘所对应的文档行号就是要把文档中的哪一行作为重绘的起始行。给它一个简称iPaintBeg表示重绘起始文档行号。到了这里iPaintBeg 我们就算是讲完了。讲完了 iPaintBeg 以后iPaintEnd 就能够容易理解了。对于 iPaintEnd程序主要是将其设定为 iVertPos ps.rcPaint.bottom / cyChar 。其中ps.rcPaint.bottom / cyChar 表示重绘区域的下边缘所处的客户区文本行号iVertPos 表示文档滚动条行号所以iVertPos ps.rcPaint.bottom / cyChar 所表示的是重绘区域的下边缘所处的文档行号。所以iPaintEnd表示充会趋于的下边缘所述的文档行号简称为重绘结束文档行号。好了到了这里重绘的起始位置和结束位置我们就讲完了。接下来我们还需要看具体的重绘代码 TextOut 。三. for 循环中的文本重绘代码我们来看代码截图。图3图3 中for 循环的开始是设置 x 和 y而这个 x 和 y 分别作为245 行的的 TextOut 调用的 x 实参和 y 实参。再往后的几个 TextOut 函数调用我们在 SysMets1 和 SysMets2 里面是讲过的。因此我们本节不详细展开 249 行到 259 行只重点关注着 245 行的 TextOut 调用。245 行的 TextOut 函数调用中x 和 y 实参位置传过去的便是 242 和 243 行设置的 x 和 y 变量。242 行设置的 x 变量它与水平滚动条有关。243 行设置的 y 变量它与垂直滚动条有关。本节我们不讲水平滚动条的内容。水平滚动条的内容我们留到下一节来展开讲解。因此在本节对于 x我们可以忽略它。或者你可以在 245 的 TextOut 函数调用中将 x 看作是 0 就可以了。在进一步地你可以将 245 到 259 行中出现的 x全都看作是 0 。总之本节我们不管 x 变量只探讨 y 变量的使用。for 循环中i 的初始设定是 iPaintBeg也就是重绘的文档起始行号。i 的结束设定是 iPaintEnd也就是重绘结束文档行号。迭代变量代码中i 每一次自加1 。这其实是用来设定重绘的文档行的在第一次进入 for 循环时去绘制的是待重绘的文档起始起始行号iPaintBeg 。绘制完了这一行以后i 自加1绘制下一行。以后每绘制完了一行就将 i 自加 1以便下一次的 for 循环中绘制下一行。在具体的某一次绘制中我们来看一看绘制工作是如何来进行的。在第一次进入 for 循环时i 被赋值为 iPaintBeg也就是重绘文档起始行号。接下来设定 y 值y 的赋值代码如下。y cyChar * (i - iVertPos);i 此时是重绘文档起始行号而 iVertPos 是文档滚动条行号。文档滚动条行号指的是文档中哪一行要被绘制在客户区的 0 行。假定文档滚动条行号为 3则文档行号3 被绘制在客户区 0 行文档行号 4 被绘制在客户区 1 行文档行号 5 被绘制在客户区 2 行其余的依此类推。由于在第一次进入 for 循环时i 为 iPaintBeg为重绘文档起始行号所以i - iVertPos 表示重绘文档起始行号要被绘制在客户区的哪一文本行号的位置。进一步地在任一次 for 循环中i - iVerPos它所表示的是当次待重绘的文档行号要被绘制在客户区的哪一个文本行号之中。cyChar 是以像素表示的一行文本的像素高度。计算好了待重绘的客户区文本行号之后将 i - iVertPos 乘以 cyChar所得到的便是当次 for 循环中待重绘的文档行号的客户区起始 y 坐标其单位是像素。当次 for 循环中待重绘的文档行号对应的客户区起始 y 坐标计算好了以后接下来调用 TextOut将相关的文本显示在 纵坐标 y 起始的位置上那么当次的 for 循环的绘制工作就完成了。每进入一次 for 循环就计算一次 y 值每一个 y 值用来绘制一行文本。绘制完了一行以后继续绘制下一行。直到将最后一行也就是 i iPaintEnd带重绘的结束文档行号将其绘制好了以后for 循环会结束 。for 循环结束以后WM_PAINT 消息处理也就差不多结束了。四. iPaintBeg 与 iPaintEnd 的初始设定对于滚屏重绘的原理与过程我们都已经是讲完了。可是在这里还有一个小问题。当我们刚刚打开程序时重绘工作是如何进行的呢当我们刚刚打开程序时程序首先会执行 WinMain 函数。在 WinMain 函数里面首先会在调用 CreateWindow 函数时产生一个 WIM_CREATE 消息之后又会产生一个 WM_SIZE 消息。在第一次产生 WM_SIZE 消息时消息处理代码会设置滚动条参数。此时滚动条的范围和页面大小将会进行第一次设置。如图所示。图4在图4 里面无论是垂直滚动条还是水平滚动条所设置的都是页面大小和滚动条范围。而滚动条的位置是没有设置的。这是因为在初始情况里滚动条滑块的位置是 0无须程序员手动设置。WM_ SIZE 之后接下来呢就是在主函数里面调用了 UpdateWindow 函数之后产生一条 WM_PAINT 消息。在 WM_PAINT 消息里面获取的垂直和水平滚动条滑块的位置都会是 0 。也就是iVertPos 和 iHorzPos 都会被设置为 0 。接下来呢要去设置 iPaintBeg 和 iPaintEnd 了。iPaintBeg max(0, iVertPos ps.rcPaint.top / cyChar);iPaintEnd min(NUMLINES - 1, iVertPos ps.rcPaint.bottom / cyChar);第一次产生 WM_PAINT 时整个客户区都是无效的。此时ps.rcPaint.top 会等于 0而 iVertPos 也是 0因此iPaintBeg 会被设定为 0 值。因此重绘的起始行号是文档 0 行并且会将其绘制在客户区的 0 行位置。第一次产生 WM_PAINT 时由于整个客户区都是无效的因此ps.rcPaint.bottom 会等于客户区的像素高度。大体上在程序刚刚打开时程序会从客户区的 0 行开始绘制绘制在客户区 0 行的会是文档 0 行。然后呢以整个客户区的高度为限能绘制多少行就绘制多少行。若是在客户区的下面绘制完了最后一个整行以后剩余高度不足以绘制完整的一行文字也要去绘制这一行文字只是只是客户区以内的正常显示客户区之外的部分不予显示。结束语本节内容较多。我在写作的时候也是花费了一番精力去思考。希望大家能够学好本节内容。也许本节内容你还需要多看两遍呢。专栏导航上一篇第4章[标签 Win32] SysMets3 程序讲解03垂直滚屏原理回到目录下一篇第4章[标签 Win32] SysMets3 程序讲解05水平滚动

更多文章