第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 的意思。所以,这个变量名,分解开来,是 int,Paint 和 Begin 。意思就是,此变量用来设定绘制的开始,也就是重绘的起始位置。

相对应地,iPaintEnd,它分解开来,是 int,Paint 和 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,水平滚动

相关推荐
AIminminHu2 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(三-1-(3):番外篇-当你的CAD打开“怪兽级”STL时:从内存爆炸到零拷贝的极致优化
c++·零拷贝·mmap·内存拷贝
xiaoye-duck2 小时前
《算法题讲解指南:动态规划算法--子序列问题(附总结)》--32.最长的斐波那契子序列的长度,33.最长等差数列,34.等差数列划分II-子序列
c++·算法·动态规划
BestOrNothing_20152 小时前
C++零基础到工程实战(1.3):cpp注释与输出详解
c++·注释·命名空间·初学者教程·cout输出
sinat_255487812 小时前
泛型:超级、扩展、列表·学习笔记
java·windows·学习·算法
CoderMeijun2 小时前
C++构造与析构:对象的生与死
c++·面向对象·构造函数·析构函数·c++基础
REDcker2 小时前
C++ 多线程内存模型与 memory_order 详解
java·c++·spring
AbandonForce2 小时前
STL list
开发语言·c++
水饺编程2 小时前
第4章,[标签 Win32] :SysMets3 程序讲解05,水平滚动
c语言·c++·windows·visual studio
lihao lihao2 小时前
进程地址空间
数据结构·c++·算法