windows程序设计第五章 图形基础

来源:互联网 发布:斩魂关服 知乎 编辑:程序博客网 时间:2024/05/02 02:33

GDI 的结构

GDI由几百个函数呼叫和一些相关的数据型态、宏和结构组成。

GDI原理

Windows 98和Microsoft Windows NT中的图形主要由GDI32.DLL动态链接库输出的函数来处理。在Windows 98中,这个GDI32.DLL实际是利用16位GDI.EXE动态链接库来执行许多函数。在Windows NT中,GDI.EXE只用于16位的程序。

这些动态链接库呼叫您安装的视讯显示器和任何打印机呼叫驱动程序中的例程。视讯驱动程序存取视讯显示器的硬件,打印机驱动程序将GDI命令转换为各种打印机能够理解的代码或者命令。

GDI的主要目的之一是支持与设备无关的图形。Windows程序应该能够毫无困难地在Windows支持的任意一种图形输出设备上执行,GDI通过将您的程序和不同输出设备的特性隔离开来的方法来达到这一目的。

图形输出设备分为两大类:位映像设备和向量设备。大多数PC的输出设备是位映像设备,这意味着它们以图点构成的数组来表示图像,这类设备包括视讯显示卡、点阵打印机和激光打印机。向量设备使用线来绘制图像,通常局限于绘图机。

正如C有时被认为是一种「高级汇编语言」一样,您可以认为GDI是图形设备硬件之间的一种高阶界面。

GDI函数呼叫

设备内容中目前选中的画笔就是设备内容的一个属性。这样,您画任何线都使用这个画笔,然后,您可以取消设备内容中的画笔选择,并清除画笔对象。清除画笔对象是必要的,因为画笔定义占用了分配的内存空间。除了画笔以外,GDI对象还用于建立填入封闭区域的画刷、字体、位图以及GDI的其它一些方面。

GDI基本图形

线条用设备内容中选中的目前画笔绘制。

填入区域当一系列直线或者曲线封闭了一个区域时,该区域可以使用目前GDI画刷对象进行填图。

位图位图是位的矩形数组,这些位对应于显示设备上的像素,它们是位映像图形的基础工具。

GDI支持两种型态的位图-旧式的(虽然还非常有用)「设备相关」位图,是GDI对象;和新的(如Windows 3.0的)「设备无关」位图(或者DIB),可以储存在磁盘文件中。

从Windows 3.1开始,GDI开始支持TrueType字体,该字体是在填入轮廓线基础上建立的,这样的填入轮廓线可由其它GDI函数处理。依据兼容性和储存大小的考虑,Windows 98继续支持旧式的点阵字体。

其它部分

Metafile主要用于通过剪贴板传输向量图形。

绘图区域用于绘制轮廓、填入图形和剪裁。

路径路径是GDI内部储存的直线和曲线的集合。路径可以用于绘图、填入图形和剪裁,还可以转换为绘图区域。

剪裁绘图可以限制在显示区域的某一部分中,这就是所谓的剪裁。剪裁区域是不是矩形都可以,剪裁通常是通过区域或者路径来定义的。

 

设备内容

当您想在一个图形输出设备(诸如屏幕或者打印机)上绘图时,您首先必须获得一个设备内容(或者DC)的句柄。将句柄传回给程序时,Windows就给了您使用设备的权限。然后您在GDI函数中将这个句柄作为一个参数,向Windows标识您想在其上进行绘图的设备。

设备内容中包含许多确定GDI函数如何在设备上工作的目前「属性」,这些属性允许传递给GDI函数的参数只包含起始坐标或者尺寸信息,而不必包含Windows在设备上显示对象时需要的所有其它信息。例如,呼叫TextOut时,您只需要在函数中给出设备内容句柄、起始坐标、文字和文字的长度。您不必指定字体、文字颜色、文字后面的背景色彩以及字符间距,因为这些属性都是设备内容的一部分。当您想改变这些属性之一时,您呼叫一个可以改变设备内容中属性的函数,以后针对该设备内容的TextOut呼叫来使用改变后的属性。

取得设备内容句柄

除了第四章提到的:在处理WM_PAINT消息时,使用hdc = BeginPaint (hwnd, &ps)和EndPaint(hwnd, &ps)呼叫,在处理非WM_PAINT消息时使用hdc = GetDC (hwnd)和ReleaseDC (hwnd, hdc)。这些呼叫与BeginPaint和EndPaint的组合之间的基本区别是,利用从GetDC传回的句柄可以在整个显示区域上绘图。当然, GetDC和ReleaseDC不使显示区域中任何可能的无效区域变成有效。以及取得适用于整个窗口(而不仅限于窗口的显示区域)的设备内容句柄:hdc = GetWindowDC (hwnd)和ReleaseDC (hwnd,hdc);这个设备内容除了显示区域之外,还包括窗口的标题栏、菜单、滚动条和框架(frame)。GetWindowDC函数很少使用,如果想尝试用一用它,则必须拦截处理WM_NCPAINT消息,Windows使用该消息在窗口的非显示区域上绘图。

取得设备内容句柄的另一个更通用的函数是CreateDC


hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData) ;//其它行程序DeleteDC (hdc) ;

例如,您可以通过下面的呼叫来取得整个屏幕的设备内容句柄:

hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;

有时您只是需要取得关于某设备内容的一些信息而并不进行任何绘画,在这种情况下,您可以使用CreateIC来取得一个「信息内容」的句柄,其参数与CreateDC函数相同,例如:

hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;//您不能用这个信息内容句柄往设备上写东西。


使用位图时,取得一个「内存设备内容」有时是有用的:

hdcMem = CreateCompatibleDC (hdc) ;//其它行程序DeleteDC (hdcMem) ;


您可以将位图选进内存设备内容,然后使用GDI函数在位图上绘画。

前面已经提到过,metafile是一些GDI呼叫的集合,以二进制形式编码。您可以通过取得metafile设备内容来建立metafile:

hdcMeta = CreateMetaFile (pszFilename) ;//其它行程序hmf = CloseMetaFile (hdcMeta) ;


在metafile设备内容有效期间,任何用hdcMeta所做的GDI呼叫都变成metafile的一部分而不会显示。在呼叫CloseMetaFile之后,设备内容句柄变为无效,函数传回一个指向metafile(hmf)的句柄。

取得设备内容信息

一个设备内容通常是指一个实际显示设备,如视讯显示器和打印机。通常,您需要取得有关该设备的信息,包括显示器的大小(单位为像素或者实际长度单位)和色彩显示能力。您可以通过呼叫GetDeviceCaps(「取得设备功能」)函数来取得这些信息:

iValue = GetDeviceCaps (hdc, iIndex) ;


iIndex为VERTRES时将让GetDeviceCaps传回设备的高度(单位为像素)。如果hdc是打印机设备内容的句柄,则GetDeviceCaps传回打印机显示区域的高度和宽度,它们也是以像素为单位的。


//DEVCAPS.cpp#include <windows.h>#define NUMLINES ((int) (sizeof devcaps / sizeof devcaps [0]))struct{int iIndex ;TCHAR *szLabel ;TCHAR *szDesc ;}devcaps [] ={HORZSIZE, TEXT ("HORZSIZE"),TEXT ("Width in millimeters:"),VERTSIZE, TEXT ("VERTSIZE"),TEXT ("Height in millimeters:"),HORZRES, TEXT ("HORZRES"), TEXT ("Width in pixels:"),VERTRES, TEXT ("VERTRES"), TEXT ("Height in raster lines:"),BITSPIXEL, TEXT ("BITSPIXEL"),TEXT ("Color bits per pixel:"),PLANES, TEXT ("PLANES"), TEXT ("Number of color planes:"),NUMBRUSHES, TEXT ("NUMBRUSHES"), TEXT ("Number of device brushes:"),NUMPENS, TEXT ("NUMPENS"), TEXT ("Number of device pens:"),NUMMARKERS, TEXT ("NUMMARKERS"), TEXT ("Number of device markers:"),NUMFONTS, TEXT ("NUMFONTS"), TEXT ("Number of device fonts:"),NUMCOLORS, TEXT ("NUMCOLORS"), TEXT ("Number of device colors:"),PDEVICESIZE, TEXT ("PDEVICESIZE"),TEXT ("Size of device structure:"),ASPECTX, TEXT ("ASPECTX"), TEXT ("Relative width of pixel:"),ASPECTY, TEXT ("ASPECTY"), TEXT ("Relative height of pixel:"),ASPECTXY, TEXT ("ASPECTXY"), TEXT ("Relative diagonal of pixel:"),LOGPIXELSX, TEXT ("LOGPIXELSX"), TEXT ("Horizontal dots per inch:"),LOGPIXELSY, TEXT ("LOGPIXELSY"), TEXT ("Vertical dots per inch:"),SIZEPALETTE, TEXT ("SIZEPALETTE"),TEXT ("Number of palette entries:"),NUMRESERVED, TEXT ("NUMRESERVED"),TEXT ("Reserved palette entries:"),COLORRES, TEXT ("COLORRES"), TEXT ("Actual color resolution:")} ;LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,   PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT("DevCaps1");HWND hwnd;MSG msg;/*typedef struct tagMSG{ HWND hwnd ;      //hwnd 接收消息的窗口句柄。UINT message ;   //message 消息标识符。这是一个数值,用以标识消息。WPARAM wParam ;  //wParam 一个32位的「message parameter(消息参数)」,其含义和数值根据消息的不同而不同。LPARAM lParam ;  //lParam 一个32位的消息参数,其值与消息有关。  DWORD time ;     //time 消息放入消息队列中的时间。POINT pt ;       //pt 消息放入消息队列时的鼠标坐标。}MSG, * PMSG ;*/WNDCLASS wndclass;wndclass.cbClsExtra=0;wndclass.cbWndExtra=0;wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.hCursor=LoadCursor(NULL, IDC_ARROW);wndclass.hIcon=LoadIcon(NULL, IDI_APPLICATION);wndclass.hInstance=hInstance;wndclass.lpfnWndProc=WndProc;wndclass.lpszClassName=szAppName;wndclass.lpszMenuName=NULL;wndclass.style=CS_HREDRAW | CS_VREDRAW;if (!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("This program requires Window 7!"),szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName, // window class nameTEXT("Device Capabilities"),// window captionWS_OVERLAPPEDWINDOW,// window styleCW_USEDEFAULT, // initial x positionCW_USEDEFAULT, // initial y positionCW_USEDEFAULT, // initial x sizeCW_USEDEFAULT, // initial y sizeNULL, // parent window handleNULL, // window menu handlehInstance, // program instance handleNULL);// creation parameters「建立参数」指标设定为NULL,可以用这个参数存取稍后程序中可能引用到的数据。ShowWindow(hwnd, iCmdShow);UpdateWindow(hwnd);/*呼叫UpdateWindow之后,窗口就出现在视讯显示器上。程序现在必须准备读入使用者用键盘和鼠标输入的数据。Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」并将消息放入程序的消息队列中。程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息:*/while (GetMessage(&msg, NULL, 0, 0))//GetMessage第二、第三和第四个参数设定为NULL或者0,表示程序接收它自己建立的所有窗口的所有消息{TranslateMessage(&msg);//将msg结构传给Windows,进行一些键盘转换。DispatchMessage(&msg);//又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。}return msg.wParam;  //传递给PostQuitMessage函数的值(通常是0)。}LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static int cxChar, cyChar, cxCaps;TCHAR szBuffer[10];HDC hdc;int i;PAINTSTRUCT ps;TEXTMETRIC tm;switch(message){case WM_CREATE:hdc = GetDC(hwnd);GetTextMetrics(hdc, &tm);cxChar = tm.tmAveCharWidth;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;cyChar = tm.tmHeight + tm.tmExternalLeading;ReleaseDC(hwnd, hdc);return 0;case WM_PAINT:hdc = BeginPaint(hwnd, &ps);for (i = 0; i < NUMLINES; i++){TextOut(hdc, 0, cyChar * i, devcaps[i].szLabel,lstrlen(devcaps[i].szLabel));TextOut(hdc, 14 * cxCaps, cyChar * i, devcaps[i].szDesc,lstrlen(devcaps[i].szDesc));SetTextAlign(hdc, TA_RIGHT | TA_TOP);TextOut(hdc, 14*cxCaps + 35*cxChar, cyChar * i, szBuffer,wsprintf(szBuffer, TEXT("%5d"),GetDeviceCaps(hdc, devcaps[i].iIndex)));SetTextAlign(hdc, TA_LEFT | TA_TOP);}EndPaint(hwnd, &ps);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);}


设备的大小

用「分辨率」来严格定义每度量单位(一般为英寸)内的像素数。我使用「像素大小」或「像素尺寸」表示设备水平或垂直显示的总像素数。「度量大小」或「度量尺寸」是以英寸或毫米为单位的设备显示区域的大小。(对于打印机页面,它不是整个页面,只是可打印的区域。)像素大小除以度量大小就得到分辨率。

如果设备的水平分辨率与垂直分辨率相等,就称设备具有「正方形像素」。

Windows应用程序可以使用SM_CXSCREEN和SM_CYSCREEN参数从GetSystemMetrics得到像素尺寸。从DEVCAPS1程序中您会注意到,程序可以用HORZRES(水平分辨率)和VERTRES参数从GetDeviceCaps中得到同样的值。

在传统的排版中,字体的字母大小由「点」表示。1点大约1/72英寸,在计算机排版中1点正好为1/72英寸。

在真正的排版中,字体的点值与字体字母的实际大小并不正好相等。

Windows系统字体-不考虑是大字体还是小字体,也不考虑所选择的视频像素大小-固定假设为10点字体和12点行距。

在Windows程序中,您可以使用GetDeviceCaps函数取得使用者在「控制台」的「显示器」程序中选择的以每英寸的点数为单位的假定分辨率。要得到这些值(如果视讯显示器不具有正方形像素,在理论上这些值是不同的),可以使用索引LOGPIXELSX和LOGPIXELSY。LOGPIXELS指逻辑像素,它的基本意思是「以每英寸的像素数为单位的非实际分辨率」。



常数25.4用于把英寸转变为毫米。

在Windows NT中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的像素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows 98下的HORZSIZE和VERTSIZE值。

来自GetDeviceCaps的另三个值与视讯大小有关。ASPECTX、ASPECTY和ASPECTXY值是每一个像素的相对宽度、高度和对角线大小,四舍五入到整数。对于正方形像素,ASPECTX和ASPECTY值相同。无论如何,ASPECTXY值应等于ASPECTX与ASPECTY平方和的平方根,就像直角三角形一样。

关于色彩

视讯显示卡可以同时显示的不同色彩的数目等于2的位数次方。

祇有在某些怪异的程序中才需要知道视讯显示卡上的内存是如何组织的,但是GetDeviceCaps使程序写作者可以知道显示卡的储存组织以及它能够表示的色彩数目,下面的呼叫传回色彩平面的数目:

iPlanes = GetDeviceCaps (hdc, PLANES);


下面的呼叫传回每个像素的色彩位数:

iBitsPixel = GetDeviceCaps (hdc, BITSPIXEL);


大多数彩色图形显示设备使用多个色彩平面或每像素有多个色彩位的设计,但是不能同时一齐使用这两种方式;换句话说,这两个呼叫必有一个传回1。显示卡能够表示的色彩数可以用如下公式来计算:

iColors = 1 << (iPlanes * iBitsPixel);


这个值与用NUMCOLORS参数得到的色彩数值可能一样,也可能不一样:

iColors = GetDeviceCaps (hdc, NUMCOLORS);


256色的显示卡使用色彩调色盘。在那种情况下,以NUMCOLORS为参数时,GetDeviceCaps传回由Windows保留的色彩数,值为20,剩余的236种颜色可以由Windows程序用调色盘管理器设定。对于High-Color和True-Color显示分辨率,带有NUMCOLORS参数的GetDeviceCaps通常传回-1,这样就无法得到需要的信息,因此应该使用前面所示的带有PLANES和BITSPIXEL值的iColors公式。

在大多数GDI函数呼叫中,使用COLORREF值(只是一个32位的无正负号长整数)来表示一种色彩。COLORREF值按照红、绿和蓝色的亮度指定了一种颜色,通常叫做「RGB色彩」 。32位的COLORREF值的设定如下图所示。



Windows表头文件WINGDI.H提供了几种使用RGB色彩值的宏。RGB宏要求三个参数分别代表红、绿和蓝值,然后将它们组合为一个无正负号长整数:

#define RGB(r,g,b) ((COLORREF)(((BYTE)(r) | \((WORD)((BYTE)(g)) << 8)) | \(((DWORD)(BYTE)(b)) << 16)))


注意三个参数的顺序是红、绿和蓝。

GetRValue、GetGValue和GetBValue宏从COLORREF值中抽取出原色值。当您在使用传回RGB色彩值的Windows函数时,这些宏有时会很方便。

在16色或256色显示卡上,Windows可以使用「混色」来模拟设备能够显示的颜色之外的色彩。混色利用了由多种色彩的像素组成的像素图案。可以呼叫GetNearestColor来决定与某一色彩最接近的纯色:

crPureColor = GetNearestColor (hdc, crColor) ;


保存设备内容

在释放设备内容之后,仍然保存程序中对设备内容属性所做的改变,以便在下一次呼叫GetDC和BeginPaint时它们仍然能够起作用。为此,可在登录窗口类别时,将CS_OWNDC旗标纳入窗口类别的一部分:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;


现在,依据这个窗口类别所建立的每个窗口都将拥有自己的设备内容,它一直存在,直到窗口被删除。如果使用了CS_OWNDC风格,就只需初始化设备内容一次,可以在处理WM_CREATE消息处理期间完成这一操作:

case WM_CREATE:hdc = GetDC (hwnd) ;//初始化设备内容属性ReleaseDC (hwnd, hdc) ;


这些属性在改变之前一直有效。

CS_OWNDC风格只影响GetDC和BeginPaint获得的设备内容,不影响其它函数(如GetWindowDC)获得的设备内容。以前不提倡使用CS_OWNDC风格,因为它需要内存;现在,在处理大量图形的Windows NT应用程序中,它可以提高性能。即使用了CS_OWNDC,您仍然应该在退出窗口消息处理程序之前释放设备内容。

某些情况下,您可能想改变某些设备内容属性,用改变后的属性进行绘图,然后恢复原来的设备内容。要简化这一过程,可以通过如下呼叫来保存设备内容的状态:

idSaved = SaveDC (hdc) ;

现在,可以改变一些属性,在想要回到呼叫SaveDC前存在的设备内容时,呼叫:

RestoreDC (hdc, idSaved) ;

您可以在呼叫RestoreDC之前呼叫SaveDC数次。

更像汇编语言中的PUSH和POP指令,当您呼叫SaveDC时,不需要保存传回值:

SaveDC (hdc) ;

然后,您可以更改某些属性并再次呼叫SaveDC。要将设备内容恢复到一个已经保存的状态,

呼叫:

RestoreDC (hdc, -1) ;

这就将设备内容恢复到最近由SaveDC函数保存的状态中。

画点和线

设定像素

SetPixel函数在指定的x和y坐标以特定的颜色设定像素:

SetPixel (hdc, x, y, crColor) ;

如同在任何绘图函数中一样,第一个参数是设备内容的句柄。第二个和第三个参数指明了坐标位置。通常要获得窗口显示区域的设备内容,并且x和y相对于该显示区域的左上角。最后一个参数是COLORREF型态指定了颜色。如果在函数中指定的颜色视讯显示器不支持,则函数将像素设定为最接近的纯色并从函数传回该值。

GetPixel函数传回指定坐标处的像素颜色:

crColor = GetPixel (hdc, x, y) ;

直线

7个画线函数是:

LineTo画直线。

Polyline和PolylineTo画一系列相连的直线。

PolyPolyline画多组相连的线。

Arc画椭圆线。

PolyBezier和PolyBezierTo画贝塞尔曲线。

ArcTo和AngleArc画椭圆线。

PolyDraw画一系列相连的线以及贝塞尔曲线。

一些既画线也填入所画图形的封闭区域的函数,这些函数是:

Rectangle画矩形。

Ellipse画椭圆。

RoundRect画带圆角的矩形。

Pie画椭圆的一部分,使其看起来像一个扇形。

Chord画椭圆的一部分,以呈弓形。

设备内容的五个属性影响着用这些函数所画线的外观:目前画笔的位置(仅用于LineTo、PolylineTo、PolyBezierTo和ArcTo )、画笔、背景方式、背景色和绘图模式。

画一条直线,必须呼叫两个函数。第一个函数指定了线的开始点,第二个函数指定了线的终点:

MoveToEx (hdc, xBeg, yBeg, NULL) ;

LineTo (hdc, xEnd, yEnd) ;

MoveToEx实际上不会画线,它只是设定了设备内容的「目前位置」属性。然后LineTo函数从目前的位置到它所指定的点画一条直线。目前位置只是用于其它几个GDI函数的开始点。在内定的设备内容中,目前位置最初设定在点(0,0)。如果在呼叫LineTo之前没有设定目前位置,那么它将从显示区域的左上角开始画线。

MoveToEx的最后一个参数是指向POINT结构的指针。从该函数传回后,POINT结构的x和y字段指出了先前的目前位置。如果您不需要这种信息(通常如此),可以简单地如上面的例子所示的那样将最后一个参数设定为NULL。

如果您需要目前位置,就可以通过以下呼叫获得:

GetCurrentPositionEx (hdc, &pt) ;

其中,pt是POINT结构的。

下面的程序代码从窗口的左上角开始,在显示区域中画一个网格,线与线之间相隔100个像素,其中hwnd是窗口句柄,hdc是设备内容句柄,而x和y是整数:

GetClientRect (hwnd, &rect) ;for ( x = 0 ; x < rect.right ; x+= 100){MoveToEx (hdc, x, 0, NULL) ;LineTo (hdc, x, rect.bottom);}for (y = 0 ; y <rect.bottom ; y += 100){MoveToEx (hdc, 0, y, NULL) ;LineTo (hdc, rect.right, y) ;}


在希望画一组相连的直线时,目前画笔位置属性又会变得很有用。定义一个包含5个点(10个值)的数组,来画一个矩形的边界框:

POINT apt[5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100,100 } ;

注意,最后一个点与第一个点相同。现在,只需要使用MoveToEx移到第一个点,并对后面的点使用LineTo:

MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;

for ( i = 1 ; i < 5 ; i++)

LineTo (hdc, apt[i].x, apt[i].y) ;

由于LineTo从目前位置画到(但不包括)LineTo函数中给出的点,所以这段程序代码没有在任何坐标处画两次。虽然在显示器上多输出几次不存在问题,但是在绘图机上或者在其它绘图方式(下面马上会讲到)下,视觉效果就不太好了。

这条叙述画出与上面一段程序代码相同的矩形:

Polyline (hdc, apt, 5) ;

Polyline既不使用也不改变目前位置。PolylineTo有些不同,这个函数使用目前位置作为开始点,并将目前位置设定为最后一根线的终点。下面的程序代码画出与上面所示一样的矩形:

MoveToEx (hdc, apt[0].x,apt[0].y, NULL) ;

PolylineTo (hdc, apt + 1, 4) ;

画正弦波就是这样的:

#include <windows.h>#include <math.h>#define NUM 1000#define TWOPI (2 * 3.14159)LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT ("SineWave") ;HWND hwnd ;MSG msg ;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 ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow ( szAppName, TEXT ("Sine Wave Using Polyline"),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 int cxClient, cyClient ;HDC hdc ;int i ;PAINTSTRUCT ps ;POINT apt [NUM] ;switch (message){case WM_SIZE:cxClient = LOWORD (lParam) ;cyClient = HIWORD (lParam) ;return 0 ;case WM_PAINT:hdc = BeginPaint (hwnd, &ps) ;MoveToEx (hdc, 0, cyClient / 2, NULL) ;LineTo (hdc, cxClient, cyClient / 2) ;for (i = 0 ; i < NUM ; i++){apt[i].x = i * cxClient / NUM ;apt[i].y = (int) (cyClient / 2 * (1 - sin (TWOPI * i / NUM))) ;}Polyline (hdc, apt, NUM) ;return 0 ;case WM_DESTROY:PostQuitMessage (0) ;return 0 ;}return DefWindowProc (hwnd, message, wParam, lParam) ;}


边界框函数

Rectangle、Ellipse、RoundRect、Chord和Pie函数严格来说不是画线函数。没错,这些函数是在画线,但它们同时又填入画刷填入一个封闭区域。这个画刷内定为白色,因此当您第一次使用这些函数时,您可能不会注意到它们不只是画线。它们都是依据一个矩形边界框来绘图的。

这些函数中最简单的就是画一个矩形:

Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;



有些图形系统画出的图形包含右坐标和底坐标,而有些则只画到(而不包含)右坐标和底坐标。Windows采用后一种方法,不过有一种更简单的方法来思考这个问题。

Rectangle (hdc, 1, 1, 5, 4) ;

上面我们提到,Windows在边界框内画图。可以将显示器想象成一个网格,其中,每个像素都在一个网格单元内。边界框画在网格上,然后在边界框内画矩形,下面说明了图形画出来时的样子:



Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;

用Ellipse函数画出的图形下图所示(加上了虚线构成的边界框)。



RoundRect (hdc, xLeft, yTop, xRight, yBottom,xCornerEllipse, yCornerEllipse) ;

用这个函数画出的图形如下所示。



在绘制上图所示的圆角矩形时,用了下面的公式来计算角上椭圆的尺寸。

xCornerEllipse = (xRight - xLeft) / 4 ;

yCornerEllipse = (yBottom- yTop) / 4 ;

这是一种简单的方法,但是结果看起来有点不对劲,因为角的弯曲部分在矩形长的一边要大些。要矫正这一问题,您可以让xCornerEllipse与yCornerEllipse的值相等。

Arc、Chord和Pie函数都只要相同的参数:

Arc(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd,yEnd) ;

Chord (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd,yEnd) ;

Pie(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd,yEnd) ;



Arc函数画出的线



Chord函数画出的线



Pie函数画出的线

对于Arc函数,这样就结束了。因为弧只是一条椭圆形的线而已,而不是一个填入区域。对于Chord函数,Windows连接弧线的端点。而对于Pie函数,Windows将弧的两个端点与椭圆的中心相连接。弦与扇形图的内部以目前画刷填入。

LINEDEMO画一个矩形、一个椭圆、一个圆角矩形和两条线段,不过不是按这一顺序。程序表明了定义封闭区域的函数实际上对这些区域进行了填入,因为在椭圆后面的线被遮住了。

#include <windows.h>LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT ("LineDemo") ;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 ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow (szAppName, TEXT ("Line Demonstration"),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 int cxClient, cyClient ;HDC hdc ;PAINTSTRUCT ps ;switch (message){case WM_SIZE:cxClient = LOWORD (lParam) ;cyClient = HIWORD (lParam) ;return 0 ;case WM_PAINT:hdc = BeginPaint (hwnd, &ps) ;Rectangle (hdc, cxClient / 8, cyClient / 8,7 * cxClient / 8, 7 * cyClient / 8) ;MoveToEx (hdc, 0, 0, NULL) ;LineTo (hdc, cxClient, cyClient) ;MoveToEx (hdc, 0, cyClient, NULL) ;LineTo (hdc, cxClient, 0) ;Ellipse (hdc, cxClient / 8, cyClient / 8,7 * cxClient / 8, 7 * cyClient / 8) ;RoundRect (hdc, cxClient / 4, cyClient / 4,3 * cxClient / 4, 3 * cyClient / 4,cxClient / 4, cyClient / 4) ;EndPaint (hwnd, &ps) ;return 0 ;case WM_DESTROY:PostQuitMessage (0) ;return 0 ;}return DefWindowProc (hwnd, message, wParam, lParam) ;}




LINEDEMO显示

贝塞尔曲线

一条二维的贝塞尔曲线由四个点定义-两个端点和两个控制点。曲线的端点在两个端点上,控制点就好像「磁石」一样把曲线从两个端点间的直线处拉走。这一点可以由底下的BEZIER互动交谈程序做出最好的展示。

#include <windows.h>LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT ("Bezier") ;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 ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow (szAppName, TEXT ("Bezier Splines"),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 ;}void DrawBezier (HDC hdc, POINT apt[]){PolyBezier (hdc, apt, 4) ;MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;LineTo (hdc, apt[1].x, apt[1].y) ;MoveToEx (hdc, apt[2].x, apt[2].y, NULL) ;LineTo (hdc, apt[3].x, apt[3].y) ;}LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static POINT apt[4] ;HDC hdc ;int cxClient, cyClient ;PAINTSTRUCT ps ;switch (message){case WM_SIZE:cxClient = LOWORD (lParam) ;cyClient = HIWORD (lParam) ;apt[0].x = cxClient / 4 ;apt[0].y = cyClient / 2 ;apt[1].x = cxClient / 2 ;apt[1].y = cyClient / 4 ;apt[2].x = cxClient / 2 ;apt[2].y = 3 * cyClient / 4 ;apt[3].x = 3 * cxClient / 4 ;apt[3].y = cyClient / 2 ;return 0 ;case WM_LBUTTONDOWN:case WM_RBUTTONDOWN:case WM_MOUSEMOVE:if (wParam & MK_LBUTTON || wParam & MK_RBUTTON){hdc = GetDC (hwnd) ;SelectObject (hdc, GetStockObject (WHITE_PEN)) ;DrawBezier (hdc, apt) ;if (wParam & MK_LBUTTON){apt[1].x = LOWORD (lParam) ;apt[1].y = HIWORD (lParam) ;}if (wParam & MK_RBUTTON){apt[2].x = LOWORD (lParam) ;apt[2].y = HIWORD (lParam) ;}SelectObject (hdc, GetStockObject (BLACK_PEN)) ;DrawBezier (hdc, apt) ;ReleaseDC (hwnd, hdc) ;}return 0 ;case WM_PAINT:InvalidateRect (hwnd, NULL, TRUE) ;hdc = BeginPaint (hwnd, &ps) ;DrawBezier (hdc, apt) ;EndPaint (hwnd, &ps) ;return 0 ;case WM_DESTROY:PostQuitMessage (0) ;return 0 ;}return DefWindowProc (hwnd, message, wParam, lParam) ;}


要画一条或多条连接的贝塞尔曲线,只需呼叫:

PolyBezier (hdc, apt, iCount) ;

PolyBezierTo (hdc, apt, iCount) ;

两种情况下,apt都是POINT结构的数组。对PolyBezier,前四个点(按照顺序)给出贝塞尔曲线的起点、第一个控制点、第二个控制点和终点。此后的每一条贝塞尔曲线只需给出三个点,因为后一条贝塞尔曲线的起点就是前一条贝塞尔曲线的终点,如此类推。iCount参数等于1加上您所绘制的这些首尾相接曲线条数的三倍。

PolyBezierTo函数使用目前点作为第一个起点,第一条以及后续的贝塞尔曲线都只需要给出三个点。当函数传回时,目前点设定为最后一个终点。

使用现有画笔(Stock Pens)

内定设备内容中画笔为BLACK_PEN,其它两种是WHITE_PEN和NULL_PEN。NULL_PEN什么都不画。

Windows程序以句柄来使用画笔。Windows表头文件WINDEF.H中包含一个叫做HPEN的型态定义,即画笔的句柄,可以定义这个型态的变量(例如hPen):

HPEN hPen ;

呼叫GetStockObject,可以获得现有画笔的句柄。例如,假设您想使用名为WHITE_PEN的现有画笔,可以如下取得画笔的句柄:

hPen = GetStockObject (WHITE_PEN) ;

现在必须将画笔选进设备内容:

SelectObject (hdc, hPen) ;

目前的画笔是白色。在这个呼叫后,您画的线将使用WHITE_PEN,直到您将另外一个画笔选进设备内容或者释放设备内容句柄为止。

您也可以不定义hPen变量,而将GetStockObject和SelectObject呼叫合并成一个叙述:

SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

SelectObject的传回值是此呼叫前设备内容中的画笔句柄。如果启动一个新的设备内容并呼叫

hPen = SelectObject (hdc, GetStockobject (WHITE_PEN)) ;

则设备内容中的目前画笔将为WHITE_PEN,变量hPen将会是BLACK_PEN的句柄。以后通过呼叫

SelectObject (hdc, hPen) ;

就能够将BLACK_PEN选进设备内容。

画笔的建立、选择和删除

逻辑画笔是一种「GDI对象」,它是您可以建立的六种GDI对象之一,其它五种是画刷、位图、区域、字体和调色盘。除了调色盘之外,这些对象都是通过SelectObject选进设备内容的。

如果想得到更丰富多彩的效果,就必须建立自己的画笔。

这一过程通常是:使用函数CreatePen或CreatePenIndirect建立一个「逻辑画笔」,这仅仅是对画笔的描述。这些函数传回逻辑画笔的句柄;然后,呼叫SelectObject将画笔选进设备内容。现在,就可以使用新的画笔来画线了。在任何时候,都只能有一种画笔选进设备内容。在释放设备内容(或者在选择了另一种画笔到设备内容中)之后,就可以呼叫DeleteObject来删除所建立的逻辑画笔了。在删除后,该画笔的句柄就不再有效了。

在使用画笔等GDI对象时,应该遵守以下三条规则:

最后要删除自己建立的所有GDI对象。

当GDI对象正在一个有效的设备内容中使用时,不要删除它。

不要删除现有对象。

CreatePen函数的语法形如:

hPen = CreatePen (iPenStyle, iWidth, crColor) ;

其中,iPenStyle参数确定画笔是实线、点线还是虚线,该参数可以是WINGDI.H表头文件中定义的以下标识符,下图显示了每种画笔产生的画笔样式。



七种画笔样式

对于PS_SOLID、PS_NULL和PS_INSIDEFRAME画笔样式,iWidth参数是画笔的宽度。iWidth值为0则意味着画笔宽度为一个像素。现有画笔是一个像素宽。如果指定的是点划线或者虚线式画笔样式,同时又指定一个大于1的实际宽度,那么Windows将使用实线画笔来代替。

CreatePen的crColor参数是一个COLORREF值,它指定画笔的颜色。对于除了PS_INSIDEFRAME之外的画笔样式,如果将画笔选入设备内容中,Windows会将颜色转换为设备所能表示的最相近的纯色。PS_INSIDEFRAME是唯一一种可以使用混色的画笔样式,并且只有在宽度大于1的情况下才如此。

与定义一个填入区域的函数一起使用时,PS_INSIDEFRAME画笔样式还有另外一个奇特之处:对于除了PS_INSIDEFRAME以外的所有画笔样式来说,如果用来画边界框的画笔宽度大于1个像素,那么画笔将居中对齐在边界框在线,这样边界框线的一部分将位于边界框之外;而对于PS_INSIDEFRAME画笔样式来说,整条边界框线都画在边界框之内。

也可以通过建立一个型态为LOGPEN(「逻辑画笔」)的结构,并呼叫CreatePenIndirect来建立画笔。

要使用CreatePenIndirect,首先定义一个LOGPEN型态的结构:

LOGPEN logpen ;

此结构有三个成员:lopnStyle(无正负号整数或UINT)是画笔样式,lopnWidth(POINT结构)是按逻辑单位度量的画笔宽度,lopnColor (COLORREF)是画笔颜色。Windows只使用lopnWidth结构的x值作为画笔宽度,而忽略y值。

将结构的地址传递给CreatePenIndirect结构就可以建立画笔了:

hPen = CreatePenIndirect (&logpen) ;

下面是建立、选择和删除画笔的一种方法。假设您的程序使用三种画笔-一种宽度为1的黑画笔、一种宽度为3的红画笔和一种黑色点式画笔,您可以先定义三个变量来存放这些画笔的句柄:

static HPEN hPen1, hPen2, hPen3 ;

在处理WM_CREATE期间,您可以建立这三种画笔:

hPen1 = CreatePen (PS_SOLID, 1, 0) ;

hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0)) ;

hPen3 = CreatePen (PS_DOT, 0, 0) ;

在处理WM_PAINT期间,或者是在拥有一个设备内容有效句柄的任何时间里,您都可以将这三个画笔之一选进设备内容并用它来画线:

SelectObject (hdc, hPen2) ;

画线函数

SelectObject (hdc, hPen1) ;

其它画线函数

在处理WM_DESTROY期间,您可以删除您建立的三种画笔:

DeleteObject (hPen1) ;

DeleteObject (hPen2) ;

DeleteObject (hPen3) ;

这是建立、选择和删除画笔最直接的方法。但是您的程序必须知道执行期间需要哪些逻辑画笔,为此,您可能想要在每个WM_PAINT消息处理期间建立画笔,并在呼叫EndPaint之后删除它们(您可以在呼叫EndPaint之前删除它们,但是要小心,不要删除设备内容中目前选择的画笔)。

您可能还希望随时建立画笔,

SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN)));

下面是另一种方法,在将新建立的画笔选进设备内容时,保存SelectObject传回的画笔句柄:

hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0,0))) ;

如果有一个画笔的句柄,就可以通过呼叫GetObject取得LOGPEN结构各个成员的值:

GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen) ;

如果需要目前选进设备内容的画笔句柄,可以呼叫:

hPen = GetCurrentObject (hdc, OBJ_PEN) ;

填入空隙

使用点式画笔和虚线画笔会产生一个有趣的问题:点和虚线之间的空隙会怎样呢?

空隙的着色取决于设备内容的两个属性-背景模式和背景颜色。内定背景模式为OPAQUE,在这种方式下,Windows使用背景色来填入空隙,内定的背景色为白色。

可以通过如下呼叫来改变Windows用来填入空隙的背景色:

SetBkColor (hdc, crColor) ;

与画笔色彩所使用的crColor参数一样,Windows将这里的背景色转换为纯色。可以通过用GetBkColor来取得设备内容中定义的目前背景色。

通过将背景模式转换为TRANSPARENT,可以阻止Windows填入空隙:

SetBkMode (hdc, TRANSPARENT) ;

此后,Windows将忽略背景色,并且不填入空隙,可以通过呼叫GetBkMode来取得目前背景模式(TRANSPARENT或者OPAQUE)。

绘图方式

当Windows使用画笔来画线时,它实际上执行画笔像素与目标位置处原来像素之间的某种位布尔运算。像素间的位布尔运算叫做「位映像运算」,简称为「ROP」。由于画一条直线只涉及两种像素(画笔和目标),因此这种布尔运算又称为「二元位映像运算」,简记为「ROP2」。

在内定设备内容中,绘图方式定义为R2_COPYPEN,这意味着Windows只是将画笔像素复制到目标像素,这也是我们通常所熟知的。

可以通过以下呼叫在设备内容中设定新的绘图模式:

SetROP2 (hdc, iDrawMode) ;

iDrawMode参数是表中「绘图模式」一栏中给出的值之一。您可以用函数:

iDrawMode = GetROP2 (hdc) ;

在彩色系统中,Windows为画笔和目标像素的每个颜色位执行绘图方式的位运算,并再次使用上表描述的16种ROP2代码。

绘制填入区域

图形以目前设备内容中选择的画刷来填入。内定情况下,使用现有对象,这意味着图形内部将画为白色。Windows定义六种现有画刷:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH (也叫HOLLOW_BRUSH)。

HBRUSH hBrush ;

您可以通过呼叫GetStockObject来取得GRAY_BRUSH的句柄:

hBrush = GetStockObject (GRAY_BRUSH) ;

您可以呼叫SelectObject将它选进设备内容:

SelectObject (hdc, hBrush) ;

现在,如果您要画上表中的任一个图形,则其内部将为灰色。

如果您想画一个没有边界框的图形,可以将NULL_PEN选进设备内容:

SelectObject (hdc, GetStockObject (NULL_PEN)) ;

如果您想画出图形的边界框,但不填入内部,则将NULL_BRUSH选进设备内容:

SelectObject (hdc, GetStockobject (NULL_BRUSH) ;

Polygon函数和多边形填入方式

Polygon (hdc, apt, iCount) ;

其中,apt参数是POINT结构的一个数组,iCount是点的数目。如果该数组中的最后一个点与第一个点不同,则Windows将会再加一条线,将最后一个点与第一个点连起来(在Polyline函数中,Windows不会这么做)。

PolyPolygon函数如下所示:

PolyPolygon (hdc, apt, aiCounts, iPolyCount) ;

该函数绘制多个多边形。最后一个参数给出了所画的多边形的个数。对于每个多边形,aiCounts数组给出了多边形的端点数。apt数组具有全部多边形的所有点。除传回值以外,PolyPolygon在功能上与下面的代码相同:

for (i = 0, iAccum = 0 ; i < iPolyCount ; i++){Polygon (hdc, apt + iAccum, aiCounts[i]) ;iAccum += aiCounts[i] ;}


对于Polygon和PolyPolygon函数,Windows使用定义在设备内容中的目前画刷来填入这个带边界的区域。至于填入内部的方式,则取决于多边形填入方式,您可以用SetPolyFillMode函数来设定:

SetPolyFillMode (hdc, iMode);

内定情况下,多边形填入方式是ALTERNATE,但是您可以将它设定为WINDING。两种方式的区别参见下图所示。



用两种多边形填入方式画出的图:ALTERNATE(左)和WINDING(右)

1.ALTERANATE模式下:从左到右水平扫描!~~系统只填充每个扫描行的多边行的奇数边到偶数边的部分,不填充偶数边到奇数边的部分;

什么是水平扫描

就是显示器上的从左到右一行一行的水平扫描,假如你客户区大小是500X600,那就是从(0,0)(500,0)这是第一次水平扫描,.....一直到(0,600)(500,600)。共扫描了600次就扫描完了客户

什么是奇数边和偶数边:

这里说的奇数和偶数边完全是相对而言的啊,只是我们便于理解的一种方法,

就是说在水平扫描一行时,第一次遇到对线就把它定义为第一条边(注意:同一条线的话在不同的扫描行上相对的边可能不是一样的,这点一定要注意思了).,第二次遇到的线就是第二条边,以此类推;
那么对于一个矩形( 就单单一个矩形),左是第一条边,右是第二条边,上下它就什么都不是了,不会存在第三条边和第四条边的.
奇数边到奇数边,偶数边到偶数边其它跟本不会出现这种情况的;


2.首先,假想线应为扫描线,即从左向右扫描

    ALTERNATE,字面意思交替的,也就是说,选用这种方式的情况下,为偶数数的封闭区域不填充,从图上很好理解。

WINDING,字面意思缠绕的,选用这种方式的情况下,为偶数数的封闭区域可能填充,也可能不填充,填充的原则是:绘制该区域的线段的方向为奇数时填充,偶数时不填充。从下图可以看出,区域5的四条边是一个流动方向,而区域4的四条边是两个流动方向。




#include <windows.h>LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT ("AltWind") ;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 ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow (szAppName, TEXT ("Alternate and Winding Fill Modes"),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 POINT aptFigure [10] = {10,70, 50,70, 50,10, 90,10, 90,50,30,50, 30,90, 70,90, 70,30, 10,30 };static int cxClient, cyClient ;HDC hdc ;int i ;PAINTSTRUCT ps ;POINT apt[10] ;switch (message){case WM_SIZE:cxClient = LOWORD (lParam) ;cyClient = HIWORD (lParam) ;return 0 ;case WM_PAINT:hdc = BeginPaint (hwnd, &ps) ;SelectObject (hdc, GetStockObject (GRAY_BRUSH)) ;for (i = 0 ; i < 10 ; i++){apt[i].x = cxClient * aptFigure[i].x / 200 ;apt[i].y = cyClient * aptFigure[i].y / 100 ;}SetPolyFillMode (hdc, ALTERNATE) ;Polygon (hdc, apt, 10) ;for (i = 0 ; i < 10 ; i++){apt[i].x += cxClient / 2 ;}SetPolyFillMode (hdc, WINDING) ;Polygon (hdc, apt, 10) ;EndPaint (hwnd, &ps) ;return 0 ;case WM_DESTROY:PostQuitMessage (0) ;return 0 ;}return DefWindowProc (hwnd, message, wParam, lParam) ;}



ALTWIND的显示

用画刷填入内部

画刷是一个8×8的位图,它水平和垂直地重复使用来填入内部区域。

Windows还有五个函数,可以让您建立逻辑画刷,然后就可使用SelectObject将画刷选进设备内容。与逻辑画笔一样,逻辑画刷也是GDI对象。您建立的所有画刷都必须被删除,但是当它还在设备内容中时不能将其删除。

下面是建立逻辑画刷的第一个函数:

hBrush = CreateSolidBrush(crColor) ;

函数中的Solid并不是指画刷为纯色。在将画刷选入设备内容中时,Windows建立一个混色色的位图,并为画刷使用该位图。

您还可以使用由水平、垂直或者倾斜的线组成的「影线标记(hatch marks)」来建立画刷,这种风格的画刷对着色条形图的内部和在绘图机上进行绘图最有用。建立影线画刷的函数为:

hBrush = CreateHatchBrush(iHatchStyle, crColor) ;

iHatchStyle参数描述影线标记的外观。下图显示了六种可用的影线标记风格。



CreateHatchBrush中的crColor参数是影线的色彩。在将画刷选进设备内容时,Windows将这种色彩转换为与之最相近的纯色。影线之间的区域根据设备内容中定义的背景方式和背景色来着色。如果背景方式为OPAQUE,则用背景色(它也被转换为纯色)来填入线之间的空间。在这种情况下,影线和填入色都不能是混色而成的颜色。如果背景方式为TRANSPARENT,则Windows只画出影线,不填入它们之间的区域。

您也可以使用CreatePatternBrush和CreateDIBPatternBrushPt建立自己的位图画刷。

变量logbrush是一个型态为LOGBRUSH(「逻辑画刷」)的结构,该结构的三个字段如下表所示,lbStyle字段的值确定了Windows如何解释其它两个字段的值:

lbStyle (UINT)

lbColor (COLORREF)

lbHatch (LONG)

BS_SOLID

画刷的色彩

忽略

BS_HOLLOW

忽略

忽略

BS_HATCHED

影线的色彩

影线画刷风格

BS_PATTERN

忽略

位图的句柄

BS_DIBPATTERNPT

忽略

指向DIB的指标


一旦您取得到了画刷句柄,就可以使用SelectObject将该画刷选进设备内容:

SelectObject (hdc, hBrush) ;

然后,您可以使用DeleteObject函数删除所建立的画刷:

DeleteObject (hBrush) ;

但是,不要删除目前选进设备内容的画刷。

如果您需要取得画刷的信息,可以呼叫GetObject:

GetObject (hBrush, sizeof(LOGBRUSH), (LPVOID) &logbrush) ;

其中,logbrush是一个型态为LOGBRUSH的结构。

GDI 映像方式

「映像方式」是一种几乎影响任何显示区域绘图的设备内容属性。另外有四种设备内容属性-窗口原点、视端口原点、窗口范围和视端口范围-与映像方式密切相关。

映像方式还指示着x轴和y轴的方向(orientation);也就是说,它确定了当您在向显示器的左或者右移动时x的值是增大还是减小,以及在上下移动时y的值是增大还是减小。

Windows定义了8种映像方式,它们在WINGDI.H中相应的标识符和含义如下表所示。

映像方式

逻辑单位

x值

y值

MM_TEXT

像素

MM_LOMETRIC

0.1 mm

MM_HIMETRIC

0.01 mm

MM_LOENGLISH

0.01 in.

MM_HIENGLISH

0.001 in.

MM_TWIPS

1/1440 in.

MM_ISOTROPIC

任意(x = y)

可选

可选

MM_ANISOTROPIC

任意(x != y)

可选

可选


METRIC和ENGLISH指一般通行的度量衡系统,点是印刷的测量单位,约等于1/72英寸,但在图形程序设计中假定为正好1/72英寸。「Twip」等于1/20点,也就是1/1440英寸。「Isotropic」和「anisotropic」是真正的单字,意思是「等方性」(同方向)和「异方性」(不同方向)。

您可以使用下面的叙述来设定映射方式:

SetMapMode (hdc, iMapMode) ;

其中,iMapMode是8个映像方式标识符之一。您可以通过以下呼叫取得目前的映像方式:

iMapMode = GetMapMode (hdc) ;

内定映像方式为MM_TEXT。在这种映像方式下,逻辑单位与实际单位相同,这样我们可以直接以像素为单位进行操作。在TextOut呼叫中,它看起来像这样:

TextOut (hdc, 8, 16, TEXT("Hello"), 5) ;

文字从距离显示区域左端8像素、上端16像素的位置处开始。

如果映像方式设定为MM_LOENGLISH:

SetMapMode (hdc,MM_LOENGLISH) ;

则逻辑单位是百分之一。现在,TextOut呼叫如下:

TextOut (hdc, 50, -100, TEXT("Hello"), 5) ;

文字从距离显示区域左端0.5英寸、上端1英寸的位置处开始。

设备坐标和逻辑坐标

Windows对所有消息(如WM_MOVE、WM_SIZE和WM_MOUSEMOVE),对所有非GDI函数,甚至对一些GDI函数,永远使用设备坐标。可以这样来考虑:由于映像方式是一种设备内容属性,所以,只有对需要设备内容句柄作参数的GDI函数,映像方式才会起作用。GetSystemMetrics不是GDI函数,所以它总是以设备单位(即像素)为量度来传回大小的。尽管GetDeviceCaps是GDI函数,需要一个设备内容句柄作为参数,但是Windows仍然对HORZRES和VERTRES以设备单位作为传回值,因为该函数的目的之一就是给程序提供以像素为单位的设备大小。

从GetTextMetrics呼叫中传回的TEXTMETRIC结构的值是使用逻辑单位的。如果在进行此呼叫时映像方式为MM_LOENGLISH,则GetTextMetrics将以百分之一英寸为单位提供字符的宽度和高度。

设备坐标系

所有设备坐标系都以像素为单位,水平轴(即x轴)上的值从左到右递增,垂直轴(即y轴)上的值从上到下递增。

「屏幕坐标」用在WM_MOVE消息(对于非子窗口)以及下列Windows函数中:CreateWindow和MoveWindow(都是对于非子窗口)、GetMessagePos、GetCursorPos、SetCursorPos、GetWindowRect以及WindowFromPoint(这不是全部函数的列表)。如果以DISPLAY为参数呼叫CreateDC,以取得整个屏幕的设备内容,则内定情况下GDI呼叫中指定的逻辑坐标将被映像为屏幕坐标。

「全窗口坐标」以程序的整个窗口为基准,如标题栏、菜单、滚动条和窗口框都包括在内。而对于普通窗口,点(0,0)是缩放边框的左上角。全窗口坐标在Windows中极少使用,但是如果用GetWindowDC取得设备内容,GDI函数中的逻辑坐标就会转换为显示区域坐标。

第三种坐标系是我们最常使用的「显示区域坐标系」。点(0,0)是显示区域的左上角。当使用GetDC或BeginPaint取得设备内容时,GDI函数中的逻辑坐标就会内定转换为显示区域坐标。

用函数ClientToScreen和ScreenToClient可以将显示区域坐标转换为屏幕坐标,或者反过来,将屏幕坐标转换为显示区域坐标。也可以使用GetWindowRect函数取得屏幕坐标下的整个窗口的位置和大小。

「视端口」是依据设备坐标(像素)的。通常,视端口和显示区域相同,但是,如果您已经用GetWindowDC或CreateDC取得了一个设备内容,则视端口也可以是指整窗口坐标或者屏幕坐标。点(0,0)是显示区域(或者整个窗口或屏幕)的左上角,x的值向右增加,y的值向下增加。

「窗口」是依据逻辑坐标的,逻辑坐标可以是像素、毫米、英寸或者您想要的任何其它单位。您在GDI绘图函数中指定逻辑窗口坐标。

对于所有的映像方式,Windows都用下面两个公式来将窗口(逻辑)坐标转化为视埠(设备)坐标:



这两个公式使用了分别指定窗口和视端口「原点」的点:(xWinOrg,yWinOrg)是逻辑坐标的窗口原点;(xViewOrg,yViewOrg)是设备坐标的视端口原点。在内定的设备内容中,这两个点均被设定为(0,0),但是它们可以改变。此公式意味着,逻辑点(xWinOrg,yWinOrg)总被映像为设备点(xViewOrg,yViewOrg)。

此公式还使用了两点来指定「范围」:(xWinExt,yWinExt)是逻辑坐标的窗口范围;(xViewExt,yViewExt)是设备坐标的窗口范围。每个范围自身没有什么意义,但是视端口范围与窗口范围的比例是逻辑单位转换为设备单位的换算因子。为了提高转换效能,换算因子表示为整数比而不是浮点数。范围可以为负,也就是说,逻辑x轴上的值不一定非得在向右时增加;逻辑y轴上的值不一定非得在向下时增加。

Windows提供了两个函数来让您将设备点转换为逻辑点以及将逻辑点转换为设备点。下面的函数将设备点转换为逻辑点:

DPtoLP (hdc, pPoints,iNumber) ;

其中,pPoints是一个指向POINT结构数组的指针,而iNumber是要转换的点的个数。您会发现这个函数对于将GetClientRect(它总是使用设备单位)取得的显示区域大小转换为逻辑坐标很有用:

GetClientRect (hwnd,&rect) ;

DPtoLP (hdc, (PPOINT) &rect,2) ;

下面的函数将逻辑点转换为设备点:

LPtoDP (hdc, pPoints,iNumber) ;

处理MM_TEXT

对于MM_TEXT映像方式,内定的原点和范围如下所示:

窗口原点:(0, 0) 可以改变

视埠原点:(0, 0) 可以改变

窗口范围:(1, 1) 不可改变

视埠范围:(1, 1) 不可改变

这种映像方式称为「文字」映像方式,不是因为它对于文字最适合,而是由于轴的方向。我们读文字是从左至右,从上至下的,而MM_TEXT以同样的方向定义轴上值的增长方向。

Windows提供了函数SetViewportOrgEx和SetWindowOrgEx,用来改变视端口和窗口的原点,这些函数都具有改变轴的效果,以致(0,0)不再指左上角。一般来说,您会使用SetViewportOrgEx或SetWindowOrgEx之一,但不会同时使用二者。

不管对窗口和视端口原点作什么改变,设备点(0,0)始终是显示区域的左上角。

例如,假设显示区域为cxClient个像素宽和cyClient个像素高。如果想将逻辑点(0,0)定义为显示区域的中心,可进行如下呼叫:

SetViewportOrgEx (hdc,cxClient / 2, cyClient / 2, NULL) ;

SetViewportOrgEx的参数总是使用设备单位。现在,逻辑点(0,0)将映像为设备点(cxClient/2,cyClient/2),而显示区域的坐标系变成如下形状:


用下面的SetWindowOrgEx叙述可以获得与上面使用SetViewportOrgEx同样的效果:

SetWindowOrgEx (hdc,-cxClient / 2, -cyClient / 2, NULL) ;

SetWindowOrgEx的参数总是使用逻辑单位。在这个呼叫之后,逻辑点(-cxClient /2,-cyClient / 2)映像为设备点(0,0),即显示区域的左上角。

您可以使用下面两个函数取得目前视端口和窗口的原点:

GetViewportOrgEx (hdc,&pt) ;

GetWindowOrgEx (hdc, &pt);

「度量」映像方式

Windows包含五种以实际尺寸来表示逻辑坐标的映像方式,这些映像方式能使您画出不变形的圆和矩形。

这五种「度量」映像方式在下表中列出:

映像方式

逻辑单位

MM_LOENGLISH

0.01 in.

MM_LOMETRIC

0.1 mm.


下面是窗口坐标到视端口坐标的转换公式:


要想在显示区域显示任何东西,必须使用负的y值。例如下面的程序代码:

SetMapMode (hdc,MM_LOENGLISH) ;

TextOut (hdc, 100, -100,"Hello", 5) ;

将把文字显示在距离显示区域左边和上边各一英寸的地方。

为了使自己保持头脑清醒,您可能想避免这样做。一种解决办法是将逻辑的(0,0)点设为显示区域的左下角,您可以通过呼叫SetViewportOrgEx来完成(假设cyClient是以像素为单位的显示区域的高度):

SetViewportOrgEx (hdc, 0,cyClient, NULL) ;

此时的坐标系如下:


SetViewportOrgEx (hdc,cxClient / 2, cyClient / 2, NULL) ;

此时的坐标系如下所示:


还可以使用SetWindowOrgEx函数来改变逻辑(0,0)点,但是这稍微困难一些,因为SetWindowOrgEx的参数必须使用逻辑单位,先要将(cxClient,cyClient)用DPtoLP函数转换为逻辑坐标。假设变量pt是型态为POINT的结构,下面的代码将逻辑(0,0)点改变到显示区域的中央:

pt.x = cxClient ;

pt.y = cyClient ;

DptoLP (hdc,&pt, 1) ;

SetWindowOrgEx (hdc, -pt.x /2, -pt.y / 2, NULL) ;

「自行决定」的映像方式

MM_ISOTROPIC与度量映像方式之间的区别是,使用MM_ISOTROPIC,您可以控制逻辑单位的实际尺寸。如果愿意,您可以根据显示区域的大小来调整逻辑单位的实际尺寸,从而使所画的图像总是包含在显示区域内,并相应地放大或缩小。

如果想要在使用任意的轴时都保证两个轴上的逻辑单位相同,则MM_ISOTROPIC映像方式就是理想的映像方式。

当您刚开始将映像方式设定为MM_ISOTROPIC时,Windows使用与MM_LOMETRIC同样的窗口和视端口范围(但是,不要对此有所依赖)。区别在于,您现在可以呼叫SetWindowExtEx和SetViewportExtEx来根据自己的偏好改变范围了,然后,Windows将调整范围的值,以便两条轴上的逻辑单位有相同的实际距离。

您可以用所期望的逻辑窗口的逻辑尺寸作为SetWindowExtEx的参数,用显示区域的实际宽和高作为SetViewportExtEx的参数。Windows在调整这些范围时,必须让逻辑窗口适应实际窗口,这就有可能导致显示区域的一段落到了逻辑窗口的外面。必须在呼叫SetViewportExtEx之前呼叫SetWindowExtEx,以便最有效地使用显示区域中的空间。

SetMapMode (hdc,MM_ISOTROPIC) ;

SetWindowExtEx(hdc, 32767, 32767, NULL) ;

SetViewportExtEx(hdc, cxClient, -cyClient, NULL) ;

SetViewportOrgEx(hdc, 0, cyClient, NULL) ;

如果其后用GetWindowExtEx和GetViewportExtEx函数获得了窗口和视端口的范围,可以发现,它们并不是先前指定的值。Windows将根据显示设备的纵横比来调整范围,以便两条轴上的逻辑单位表示相同的实际尺寸。

在MM_ISOTROPIC映像方式下设定窗口和视端口范围时,Windows会调整范围,以便两条轴上的逻辑单位具有相同的实际尺度。在MM_ANISOTROPIC映射方式下,Windows不对您所设定的值进行调整,这就是说,MM_ANISOTROPIC不需要维持正确的纵横比。

#include <Windows.h>LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,   PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT("WhatSize");HWND hwnd;MSG msg;WNDCLASS wndclass;wndclass.cbClsExtra=0;wndclass.cbWndExtra=0;wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.hCursor=LoadCursor(NULL, IDC_ARROW);wndclass.hIcon=LoadIcon(NULL, IDI_APPLICATION);wndclass.hInstance=hInstance;wndclass.lpfnWndProc=WndProc;wndclass.lpszClassName=szAppName;wndclass.lpszMenuName=NULL;wndclass.style=CS_VREDRAW | CS_HREDRAW;if (!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("It must be Win7!"),szAppName, MB_OK);return 0;}hwnd = CreateWindow(szAppName,TEXT("what is the window size"),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;}void Show(HWND hwnd, HDC hdc, int xText, int yText, int iMapMode,  TCHAR *szMapMode){TCHAR szBuffer[60];RECT rect;SaveDC(hdc);SetMapMode(hdc, iMapMode);GetClientRect(hwnd, &rect);DPtoLP(hdc, (PPOINT)&rect, 2);RestoreDC(hdc, -1);TextOut(hdc, xText, yText, szBuffer,wsprintf(szBuffer, TEXT("%-20s %7d %7d %7d %7d"), szMapMode,rect.left, rect.right, rect.top, rect.bottom));}LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static TCHAR szHeading[] = TEXT("Mapping Mode Left Right Top Bottom");static TCHAR szUndLine[] =TEXT("------------ ---- ----- --- ------");static int cxChar, cyChar;HDC hdc;PAINTSTRUCT ps;TEXTMETRIC tm;switch (message){case WM_CREATE:hdc  = GetDC(hwnd);SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));GetTextMetrics(hdc, &tm);cxChar = tm.tmAveCharWidth;cyChar = tm.tmHeight + tm.tmExternalLeading;ReleaseDC(hwnd, hdc);return 0;case WM_PAINT:hdc = BeginPaint(hwnd, &ps);SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));SetMapMode(hdc, MM_ANISOTROPIC);SetWindowExtEx(hdc, 1, 1, NULL);SetViewportExtEx(hdc, cxChar, cyChar, NULL);TextOut(hdc, 1, 1, szHeading, lstrlen(szHeading));TextOut(hdc, 1, 2, szUndLine, lstrlen(szUndLine));Show(hwnd, hdc, 1, 3, MM_TEXT, TEXT("TEXT (pixels"));Show (hwnd, hdc, 1, 4, MM_LOMETRIC, TEXT ("LOMETRIC (.1 mm)")) ;Show (hwnd, hdc, 1, 5, MM_HIMETRIC, TEXT ("HIMETRIC (.01 mm)")) ;Show (hwnd, hdc, 1, 6, MM_LOENGLISH, TEXT ("LOENGLISH (.01 in)")) ;Show (hwnd, hdc, 1, 7, MM_HIENGLISH,TEXT ("HIENGLISH (.001 in)")) ;Show (hwnd, hdc, 1, 8, MM_TWIPS, TEXT ("TWIPS (1/1440 in)")) ;EndPaint(hwnd, &ps);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);}


矩形、区域和剪裁

矩形函数

FillRect (hdc,&rect, hBrush) ;

FrameRect (hdc,&rect, hBrush) ;

InvertRect (hdc,&rect) ;

在这些函数中,rect参数是一个RECT型态的结构,它包含有4个字段:left、top、right和bottom。这个结构中的坐标被当作逻辑坐标。

FillRect用指定画刷来填入矩形(直到但不包含right和bottom坐标),该函数不需要先将画刷选进设备内容。

FrameRect使用画刷画矩形框,但是不填入矩形。使用画刷画矩形看起来有点奇怪,因为对于我们所介绍过的函数(如Rectangle),其边线都是用目前画笔绘制的。FrameRect允许使用者画一个不一定为纯色的矩形框。该边界框为一个逻辑单位元宽。如果逻辑单位大于设备单位,则边界框将会为2个像素宽或者更宽。

InvertRect将矩形中所有像素翻转,1转换成0,0转换为1,该函数将白色区域转变成黑色,黑色区域转变为白色,绿色区域转变成洋红色。

Windows还提供了9个函数,使您可以更容易、更清楚地操作RECT结构:

将RECT结构的四个字段设定为特定值

SetRect (&rect, xLeft, yTop, xRight,yBottom) ;

将矩形沿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) ;

随机矩形

PeekMessage (&msg, NULL,0, 0, PM_REMOVE) ;

前面的四个参数(一个指向MSG结构的指针、一个窗口句柄、两个值指示消息范围)与GetMessage的参数相同。将第二、三、四个参数设定为NULL或0时,表明我们想让PeekMessage传回程序中所有窗口的所有消息。如果要将消息从消息队列中删除,则将PeekMessage的最后一个参数设定为PM_REMOVE。如果您不希望删除消息,那么您可以将这个参数设定为PM_NOREMOVE。

GetMessage不将控制传回给程序,直到从程序的消息队列中取得消息,但是PeekMessage总是立刻传回,而不论一个消息是否出现。当消息队列中有一个消息时,PeekMessage的传回值为TRUE(非0),并且将按通常方式处理消息。当队列中没有消息时,PeekMessage传回FALSE(0)。

GetMessage接收到一个WM_QUIT消息,它将传回0,但是PeekMessage用它的传回值来指示是否得到一个消息,所以需要对WM_QUIT进行检查。

尽管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循环中。)

建立和绘制剪裁区域

剪裁区域是对显示器上一个范围的描述,这个范围是矩形、多边形和椭圆的组合。

剪裁区域可以用于绘制和剪裁,通过将剪裁区域选进设备内容,就可以用剪裁区域来进行剪裁(就是说,将可以绘图的范围限制为显示区域的一部分)

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)


从CombineRgn传回的iRgnType值是下列之一:NULLREGION,表示得到一个空剪裁区域;SIMPLEREGION,表示得到一个简单的矩形、椭圆或者多边形;COMPLEXREGION,表示多个矩形、椭圆或多边形的组合;ERROR,表示出错了。

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函数用设备内容中目前画刷填入所指定的区域。所有这些函数都假定区域是用逻辑坐标定义的。

 

原创粉丝点击