5.6 矩形、区域和裁剪
2015-10-23 09:52
232 查看
摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P161
Windows 还有其他几个使用 RECT(矩形)结构和区域的绘图函数。一个区域指的是屏幕上的一块空间,它由矩形、多边形和椭圆组合而成。
FillRect 函数使用指定的画刷填充矩形(达到但不包括右下坐标)。这个函数不需要事先把画刷选入设备环境。
FrameRect 使用画刷绘制一个矩形框,但是它并不填充矩形。使用画刷来绘制边框似乎有点奇怪,因为到目前为止,你所看到的绘制边框的函数(例如 Rectangle)都是由当前画笔绘制的。FrameRect
函数允许你绘制一个不一定是纯色的矩形框。矩形的边框是 1 个逻辑单位宽。如果逻辑单位大于设备单位,边框的宽度将是 2 个或者更多的像素。
InvertRect 函数翻转矩形内所有的像素,将 1 变为 0,0 变为 1。即这个函数将白色区域变为黑色区域,黑色区域变为白色区域,绿色区域变为洋红色区域。
Windows 还包含 9 个可用于轻松便捷地操纵 RECT 结构的函数。例如,通常使用下面的代码将 RECT 结构的 4 个字段设置为特定值:
如果想做下列事情之一,可以方便的使用其他 8 个函数。
将矩形沿 x 轴和 y 轴移动几个单位:
增大或减小矩形的尺寸:
把矩形结构的各字段设置为0:
将一个矩形结构复制到另一个矩形结构:
获取两个矩形的交集:
获取两个矩形的并集:
判断矩形是否为空:
判断点是否在矩形内部:
大多数情况下,还有一些简单的代码可以实现与这些函数相同的功能。例如,复制结构时,可以通过逐个字段的结构复制操作,来代替调用 CopyRect 函数,如下面的语句:
一种可接受的方式是设置一个向你的窗口函数发送 WM_TIMER 消息的 Windows 计时器。(我将在第 8 章介绍计时器。)对于每个 WM_TIMER 消息,可以调用 GetDC 函数获取设备环境,然后绘制一个随机矩形,接着调用 ReleaseDC 函数释放设备环境。但是那样做又会使程序失去一些趣味性,因为程序不能很快地绘制随机矩形。必须等待每个 WM_TIMER 消息,那样会依赖于系统时钟的精度。
在 Windows 中有很多的“空闲时间”,在这期间所有的消息队列都是空的,Windows 就在等待键盘或者鼠标的输入。那么能否在空闲期间从某种程度上获取控制并绘制随机矩形,而一旦有消息加载到程序的消息队列,就释放控制呢?这正是 PeekMessage 函数的“用武之地”。下面是 PeekMessage 函数调用的一个例子:
函数的前 4 个参数(一个是指向 MSG 结构的指针,一个是窗口句柄,另外两个值表示信息范围)与 GetMessage 函数相同。设置第二、三、四个参数为 NULL 或者 0,表示我们想使用 PeekMessage 函数返回程序中所有窗口的所有消息。如果要删除消息队列中的消息,可以把 PeekMessage 函数的最后一个参数设置为 PM_REMOVE。如果不想删除,就设置为 PM_NOREMOVE。这就是
PeekMessage 名字的意思,它是“偷看”而不是“获得”。它允许一个程序检查程序队列中的下一个消息,而不是真实地获得并删除它看到的消息。
GetMessage 函数并不把控制权交还给程序,除非它从程序的消息队列中获得了消息。但是 PeekMessage 函数却总是立即返回,不管消息是否出现。当一个消息在程序的消息队列中时,PeekMessage 函数的返回值是TRUE(非 0),而消息则像正常情况一样处理。当队列中没有消息时,PeekMessage
函数返回FALSE(0)。
这允许我们替换正常的消息循环,正常的消息循环如下所示:
注意:在这里,必须明确检查 WM_QUIT 消息,在一个正常的消息循环中,不需要这样做,因为当获取一个 WM_QUIT 消息时,GetMessage 函数的返回值是 FLASE(0)。但是 PeekMessage 函数的返回值是表示队列中是否有消息,因此检查 WM_QUIT 是必要的。
如果 PeekMessage 函数返回 TRUE,那么消息会正常执行。如果返回 FLASE,那么程序可以在返回给 Windows 控制之前做些事情(如显示另一个随机矩形)。
(尽管 Windows 文档中指出不能使用 PeekMessage 函数从消息队列中删除 WM_PAINT 消息,但是这并没有什么问题。毕竟,GetMessage 函数其实也不能从队列中删除 WM_PAINT 消息。使客户区的无效区域变成有效是从队列中删除 WM_PAINT 消息的唯一办法,可以使用 ValidateRect、ValidateRgn 或者成对的 BeginPaint
和 EndPaint 函数来完成。如果使用 PeekMessage 函数从消息队列获取 WM_PAINT 消息后,按照正常的方式对它进行处理,就不会又任何问题。但使用下面的代码来清除消息队列中的所有消息是不允许的:
PeekMessage 函数在早期版本的 Windows 中比在 Windows 98 中重要得多。这是因为 16 位版本的 Windows 使用非抢占式多任务系统。Windows 自带的 Terminal 程序使用 PeekMessage 函数循环检查从通信端口接收到的数据。打印机管理程序也使用这项技术来打印,其他的 Windows 打印程序通常也使用一个 PeekMessage 函数的循环。在抢占式多任务的 Windows 98 中,应用程序可以建立多个线程。
GDI 对象,应当通过调用 DeleteObject 函数来删除所有建立的区域。
当建立一个区域时,Windows 返回一个类型为 HRGN 的区域句柄。最简单的区域类型是一个矩形区域。可以用下面的两种办法建立一个矩形区域:
创建一个多边形区域的函数和 Polygon 函数类似:
那么你会问,区域有什么特别之处吗?下面的函数显示了区域的作用:
hDestRgn 在初始时表示一个很小的矩形区域。)
参数 iCombine 描述 hSrcRgn1 区域和 hSrcRgn2 区域结合的方式:
iRgnType 值是从 CombineRgn 返回的下列值之一:NULLREGION,指的是一个空的区域;SIMPLEREGION,指的是一个简单的矩形、椭圆或者多边形;COMPLEXREGION,指的是矩形、椭圆或多边形的组合;ERROR,指的是有错误发生。
一旦有了一个区域的句柄,就可以使用下面 4 个绘图函数:
用完一个区域后,可以用于删除其他 GDI 对象相同的函数来删除它:
5.6.4 矩形与区域的裁剪
区域在裁剪中也扮演着重要角色。InvalidRect 函数使显示的矩形区域无效,并产生一个 WM_PAINT 消息。例如,可以使用 InvalidateRect 函数来擦除客户区的内容,并产生一个 WM_PAINT 消息:
Windows 有两个类似 InvalidateRect 和 ValidateRect 的函数,用于处理区域而不是矩形;
可以通过将一个区域选入到设备环境来创建你自己的裁剪区域,将区域选入设备环境可以使用
GDI 为裁剪区域做了一个副本,因此当把区域对象选入到设备环境后,可以删除它。Windows 还包括几个操纵这个裁剪区域的函数,例如 ExcludeClipRect 函数用来从裁剪区域中去除一个矩形;IntersectClipRect 函数用来建立一个新的裁剪区域,这个新的裁剪区域是先前的裁剪区域和某个矩形的交集;OffsetClipRgn 函数用来把一个裁剪区域移动到客户区的另外一部分。
如果使用传统的方法绘制这个图形,必须依据椭圆的圆周角公式计算出每条线段的端点。但是通过使用一个复杂的裁剪区域,就可以直接绘制直线,而让 Windows 去确定这些端点。
因为区域总是使用设备坐标,所以 CLOVER 程序不得不在每次收到 WM_SIZE 消息时重新创建区域。几年前,运行 Windows 的机器要花费几秒钟来重绘这个图形。今天,快速的机器几乎在瞬间就能完成绘制。
CLOVER 先创建 4 个椭圆区域,它们被存储在 hRgnTemp 数组的前 4 个元素中。接着,程序创建三个 “空”区域:
相对结果而言,WM_PAINT 消息处理很简单。视口原点设置在客户区的中心(这样使画直线更容易),在处理 WM_SIZE 消息时创建的区域被选入设备环境作为裁剪区域:
现在,剩下要做的就是画直线了,一共画 360 条,每一度画一条。每条线的长度是变量 fRadius,它表示的是从中心到客户区角落的距离:
Windows 还有其他几个使用 RECT(矩形)结构和区域的绘图函数。一个区域指的是屏幕上的一块空间,它由矩形、多边形和椭圆组合而成。
5.6.1 处理矩形
下面三个绘图函数需呀一个指向矩形结构的指针:FillRect (hdc, &rect, hBrush); FrameRect (hdc, &rect, hBrush); InvertRect (hdc, &rect);在这三个函数中,参数 rect 是一个类型为 RECT 的结构,它有 4 个字段:left、top、right 和 bottom。在这个结构中,坐标是逻辑坐标。
FillRect 函数使用指定的画刷填充矩形(达到但不包括右下坐标)。这个函数不需要事先把画刷选入设备环境。
FrameRect 使用画刷绘制一个矩形框,但是它并不填充矩形。使用画刷来绘制边框似乎有点奇怪,因为到目前为止,你所看到的绘制边框的函数(例如 Rectangle)都是由当前画笔绘制的。FrameRect
函数允许你绘制一个不一定是纯色的矩形框。矩形的边框是 1 个逻辑单位宽。如果逻辑单位大于设备单位,边框的宽度将是 2 个或者更多的像素。
InvertRect 函数翻转矩形内所有的像素,将 1 变为 0,0 变为 1。即这个函数将白色区域变为黑色区域,黑色区域变为白色区域,绿色区域变为洋红色区域。
Windows 还包含 9 个可用于轻松便捷地操纵 RECT 结构的函数。例如,通常使用下面的代码将 RECT 结构的 4 个字段设置为特定值:
rect.left = xLeft; rect.top = yTop; rect.right = xRight; rect.bottom = yBottom;然而,通过 SetRect 函数的调用,只用一行代码即可实现相同的结果:
SetRect (&rect, xLeft, yTop, xRight, yBottom):
如果想做下列事情之一,可以方便的使用其他 8 个函数。
将矩形沿 x 轴和 y 轴移动几个单位:
OffsetRect (&rect, x, y);
增大或减小矩形的尺寸:
InflateRect (&rect, x, y);
把矩形结构的各字段设置为0:
SetRectEmpty (&rect);
将一个矩形结构复制到另一个矩形结构:
CopyRect (&DestRect, &SrcRect);
获取两个矩形的交集:
IntersectRect (&DestRect, &SrcRect1, &SrcRect2);
获取两个矩形的并集:
UnionRect (&DestRect, &SrcRect1, &SrcRect2);
判断矩形是否为空:
bEmpty = IsRectEmpty (&rect);
判断点是否在矩形内部:
bInRect = PtInRect(&rect, point);
大多数情况下,还有一些简单的代码可以实现与这些函数相同的功能。例如,复制结构时,可以通过逐个字段的结构复制操作,来代替调用 CopyRect 函数,如下面的语句:
DestRect = SrcRect;
5.6.2 随机矩形
在任何一个图形系统中,总存在这样一个有趣的程序,即简单地使用随机的尺寸和颜色不停地绘制一系列的图像,例如,随机大小和颜色的矩形。在 Windows 中可以创建这样的一个程序,但是这并不像想象的那样容易。我希望你能够意识到,不能在处理 WM_PAINT 消息中简单地使用 while(TRUE) 循环。当然,这样做会奏效,但是这样做的结果是,程序将停止对其他消息的处理,而且程序不能退出或者最小化。一种可接受的方式是设置一个向你的窗口函数发送 WM_TIMER 消息的 Windows 计时器。(我将在第 8 章介绍计时器。)对于每个 WM_TIMER 消息,可以调用 GetDC 函数获取设备环境,然后绘制一个随机矩形,接着调用 ReleaseDC 函数释放设备环境。但是那样做又会使程序失去一些趣味性,因为程序不能很快地绘制随机矩形。必须等待每个 WM_TIMER 消息,那样会依赖于系统时钟的精度。
在 Windows 中有很多的“空闲时间”,在这期间所有的消息队列都是空的,Windows 就在等待键盘或者鼠标的输入。那么能否在空闲期间从某种程度上获取控制并绘制随机矩形,而一旦有消息加载到程序的消息队列,就释放控制呢?这正是 PeekMessage 函数的“用武之地”。下面是 PeekMessage 函数调用的一个例子:
PeekMessage (&msg, NULL, 0, 0, PM_REMOVE);
函数的前 4 个参数(一个是指向 MSG 结构的指针,一个是窗口句柄,另外两个值表示信息范围)与 GetMessage 函数相同。设置第二、三、四个参数为 NULL 或者 0,表示我们想使用 PeekMessage 函数返回程序中所有窗口的所有消息。如果要删除消息队列中的消息,可以把 PeekMessage 函数的最后一个参数设置为 PM_REMOVE。如果不想删除,就设置为 PM_NOREMOVE。这就是
PeekMessage 名字的意思,它是“偷看”而不是“获得”。它允许一个程序检查程序队列中的下一个消息,而不是真实地获得并删除它看到的消息。
GetMessage 函数并不把控制权交还给程序,除非它从程序的消息队列中获得了消息。但是 PeekMessage 函数却总是立即返回,不管消息是否出现。当一个消息在程序的消息队列中时,PeekMessage 函数的返回值是TRUE(非 0),而消息则像正常情况一样处理。当队列中没有消息时,PeekMessage
函数返回FALSE(0)。
这允许我们替换正常的消息循环,正常的消息循环如下所示:
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam;替换后的消息循环如下:
while (TRUE) { if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage (&msg); DispatchMesage (&msg); } else { [other program lines to do some work] } } return msg.wParam;
注意:在这里,必须明确检查 WM_QUIT 消息,在一个正常的消息循环中,不需要这样做,因为当获取一个 WM_QUIT 消息时,GetMessage 函数的返回值是 FLASE(0)。但是 PeekMessage 函数的返回值是表示队列中是否有消息,因此检查 WM_QUIT 是必要的。
如果 PeekMessage 函数返回 TRUE,那么消息会正常执行。如果返回 FLASE,那么程序可以在返回给 Windows 控制之前做些事情(如显示另一个随机矩形)。
(尽管 Windows 文档中指出不能使用 PeekMessage 函数从消息队列中删除 WM_PAINT 消息,但是这并没有什么问题。毕竟,GetMessage 函数其实也不能从队列中删除 WM_PAINT 消息。使客户区的无效区域变成有效是从队列中删除 WM_PAINT 消息的唯一办法,可以使用 ValidateRect、ValidateRgn 或者成对的 BeginPaint
和 EndPaint 函数来完成。如果使用 PeekMessage 函数从消息队列获取 WM_PAINT 消息后,按照正常的方式对它进行处理,就不会又任何问题。但使用下面的代码来清除消息队列中的所有消息是不允许的:
while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE));这条语句表示从你的消息队列中删除 WM_PAINT 消息之外的所有消息。如果 WM_PAINT 在队列中,你将永远陷于 while 循环无法终止。)
PeekMessage 函数在早期版本的 Windows 中比在 Windows 98 中重要得多。这是因为 16 位版本的 Windows 使用非抢占式多任务系统。Windows 自带的 Terminal 程序使用 PeekMessage 函数循环检查从通信端口接收到的数据。打印机管理程序也使用这项技术来打印,其他的 Windows 打印程序通常也使用一个 PeekMessage 函数的循环。在抢占式多任务的 Windows 98 中,应用程序可以建立多个线程。
5.6.3 建立和绘制区域
一个区域是对显示器一块空间的描述,这个空间可以是矩形、多边形和椭圆的组合。可以使用区域进行绘图或者裁剪。将区域选入设备环境,就可以使用这个区域来裁剪(也就是说,将绘制动作限制在客户区的一个特定部分)。同画笔和画刷一样,区域也就是GDI 对象,应当通过调用 DeleteObject 函数来删除所有建立的区域。
当建立一个区域时,Windows 返回一个类型为 HRGN 的区域句柄。最简单的区域类型是一个矩形区域。可以用下面的两种办法建立一个矩形区域:
hRgn = CreateRectRgn (xLeft, yTop, xRight, yBottom);或者
hRgn = CreateRectRgnIndirect (&rect);也可以使用下面的函数建立椭圆区域:
hRgn = CreateEllipticRgn (xLeft, yTop, xRight, yBottom);或者
hRgn = CreateEllipticRgnIndirect (&rect);创建圆角矩形区域可以通过 CreateRoundRectRgn 函数实现。
创建一个多边形区域的函数和 Polygon 函数类似:
hRgn = CreatePolygonRgn (&point, iCount, iPolyFillMode);参数 point 是一个类型为 POINT 结构的数组, iCount 是点的个数,iPolyFillMode 或者是 ALTERNATE,或者是 WINDING。你也可以调用 CreatePolygonRgn 函数创建多个多边形区域。
那么你会问,区域有什么特别之处吗?下面的函数显示了区域的作用:
iRgnType = CombineRgn (hDestRgn, hSrcRgn1, hSrcRgn2, iCombine);这个函数将两个源区域(hSrcRgn1 和 hSrcRgn2)结合起来,并产生目标区域句柄(hDestRgn) 来表示那个组合区域。这三个区域句柄都必须有效,但是函数调用后 hDestRgn 先前描述的区域都被销毁了。(当使用这个函数时,可能要让
hDestRgn 在初始时表示一个很小的矩形区域。)
参数 iCombine 描述 hSrcRgn1 区域和 hSrcRgn2 区域结合的方式:
iCombine 值 | 新的区域 |
---|---|
RGN_AND | 两个源区域的公共部分 |
RGN_OR | 两个源区域的全部 |
RGN_XOR | 两个源区域的全部,但除去公共部分 |
RGN_DIFF | hSrcRgn1 不在 hSrcRgn2 中的部分 |
RGN_COPY | hSrcRgn1 的全部(忽略 hSrcRgn2) |
一旦有了一个区域的句柄,就可以使用下面 4 个绘图函数:
FillRgn (hdc, hRgn, hBrush); FrameRgn (hdc, hRgn, hBrush, xFrame, yFrame); InvertRgn (hdc, hRgn); PaintRgn (hdc, hRgn);FillRgn、FrameRgn 和 InvertRgn 函数类似于 FillRect、FrameRect 和 InvertRect 函数。FrameRgn 的参数 xFrame 和 yFrame 是表示在区域周围的、要绘制的边框的逻辑宽度和高度。PaintRgn 函数使用当前被选入设备环境的画刷来填充区域。所有的这些函数都假定使用的是逻辑坐标。
用完一个区域后,可以用于删除其他 GDI 对象相同的函数来删除它:
DeleteObject (hRgn);
5.6.4 矩形与区域的裁剪
区域在裁剪中也扮演着重要角色。InvalidRect 函数使显示的矩形区域无效,并产生一个 WM_PAINT 消息。例如,可以使用 InvalidateRect 函数来擦除客户区的内容,并产生一个 WM_PAINT 消息:InvalidateRect (hwnd, NULL, TRUE);可以通过调用 GetUpdateRect 函数获取无效矩形的坐标,并且使用 ValidateRect 使客户区的矩形有效。当接收到一个 WM_PAINT 消息时,PAINTSTRUCT 结构中的无效矩形的坐标是可以利用的。这个结构是通过 BeginPaint 函数填充的。这个无效矩形也定义了一个“裁剪区域”。不能在裁剪区域之外绘图。
Windows 有两个类似 InvalidateRect 和 ValidateRect 的函数,用于处理区域而不是矩形;
InvalidateRgn (hwnd, hRgn, bErase);和
ValidateRgn (hwnd, hRgn);当接收一条由无效区域产生的 WM_PAINT 消息时,裁剪区域在形状不一定是矩形。
可以通过将一个区域选入到设备环境来创建你自己的裁剪区域,将区域选入设备环境可以使用
SelectObject (hdc, hRgn);或
SelectClipRgn (hdc, hRgn);裁剪区域被假定使用的是设备坐标。
GDI 为裁剪区域做了一个副本,因此当把区域对象选入到设备环境后,可以删除它。Windows 还包括几个操纵这个裁剪区域的函数,例如 ExcludeClipRect 函数用来从裁剪区域中去除一个矩形;IntersectClipRect 函数用来建立一个新的裁剪区域,这个新的裁剪区域是先前的裁剪区域和某个矩形的交集;OffsetClipRgn 函数用来把一个裁剪区域移动到客户区的另外一部分。
5.6.5 CLOVER 程序
CLOVER 程序由四个椭圆形成一个区域,然后把这个区域选入设备环境,接着从窗口区中心发散绘制一系列直线。这些直线仅出现剪裁区域内。如果使用传统的方法绘制这个图形,必须依据椭圆的圆周角公式计算出每条线段的端点。但是通过使用一个复杂的裁剪区域,就可以直接绘制直线,而让 Windows 去确定这些端点。
/*------------------------------------------------------------
CLOVER.C -- Clover Drawing Program Using Regions
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#include <math.h>
#define TWO_PI (2.0 * 3.14159)
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Clover");
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("Draw a Clover"),
WS_OVERLAPPEDWINDOW,
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 HRGN hRgnClip;
static int cxClient, cyClient;
double fAngle, fRadius;
HCURSOR hCursor;
HDC hdc;
HRGN hRgnTemp[6];
int i;
PAINTSTRUCT ps;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
ShowCursor(TRUE);
if (hRgnClip)
DeleteObject(hRgnClip);
hRgnTemp[0] = CreateEllipticRgn(0, cyClient / 3,
cxClient / 2, 2 * cyClient / 3);
hRgnTemp[1] = CreateEllipticRgn(cxClient / 2, cyClient / 3,
cxClient, 2 * cyClient / 3);
hRgnTemp[2] = CreateEllipticRgn(cxClient / 3, 0,
2 * cxClient / 3, cyClient / 2);
hRgnTemp[3] = CreateEllipticRgn(cxClient / 3, cyClient / 2,
2 * cxClient / 3, cyClient);
hRgnTemp[4] = CreateRectRgn(0, 0, 1, 1); hRgnTemp[5] = CreateRectRgn(0, 0, 1, 1); hRgnClip = CreateRectRgn(0, 0, 1, 1);
CombineRgn(hRgnTemp[4], hRgnTemp[0], hRgnTemp[1], RGN_OR);
CombineRgn(hRgnTemp[5], hRgnTemp[2], hRgnTemp[3], RGN_OR);
CombineRgn(hRgnClip, hRgnTemp[4], hRgnTemp[5], RGN_XOR);
for (i = 0; i < 6; ++ i) DeleteObject(hRgnTemp[i]);
SetCursor(hCursor);
ShowCursor(FALSE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL); SelectClipRgn(hdc, hRgnClip);
fRadius = hypot(cxClient / 2.0, cyClient / 2.0); for (fAngle = 0.0; fAngle < TWO_PI; fAngle += TWO_PI / 360) { MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, (int) ( fRadius * cos (fAngle) + 0.5), (int) (-fRadius * sin (fAngle) + 0.5)); }
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
DeleteObject(hRgnClip);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
因为区域总是使用设备坐标,所以 CLOVER 程序不得不在每次收到 WM_SIZE 消息时重新创建区域。几年前,运行 Windows 的机器要花费几秒钟来重绘这个图形。今天,快速的机器几乎在瞬间就能完成绘制。
CLOVER 先创建 4 个椭圆区域,它们被存储在 hRgnTemp 数组的前 4 个元素中。接着,程序创建三个 “空”区域:
hRgnTemp[4] = CreateRectRgn(0, 0, 1, 1); hRgnTemp[5] = CreateRectRgn(0, 0, 1, 1); hRgnClip = CreateRectRgn(0, 0, 1, 1);在客户区左边和右边的两个区域先合并:
CombineRgn(hRgnTemp[4], hRgnTemp[0], hRgnTemp[1], RGN_OR);同样地,在客户区顶部的两个椭圆区域也合并了:
CombineRgn(hRgnTemp[5], hRgnTemp[2], hRgnTemp[3], RGN_OR);最后两个合并后的区域再合并成 hRgnClip:
CombineRgn(hRgnClip, hRgnTemp[4], hRgnTemp[5], RGN_XOR);RGN_XOR 标识符表示要从结果区域中排除重叠的区域。最后,6 个临时的区域被删除:
for (i = 0; i < 6; ++ i) DeleteObject(hRgnTemp[i]);
相对结果而言,WM_PAINT 消息处理很简单。视口原点设置在客户区的中心(这样使画直线更容易),在处理 WM_SIZE 消息时创建的区域被选入设备环境作为裁剪区域:
SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL); SelectClipRgn(hdc, hRgnClip);
现在,剩下要做的就是画直线了,一共画 360 条,每一度画一条。每条线的长度是变量 fRadius,它表示的是从中心到客户区角落的距离:
fRadius = hypot(cxClient / 2.0, cyClient / 2.0); for (fAngle = 0.0; fAngle < TWO_PI; fAngle += TWO_PI / 360) { MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, (int) ( fRadius * cos (fAngle) + 0.5), (int) (-fRadius * sin (fAngle) + 0.5)); }在处理 WM_DESTROY 消息期间,裁剪区域被删除:
DeleteObject(hRgnClip);
相关文章推荐
- GMF学习历程(2)
- iOS实现一个颜色渐变的弧形进度条
- LeetCode50——Pow(x,n)
- LeetCode 075 Sort Colors
- buffer busy waits
- 关于GPL协议的理解(开源与商用、免费与收费的理解)
- 第五章、供应商财务评估
- 毛玻璃
- grub启动流程及原理
- AFNetworking 3.0迁移指南
- java 移位运算符
- [工具设置]xp iis连接数破解
- P6SPY(JDBC SQL拦截)
- vs2010无法查看自定义的普通变量(CXX0017:错误:没有找到符号)
- 通过GeoIP获取ip所属地 (国家,城市,时区,邮编,经纬度等)
- C++ 11 强制类型转换 北京大学程序实习公开课
- HDU 2612 Find a way题解
- 小机更改系统时间
- 资深工程师教你如何在window下快速创建上百个G的打文件!
- AndroidVideoPlayer在线播放视频