在正式开始学习绘图之前, 还应该先了解下在屏幕上绘图的相关流程, 首先, 获取设备环境的句柄, 当获取成功时就意味着你的应用程序有了在屏幕上绘图的权限, 然后你就可以调用GDI中的绘图函数通过设备环境句柄对屏幕进行绘制, 等绘制结束后你应该释放这个句柄。
- 获取设备环境句柄
Windows提供了许多种方法用来获取不同的设备环境句柄, 这里不能一次性讲全, 当前我们需要使用的主要有以下几种:
1>. 使用BeginPaint函数和EndPaint函数:
BeainPaint函数原型:
HDC BeginPaint(
HWND hwnd, // 窗口的句柄
LPPAINTSTRUCT lpPaint // 绘制信息
);
参数二为PAINTSTRUCT类型的结构, 函数的返回值就是设备环境句柄, PAINTSTRUCT结构定义在WINUSER.H头文件中, 如下:
typedef struct tagPAINTSTRUCT
{
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT ;
在该结构中的第一个成员HDC的值正是设备环境句柄, 函数返回的设备环境句柄也正是来源于此。
通过BeginPaint函数来获取设备环境句柄通常用于处理WM_PAINT消息时, 一般的使用结构:
hdc = BeginPaint( hwnd, &ps ) ;
[相关的处理语句]
EndPaint( hwnd, &ps ) ; //释放设备环境句柄
2>. 使用GetDC函数
GetDC函数可以用来获取制定窗口句柄的设备环境句柄, 同时也可以获取整个屏幕的设备环境句柄, 其函数原型为:
HDC GetDC( HWND hWnd ) ; //函数参数为窗口句柄
当函数调用成功时返回设备环境的句柄, 失败返回NULL, 当函数参数为NULL时返回的就是整个屏幕的设备环境句柄。
当设备环境句柄使用完成后应该及时使用ReleaseDC函数释放该设备环境句柄, 值得一提的是, GetDC和ReleaseDC函数不能使无效的客户区变成有效, 当使用GetDC方式重绘完成后可以显性调用ValidateRect函数使其有效, ValidateRect原型:
BOOL ValidateRect(
HWND hWnd, // 窗口的句柄
CONST RECT *lpRect // 指向RECT结构的指针
) ;
参数一为被有效化的窗口句柄, 若该参数为NULL, 系统将更新所有的窗口; 参数二为一个包含需要生效的矩形的更新区域坐标的RECT结构体, 当参数为NULL时, 整个客户区都将有效化。
使用的一般形式:
hdc = GetDC( hwnd ) ;
[相关的处理语句]
ReleaseDC( hwnd, hdc ) ;
3>. 使用GetWindowDC
与GetDC不同, GetDC可以用来获取窗口的客户区部分的设备环境句柄, 而GetWindowDC是用来获取整个窗口的设备环境句柄, 整个窗口是指包括窗口的标题栏、菜单栏、滚动条、状态栏以及客户区和客户区的外缘边框部分, 函数原型:
HDC GetWindowDC( HWND hWnd // 窗口句柄 );
在使用完成后同样要使用ReleaseDC对设备环境句柄进行释放, 使用的一般形式:
hdc = GetWindowDC( hwnd ) ;
[相关的处理语句]
ReleaseDC( hwnd, hdc ) ;
4>. 使用CreateDC
CreateDC的作用是通过使用指定的名字为一个设备创建一个设备环境句柄, 在使用完成后应当由DeleteDC函数进行删除释放, 而不是ReleaseDC。
函数原型:
HDC CreateDC(
LPCTSTR lpszDriver,
LPCTSTR lpszDevice,
LPCTSTR lpszOutput,
const DEVMODE *lpInitData
);
参数一LPCTSTR lpszDriver指向一个以NULL结尾的字符串的指针, 当字符串为TEXT("DISPLAY")时,是获取整个屏幕的设备环境, 为TEXT("WINSPOOL")则是访问打印驱动的设备环境;
注意: 当参数为TEXT("WINSPOOL")时其他参数均为NULL。
参数二LPCTSTR lpszDevice指向一个以null结尾的字符串的指针, 该字符串指定了正在使用的特定输出设备的名字;
参数三LPCTSTR lpszOutput在32位环境下通常被忽略, 并置为NULL, 该参数的存在主要是为了提供与16位应用程序兼容;
参数四const DEVMODE *lpInitData指向包含设备驱动程序的设备指定初始化数据的DEVMODE结构的指针, DEVMODE数据结构中包含了有关设备初始化和打印机环境的信息 , 如果设备驱动程序使用用户指定的缺省初始化值。则lplnitData参数必须为NULL。
对于这个函数可能暂时有点难以理解, 不过暂时也用不到他, 现在只需要记住一条用法:
hdc = CreateDC( TEXT( "DISPLAY" ), NULL, NULL, NULL ) ; //获取当前整个屏幕的设备环境句柄
在使用完成后记住要使用DeeteDC进行释放, 而不是ReleaseDC。
- 获取设备环境信息
获取设备环境信息, 也成属性, 通常是指物理硬件的的某些信息, 比如显示器的分辨率、尺寸、色彩能力等, 要获取设备环境的信息首先要得到设备环境句柄, 然后通过GetDeviceCaps函数获取, GetDeviceCaps的原型:
int GetDeviceCaps(
HDC hdc,
int nIndex
);
参数nIndex用来指明需要获取的信息类型, 例如当参数为HORZRES时, 函数返回以像素为单位的设备环境的宽度, HORZRES是定义在WINGDI.H中的29个标识符之一, 这29个标识符如下:
/* Device Parameters for GetDeviceCaps() */
#define DRIVERVERSION 0 /* Device driver version */
#define TECHNOLOGY 2 /* Device classification */
#define HORZSIZE 4 /* Horizontal size in millimeters */
#define VERTSIZE 6 /* Vertical size in millimeters */
#define HORZRES 8 /* Horizontal width in pixels */
#define VERTRES 10 /* Vertical height in pixels */
#define BITSPIXEL 12 /* Number of bits per pixel */
#define PLANES 14 /* Number of planes */
#define NUMBRUSHES 16 /* Number of brushes the device has */
#define NUMPENS 18 /* Number of pens the device has */
#define NUMMARKERS 20 /* Number of markers the device has */
#define NUMFONTS 22 /* Number of fonts the device has */
#define NUMCOLORS 24 /* Number of colors the device supports */
#define PDEVICESIZE 26 /* Size required for device descriptor */
#define CURVECAPS 28 /* Curve capabilities */
#define LINECAPS 30 /* Line capabilities */
#define POLYGONALCAPS 32 /* Polygonal capabilities */
#define TEXTCAPS 34 /* Text capabilities */
#define CLIPCAPS 36 /* Clipping capabilities */
#define RASTERCAPS 38 /* Bitblt capabilities */
#define ASPECTX 40 /* Length of the X leg */
#define ASPECTY 42 /* Length of the Y leg */
#define ASPECTXY 44 /* Length of the hypotenuse */
#if(WINVER >= 0x0500)
#define SHADEBLENDCAPS 45 /* Shading and blending caps */
#endif /* WINVER >= 0x0500 */
#define LOGPIXELSX 88 /* Logical pixels/inch in X */
#define LOGPIXELSY 90 /* Logical pixels/inch in Y */
#define SIZEPALETTE 104 /* Number of entries in physical palette */
#define NUMRESERVED 106 /* Number of reserved entries in palette */
#define COLORRES 108 /* Actual color resolution */
关于这些标识符的详细介绍可查阅MSDN -> GetDeviceCaps函数对参数int nIndex的解释。
3. 绘图函数
绘图函数就是GDI中最重要的部分了, 其中有许多的绘图函数, 这里同样不能一次讲完, 也不打算讲完, 这些都没有什么意义, 只需要查阅下MSDN或Google下找到这些函数并尝试着使用它就行了, 这里仅描述几个最基本的绘图函数。
1>. SetPixel绘制像素点
这是一个十分不常用的函数, 如果你想拿这个来绘制一条直线, 那么效率就太低了, 这个函数为 SetPixel, 其函数的原型为:
COLORREF SetPixel(
HDC hdc,
int X, //x坐标
int Y, //y坐标
COLORREF crColor //颜色, 使用RGB宏
);
RGB宏定义在WINGDI.H中, 定义如下:
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
RGB宏有三个参数, 将红®、绿(G)、蓝(B)组合成一个无符号型的长整形用来表示颜色, 当三个参数都为0时颜色为黑色, 都为255时颜色为白色。
使用示例:
//在屏幕坐标为( 100, 100 )将该像素置为黑色
SetPixel( hdc, 100, 100, RGB( 0, 0, 0 ) ) ;
2>. LineTo绘制直线
BOOL LineTo(
HDC hdc,
int nXEnd, //结束x坐标
int nYEnd //结束y坐标
);
该函数需要和MoveToEx函数配合使用才能制定设备环境上的任意两点间绘制一条直线, MoveToEx函数的作用就是规定直线的起点坐标, 其函数原型为:
BOOL MoveToEx(
HDC hdc,
int X, //起点x坐标
int Y, //起点y坐标
LPPOINT lpPoint //一个POINT结构, 用来接收当前位置, 为NULL表示不接收
);
使用示例:
//绘制一条起点为( 100, 100 ), 终点为( 500, 100 )的水平直线
MoveToEx( hdc, 100, 100, NULL );
LineTo( hdc, 500, 100 );
3>. Polyline绘制折线
函数原型:
BOOL Polyline(
HDC hdc,
const POINT *lppt, //指向POINT结构数组
int cPoints //在该组坐标中所使用的点数, 必须 >= 2
);
用法示例:
//绘制一条折线
POINT apt[5] = { {100, 100}, {300, 200}, {100, 400}, {400, 300}, {500, 200} } ;
Polyline( hdc, apt, 5 ) ;
4>. PolyPolyline绘制多条折线
PolyPolyline实际上是对Polyline函数中使用的坐标组提供了分组功能, 分为几组就意味着绘制多少条折线, PolyPolyline函数的原型:
BOOL PolyPolyline(
HDC hdc,
const POINT *lppt, //坐标组
const DWORD *lpdwPolyPoints, //对坐标进行分组
DWORD cCount //折线条数
);
用法示例:
//8个点的坐标组
POINT apt[8] = { { 100, 100}, {200, 200}, {300, 200}, {200, 300}, {400, 200}, {600, 300}, {300, 400}, {500, 200} } ;
DWORD lpPts[] = { 4, 4 }; //将这些点分为两组, 4个点为一组
PolyPolyline( hdc, apt, lpPts, 2 ) ; //绘制两条折线
5>. Rectangle绘制矩形
绘制一个矩形十分简单, 只需要提供矩形左上角坐标与右下角坐标即可, 函数原型如下:
BOOL Rectangle(
HDC hdc,
int nLeftRect, //左上角x坐标
int nTopRect, //左上角y坐标
int nRightRect, //右下角x坐标
int nBottomRect //右下角y坐标
);
例如我们绘制一个正方形:
Rectangle( hdc, 50, 50, 200, 200 ) ;
看下效果图:
可以看到, 确实是一个由四条直线围成的矩形, 但是需要提醒的是, 这个矩形是经过自动填充的, 填充的颜色就是默认的白色颜色, 在这个图中填充颜色为白色, 和背景颜色相同, 所以我们才不易觉察到这是一个经过填充的图形, 被填充意思上就是说你以前在该矩形里或者说经过该矩形的图形会被白色给覆盖掉, 这点需要注意。
6>. Ellipse绘制椭圆
函数原型:
BOOL Ellipse(
HDC hdc,
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect
);
绘制椭圆的参数和绘制矩形的参数是相同的, 那么他是如何绘制的呢? 这里用图示说明下:
示例中的矩形参数与椭圆的参数是相同的, 很明显的可以看出椭圆是根据矩形的大小自动填充的, 需要提示的是, 它填充的是整个矩形, 而不仅仅是自身的椭圆。
7>. RoundRect画圆角矩形
函数原型:
BOOL RoundRect(
HDC hdc,
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect,
int nWidth,
int nHeight
);
相对于画矩形函数这里又多出两个参数int nWidth和int nHeight, 实际上, 这两个参数是用来描述圆角的宽和高, 如图:
8>. Arc弧线、Chord拱形与Pie扇形
之所以将这三个函数放在一起讲是因为他们三个拥有相同的参数, 他们三个的函数原型如下:
BOOL Arc ( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;
BOOL Chord( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;
BOOL Pie ( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;
这些参数都表示什么意思呢? 用文字恐怕不太好描述, 图示如下:
在指定好外部矩形的坐标后剩下的就是指定绘制的起点坐标与终点了, 多尝试几次就能掌握其使用规律。
以上的这些就是GDI种较为基础的绘图函数了, 更多的绘图函数请自行查阅MSDN Library。
下面练习使用下这些函数:
窗口过程函数部分:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
回调函数
参数:
hwnd : 窗口句柄
message : 消息ID
wParam和lParam:消息参数
//int i;
//size_t j;
HDC hdc;//定义设备环境句柄
static TCHAR szBuffer[128];
POINT apn[4] = { {150, 50}, {200, 200}, {150, 300}, {150, 500} }; //坐标组
POINT apt[8] = { { 200, 50}, {300, 200}, {250, 200}, {200, 300}, {250, 300}, {300, 350}, {250, 400}, {250, 500} }; //坐标组
DWORD lpPts[] = { 4, 4 };
//TEXTMETRIC tm;
// SCROLLINFO si;
PAINTSTRUCT ps;
//RECT rect;
size_t iStrLength;
static int cxChar, cyChar, cxClient, cyClient;
switch (message)
{
case WM_SIZE://窗体大小改变
hdc = GetDC(hWnd);
//GetClientRect(hwnd, &rect);
cxClient = LOWORD(lParam);//当前x像素
cyClient = HIWORD(lParam);//当前y像素
StringCchPrintf(szBuffer, 128, TEXT("当前客户区的分辨率:%d * %d px"), cxClient, cyClient);
StringCchLength(szBuffer, 128, &iStrLength);
SetTextAlign(hdc, TA_CENTER | TA_TOP);
TextOut(hdc, cxClient / 2, 0, szBuffer, iStrLength);
ReleaseDC(hWnd, hdc);
return 0;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
int i;
//在(50, y)方向上绘制90个点, 颜色为黑色
for (i = 0; i < 90; i++)
SetPixel(hdc, 50, 50 + i * 5, RGB(0, 0, 0));
//画线, 起点坐标( 100, 50 ), 终点坐标( 100, 500 )
MoveToEx(hdc, 100, 50, NULL);
LineTo(hdc, 100, 500);
//画一条折线
Polyline(hdc, apn, 4);
//画2条折线, 将apt分为2组
PolyPolyline(hdc, apt, lpPts, 2);
//画椭圆弧线
Arc(hdc, 350, 50, 500, 500, 400, 100, 400, 500);
//画矩形
Rectangle(hdc, 450, 50, 500, 500);
//画椭圆
Ellipse(hdc, 550, 50, 600, 500);
//画圆
Ellipse(hdc, 800, 200, 1000, 400);
//画圆角矩形
RoundRect(hdc, 650, 50, 700, 500, 20, 20);
//画扇形
Pie(hdc, 750, 50, 850, 150, 850, 80, 850, 160);
//画拱形
Chord(hdc, 750, 400, 850, 500, 850, 450, 750, 450);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}