Windows GDI 教程(六) DC与加载位图

为了能够理清楚一些常见的 DC ,这里请让博主在介绍一下几种 DC 的获得方式:

  • BeginPaint 通过窗口 (hwnd) 获得
  • GetDC 通过窗口 (hwnd) 获得
  • CreatCompatibleDC 通过 hdc 获得

关于这三种情况获得的 DC,需要注意的是:从窗口上获得到的DC在释放资源的时候需要调用 ReleaseDC (释放引用,这样其他地方还可以接着用),而通过 CreatCompatibleDC 创建的 DC 我们通常称之为 Memory DC (博主管这叫“缓存DC”),当你不需要它的时候,需要通过 DeleteDC 来释放资源(因为这个DC是你单独申请的,如果你不用了,其他也不会有地方用它,只能直接 Delete)。

BeginPaint 与 GetDC

同样是通过窗口获得其客户端区域(Client area)的可绘制DC,BeginPaint 函数是先使窗口有效再重画,而 GetDC 没有这样,使用 GetDC 时由于窗口始终处于无效状态,不断会产生WM_PAINT消息。

BeginPaint 的表现是,收到 WM_PAINT 时绘制。所以正常情况下,如果没有手动触发 WM_PAINT 那么打开窗口则只绘制一次。

GetDC 的表现是,不断收到 WM_PAINT 消息,绘图区域(DC)不停地刷新。如果绘制过程比较慢的

CreatCompatibleDC

CreatCompatibleDC 函数的书面描述是,用来创建一个与指定设备兼容的内存设备上下文环境(DC)。

这个书面描述感觉有点不好理解,这里博主说一下自己的看法:

关于这个 CreatCompatibleDC 函数首先要了解这其中的单词:Creat、Compatible、DC。其中 Create 是“创建”、Compatible 是兼容的、DC 是博主说的“可画图的内存对象”。那么创建和 DC 都很好理解。这里我们重点要理解这个“兼容的”这个单词。

3个意思:

  1. 该DC的类型与兼容目标相同。虽然博主一再强调关于DC只需要把它当做“可以用来画图的内存就好了”,但是“橘生淮南则为橘,生于淮北则为枳”,从不同的设备中创建出来的DC,互相之间也是有差别的,由此产生的DC的类型包括:显示器、打印机、存储器和数据的索引。
  2. 该DC不需要指定区域、大小。感觉这一条有点凑数的感觉(博主:别打我!)
  3. 该DC与兼容的对象可以方便交流数据。该功能在GDI内部实现。比如使用 BitBlt 函数可以将兼容 DC 上的位图写入到目标 DC中。

加载BMP位图实例

BMP 是 Bitmap 的简写,中文常常称之为 “位图”。

#define WINDOW_WIDTH 400
#define WINDOW_HEIGHT 300

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HDC	mdc;
	HBITMAP hbmp; // 位图绘制对象句柄

	hdc = BeginPaint(hwnd, &ps); 

	// 创建缓存DC (当前窗口DC的兼容DC)
	mdc = CreateCompatibleDC(hdc);

	// 加载 "E:\bg.bmp" 到位图绘制对象 hbmp 中
	hbmp = (HBITMAP)LoadImage(
		NULL,			// 模块实例句柄(要加载的图片在其他DLL中时)
		"E:\bg.bmp",	// 位图路径
		IMAGE_BITMAP,	// 位图类型
		WINDOW_WIDTH,	// 指定图片宽
		WINDOW_HEIGHT,	// 指定图片高
		LR_LOADFROMFILE	// 从路径处加载图片
		);

	// 缓存DC选择位图绘制对象(可以理解为将图片存到mdc中)
	SelectObject(mdc, hbmp);

	// 将缓存DC中的位图复制到窗口DC上
	BitBlt(
		hdc,			// 目的DC
		0,0,			// 目的DC的 x,y 坐标
		WINDOW_WIDTH,	// 要粘贴的图片宽
		WINDOW_HEIGHT,	// 要粘贴的图片高
		mdc,			// 缓存DC
		0,0,			// 缓存DC的 x,y 坐标
		SRCCOPY 		// 粘贴方式
		);

	DeleteObject(hbmp);
	DeleteDC(mdc);
	EndPaint(hwnd, &ps);
}

效果图:
加载BMP图片

如何理解上述代码

对于初学者很容易对上述代码迷惑。常见的几个问题列举:

为什么要用 HBitMap 去 LoadImage,而不是直接用 DC 去 Load?

按照博主的思路来描述一下这个问题吧,画图可以简单的分成三个部分,分别是:
载体(如白纸、油画画板)、工具(如2B铅笔、马克笔)、颜料

对应到GDI程序中,使用画笔可以看成:载体(DC)、工具(画笔 HPEN)、颜料CreatePen 函数中传入)。

那么对应加载位图可以这样看:载体(DC)、工具(?)、颜料LoadImage 中指定图片)

这样很明显可以看到缺少一个工具加载位图的工具。也就是我们的 HBITMAP 对象。

这里拓展一下GDI中可以在DC用来绘制的工具:

对象 通过什么函数创建
Bitmap CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection
Bitmap对象只能被缓存DC(通过 CreatCompatibleDC创建)选中,并且单个bitmap不能同时被多个DC选中。
Brush CreateBrushIndirect, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush, CreatePatternBrush, CreateSolidBrush
Font CreateFont, CreateFontIndirect
Pen CreatePen, CreatePenIndirect
Region CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreateRectRgn, CreateRectRgnIndirect

此列表出处见 SelectObject function on MSDN

为什么要用 LoadImage 到 HBitMap,然后让 mdc 去 select,然后再让 mdc 复制到 hdc?

如果C语言基础学的好的话,那么这个问题也可以用一个很简单的角度去看待:
1.申请缓存空间
2.加载数据到缓存
3.输出数据(将缓存中的数据写入到目标内存)

以基础的C语言代码为例,假设是“输出用户输入的一个整数”,那么C语言代码如下:

// 1.(申请缓存空间) 声明一个4字节的int型变量,用于缓存用户输入
int num;
// 2.(加载数据到缓存) 将用户输入的数字加载到num中
scanf("%d", &num);
// 3.(将缓存中的数据写入到目标内存) 输出 num 到屏幕(将num的值写入到显存)
printf("你输入的是:%d n", num);

对应到加载图片的程序就可以这样理解了。

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HDC	mdc;
	HBITMAP hbmp;

	hdc = BeginPaint(hwnd, &ps); 

	// 1.(申请缓存空间) 创建缓存DC
	mdc = CreateCompatibleDC(hdc);

	// 2.(加载数据到缓存) 通过 Hbitmap 加载数据到 缓存DC
	hbmp = (HBITMAP)LoadImage( ... );
	SelectObject(mdc, hbmp);

	// 3.(将缓存中的数据写入到目标内存) 将缓存DC中的位图复制到窗口DC上
	BitBlt( ... );

	DeleteObject(hbmp);
	DeleteDC(mdc);
	EndPaint(hwnd, &ps);
}

这代码也就是看起来长了一点,没什么大不了的,本质上还是基础的程序逻辑。区别仅仅在于:
1.缓存的空间从 int 换成了 HDC
2.加载数据的方法从使用 scanf 换成了 LoadImageSelectObject
3.输出数据的对象从 int类型的num 写入到显存,换成了 HDC类型的mdc 写入到 hdc;输出数据的方法从 printf 换成了 BitBlt

PS:引用博主一句 装逼 的话来总结一下上面的对比:“程序做并且只在做两件事,一、读数据,二、写数据”

为什么不直接让 hdc 直接去 select 图片的 HBitMap?

1.首先上文有提到过 Bitmap 对象只能被缓存DC (Memory DC,通过 CreateCompatibleDC 创建的)选中。

2.其次,就算情况1不存在,bitmap 可以被 hdc 选中,这在程序的逻辑上也是一件很别扭的事情。依旧是套用 C语言基础的程序来看这个问题:

// 1.不申请缓存空间

// 2.3.直接获取数据并输出
printf("你输入的是:%d n", scanf( ? ) );

或者这样:

// 1.不申请缓存空间

// 2.3.直接获取数据并输出
printf("你输入的是:%s n", gets( ? ) );

基础好的可以马上发现上面两个示意的代码都是没法实现的。当然,如果你依旧觉得这个合理也是可以的,毕竟在其他的语言(比如python)里面这种情况是可以实现的。但是这样写的话,会有如下两种情况:

  1. 不自由。无法修改该数据,只能死板的输出。
  2. 效率低。没有缓存,要输出的时候必须要当时加载,下一次再次用到的时候又要重新加载。

综上。缓存DC是有存在的必要。

修改图片之后再显现的实例

为了照应上文,这里附上一个修改缓存DC中图片然后再显示到窗口的例子。

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HDC	mdc;
	HBITMAP hbmp; // 位图绘制对象句柄

	hdc = BeginPaint(hwnd, &ps); 

	// 加载图片到缓存 DC
	mdc = CreateCompatibleDC(hdc);
	hbmp = (HBITMAP)LoadImage(NULL, "E:\bg.bmp", IMAGE_BITMAP, WINDOW_WIDTH, WINDOW_HEIGHT, LR_LOADFROMFILE);
	SelectObject(mdc, hbmp);

	// 修改缓存DC (在缓存的图片上写字)
	TextOut(mdc, 0,0, TEXT("Hi, 缓存DC你好。"), strlen(TEXT("Hi, 缓存DC你好。")));

	// 将缓存DC中的位图复制到窗口DC上
	BitBlt(hdc, 0,0, WINDOW_WIDTH, WINDOW_HEIGHT, mdc, 0,0, SRCCOPY);

	DeleteObject(hbmp);
	DeleteDC(mdc);
	EndPaint(hwnd, &ps); 
}

效果图:
修改缓存DC

教程索引

Windows GDI 教程(五) 填充图形和画刷

填充图形(Filled Shape)是指封闭状态的图形,常见有的矩形、多边形、圆形、椭圆等等。而画刷(Brush)与画笔(Pen)不同,画笔是专门用于绘制线条,而画刷则专门用于填充如矩形、椭圆形等填充图形。

在 DC 中,画刷与画笔一样,都是使用 SelectObject 函数来选中。并且画刷与画笔可以同时存在,互相并不冲突。

Rectangle

本节的重点是矩形(Rectangle)的绘制,而绘制矩形的函数名也就是英文的单词 Rectangle 。该函数用于在目标DC中绘制矩形的函数,只需要传一个DC的句柄以及矩形的左上点坐标和右下点坐标。

BOOL Rectangle(
  _In_  HDC hdc,		// 目标DC句柄
  _In_  int nLeftRect,	// 左
  _In_  int nTopRect,	// 上
  _In_  int nRightRect,	// 右
  _In_  int nBottomRect	// 下
);

绘制空心矩形

#define RED_COLOR RGB(255,0,0)

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hpen;
	int i;

	// 创建并选择画笔
	hdc = BeginPaint(hwnd, &ps);
	hpen = CreatePen(PS_SOLID, 1, RED_COLOR);
	SelectObject(hdc,hpen);

	// 绘制矩形
	Rectangle(hdc,80, 50, 300, 200);

	// 清理资源
	DeleteObject(hpen);
	EndPaint(hwnd, &ps);
}

效果图:
绘制空心矩形

PS:如果没有用 SelectObject 指定画笔的话,会使用默认画笔(样式:soild、宽度:1px、黑色)

CreateSolidBrush

创建一个实心填充的画刷。

HBRUSH CreateSolidBrush(
  _In_  COLORREF crColor // 指定画刷颜色
);

绘制实心矩形


#define RED_COLOR RGB(255,0,0)
#define BLUE_COLOR RGB(0,0,255)

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HBRUSH hbrush;
	HPEN hpen;

	hdc = BeginPaint(hwnd, &ps);

	hpen = CreatePen(PS_SOLID, 10, RED_COLOR);	// 创建红色实心10px粗的画笔
	hbrush = CreateSolidBrush(BLUE_COLOR); // 创建蓝色画刷

	SelectObject(hdc, hpen);	// 指定画笔
	SelectObject(hdc, hbrush);	// 指定画刷

	// 绘制矩形
	Rectangle(hdc,80, 50, 300, 200);

	// 清理资源
	DeleteObject(hpen);
	DeleteObject(hbrush);
	EndPaint(hwnd, &ps);
}

效果图:
绘制实心矩形

填充图形列表

本节并不专门讲解除了矩形以外的其他图形。因此,以下简单的将绘制填充图形的所有的函数列举出来:

  • Rectangle 绘制一个矩形。
  • RoundRect 绘制一个圆角矩形。
  • Ellipse 绘制一个椭圆形。
  • Pie 绘制一个圆饼扇形。
  • Chord 绘制一个切割椭圆。
  • Polygon 绘制多边形。
  • PolyPolygon 绘制同时绘制多个封闭的多边形。
  • FillRect 使用画刷填充一块矩形区域。
  • FrameRect 使用画刷绘制一块矩形区域的边框。
  • InvertRect 将制定的矩形范围内的像素反色。

更多信息详见: Filled Shape Functions on MSDN

CreateHatchBrush

创建影线画刷。

HBRUSH CreateHatchBrush(
  _In_  int fnStyle,		// 影线样式
  _In_  COLORREF clrref 	// 画刷颜色
);

绘制椭圆、圆角矩形、扇形以及切割椭圆实例

// 画刷样式
int ibrushStyle[4] = {
	HS_VERTICAL,	/* ||||| 垂直 */
	HS_HORIZONTAL,	/* ----- 水平 */
	HS_CROSS,		/* +++++ 正十字 */
	HS_DIAGCROSS	/* xxxxx 斜十字 */
};

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hPen;
	HBRUSH hBrush[4];
	int i;

	hdc = BeginPaint(hwnd, &ps);

	// 初始化4把画刷
	for(i = 0; i < 4; i++)
		hBrush[i] = CreateHatchBrush(ibrushStyle[i], RGB(0,0,255));

	// 画椭圆
	SelectObject(hdc, hBrush[0]);
	Ellipse(hdc, 0,0, 140,100);
	TextOut(hdc,55,100,"椭圆",strlen("椭圆"));

	// 画圆角矩形
	SelectObject(hdc,hBrush[1]);
	RoundRect(hdc, 160,0,  320,100, 30,30);
	TextOut(hdc, 210,100, "圆角矩形",strlen("圆角矩形"));

	// 画扇形
	SelectObject(hdc,hBrush[2]);
	Pie(hdc, 0,130, 140,230, 50,50, 300,300);
	TextOut(hdc,55,230,"扇形",strlen("扇形"));

	// 画切割椭圆(Chord)
	SelectObject(hdc,hBrush[3]);
	Chord(hdc, 160,130, 320,230, 50,50, 600,300);
	TextOut(hdc,210,230,"Chord",strlen("Chord"));

	// 清理资源
	for(i = 0; i < 4; i++)
		 DeleteObject(hBrush[i]);
	EndPaint(hwnd, &ps);
}

效果图:
绘制填充图形

PS:如果绘制椭圆的时候,设置其区域为正方形,则画出来的就是圆形。扇形也同样如此,画出来的就是圆的扇形。

画刷类型列表

这边再给大家拓展一下所有画刷的列表,虽然教程内没有专门讲解,但是说不定其中就有你心动的功能:

  • CreateSolidBrush 创建一个实心颜色(Solid color)画刷
  • CreateHatchBrush 创建一个影线模式(Hatch pattern)的画刷
  • CreatePatternBrush 创建一个位图模式(Bitmap pattern)的画刷
  • CreateBrushIndirect 根据指定的样式、颜色、模式(pattern)来创建一个画刷
  • CreateDIBPatternBrushPt 根据 DIB 模式(pattern from DIB)创建一个画刷

其他更多操作画刷的函数详见 Brush Functions on MSDN

教程索引

上一讲:Windows GDI 教程(四) 文字与字体
下一讲:Windows GDI 教程(六) DC与加载位图

Windows GDI 教程(四) 文字与字体

TextOut

在目标DC的指定坐标处添加一行文字。

BOOL TextOut(
  _In_  HDC hdc,			// DC句柄
  _In_  int nXStart,		// x坐标
  _In_  int nYStart,		// y坐标
  _In_  LPCTSTR lpString,	// 文字内容
  _In_  int cchString		// 文字长度
);

输出文字实例

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;

	hdc = BeginPaint(hwnd, &ps); 
	
	// 设置文字背景色
	SetBkColor(hdc, RGB(0,0,0));
	// 设置文字颜色
	SetTextColor(hdc, RGB(255,255,255));
	// 输出文字 Helloworld
	TextOut(hdc, 50, 50, TEXT("Hello world"), sizeof(TEXT("Hello world")));

	EndPaint(hwnd, &ps); 
}

效果图:
输出文字

选择字体实例

/*
 * 选择字体
 */
HFONT ChooseMyFont() 
{ 
	CHOOSEFONT cf; 
	LOGFONT lf; 
	HFONT hfont; 

	// CHOOSEFONT 结构 
	cf.lStructSize = sizeof(CHOOSEFONT); 
	cf.hwndOwner = (HWND)NULL; 
	cf.hDC = (HDC)NULL; 
	cf.lpLogFont = &lf; 
	cf.iPointSize = 0; 
	cf.Flags = CF_SCREENFONTS; 
	cf.rgbColors = RGB(0,0,0); 
	cf.lCustData = 0L; 
	cf.lpfnHook = (LPCFHOOKPROC)NULL; 
	cf.lpTemplateName = (LPSTR)NULL; 
	cf.hInstance = (HINSTANCE) NULL; 
	cf.lpszStyle = (LPSTR)NULL; 
	cf.nFontType = SCREEN_FONTTYPE; 
	cf.nSizeMin = 0; 
	cf.nSizeMax = 0; 

	// 选择字体对话框 
	ChooseFont(&cf); 
	// 得到HFONT 返回
	hfont = CreateFontIndirect(cf.lpLogFont); 
	return (hfont); 
}

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;
	HFONT hFontSetting;

	hdc = BeginPaint(hwnd, &ps); 

	// 获得字体对象句柄
	hFontSetting = ChooseMyFont();
	// DC 加载字体
	SelectObject(hdc, hFontSetting);

	// 同样输出文字 Helloworld
	TextOut(hdc, 0, 0, TEXT("Hello world"), sizeof(TEXT("Hello world")));

	EndPaint(hwnd, &ps); 
}

效果图:
选择字体
字体+大小

设置排版实例

除了 TextOut 之外,DrawText也可以用于输出文字,他比 TextOut 要多出来的地方就是可以指定文字所在的区域,然后设置文字在该区域内的排版

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;
	RECT rc;

	hdc = BeginPaint(hwnd, &ps); 

	// 获取当前窗口的矩形区域
	GetClientRect(hwnd, &rc); // 实际上就是获取左上和右下的两个坐标
	
	DrawText(
		hdc,
		TEXT("Hello worldnHello Windows GDI"),	// 文字内容
		strlen(TEXT("Hello worldnHello Windows GDI")), // 文字长度
		&rc,		// 输出区域
		DT_CENTER	// 排版格式 居中
	);

	EndPaint(hwnd, &ps); 
}

效果图:
drawtext

教程索引

上一节:Windows GDI 教程(三) 绘制函数图像以及多边形
下一节:Windows GDI 教程(五) 填充图形和画刷

Windows GDI 教程(三) 绘制函数图像以及多边形

SetPixel

画一个点。函数原型:

COLORREF SetPixel(
  _In_  HDC hdc,	// DC句柄
  _In_  int X,		// x 坐标
  _In_  int Y,		// y 坐标
  _In_  COLORREF crColor // 颜色
);

绘制函数图像

上一节有说过在画渐变的时候其实用画点的函数会更好用,这里就使用 SetPixel 函数来绘制一个点。

#include <windows.h>
#include <math.h>

// 用于注册的窗口类名
const char g_szClassName[] = "myWindowClass";

#define POS_X 150		// 坐标系原点 x 
#define POS_Y 130		// 坐标系原点 y 
#define POS_WIDTH 100	// 坐标系宽度
#define BLACK_COLOR RGB(0, 0, 0) // 黑色

/*
 * 要绘制的函数:
 * f(x) = sin(x/10) * 20
 */
double f(double x)
{
	return sin(x/10) * 20;
}

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;   // DC(可画图的内存对象) 的句柄
    HPEN hpen; // 画笔
    int x, y;
	double i;

    hdc = BeginPaint(hwnd, &ps);
    hpen = CreatePen(PS_SOLID, 1, BLACK_COLOR);
    SelectObject(hdc, hpen);

	// 绘制 x 轴
    MoveToEx(hdc, POS_X - POS_WIDTH, POS_Y, NULL);
    LineTo(hdc, POS_X + POS_WIDTH, POS_Y);

	// 绘制 y 轴
	MoveToEx(hdc, POS_X, POS_Y + POS_WIDTH, NULL);
	LineTo(hdc, POS_X, POS_Y - POS_WIDTH);

	// 绘制 f(x) = sin(x/10) * 20 的函数图像
	for (i = -POS_WIDTH; i < POS_WIDTH; i += 0.05) // x每增长0.05绘制1个点
	{
		x = POS_X + i;
		y = POS_Y + f(i);
		// 在 (x,y) 处绘制一个点
		SetPixel(hdc, x, y, BLACK_COLOR);
	}

    DeleteObject(hpen);
    EndPaint(hwnd, &ps);
}

效果图:
绘制函数图像

PS: 这个画点的 SetPixel 函数很简单,各位可以试着用它来修改一下上一节中颜色渐变的例子。

Polyline

Polyline 与 lineto 类似,区别在于 lineto 每画一条线当时就写入显存,而 Polyline则是把指定的坐标数组中所有线都画完之后才写入显存。

BOOL Polyline(
  _In_  HDC hdc,			// DC句柄
  _In_  const POINT *lppt,	// 要绘制的点的数组
  _In_  int cPoints			// 要绘制的点的个数
);

一次绘制多条连通的线示例

void Paint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc;
	HPEN hPen;
	POINT poly[4];

	hdc = BeginPaint(hwnd, &ps);

	// 设置画笔
	hPen = CreatePen(PS_SOLID, 3, RGB(0,0,255));
	SelectObject(hdc, hPen);

	poly[0].x = 100;
	poly[0].y = 150;

	poly[1].x = 200;
	poly[1].y = 150;

	poly[2].x = 150;
	poly[2].y = 50;

	poly[3].x = 250;
	poly[3].y = 50;

	Polyline(hdc, poly, 4);

	EndPaint(hwnd, &ps);
}

效果图:
绘制不规则图形

RECT 结构体

RECT 即 Rectangle 矩形结构体,实际上就存储了两个坐标,一个是左上、一个是右下。

typedef struct 
{
    LONG    left;	// 左
    LONG    top;	// 上
    LONG    right;	// 右
    LONG    bottom; // 下
} RECT;

SetRect

用于设置 RECT 结构体的值。

BOOL SetRect(
    __out LPRECT lprc,	// RECT 结构体指针
    __in int xLeft,		// 左
    __in int yTop,		// 上
    __in int xRight,	// 右
    __in int yBottom	// 下
);

RectVisible

检查目标DC中的指定矩形区域是否可见

WINGDIAPI BOOL WINAPI RectVisible(
	__in HDC hdc, 				// 目标DC
	__in CONST RECT * lprect 	// 指定矩形区域
);

SetViewportOrgEx

修改指定DC中的坐标系原点

BOOL SetViewportOrgEx(
  _In_   HDC hdc,
  _In_   int X,
  _In_   int Y,
  _Out_  LPPOINT lpPoint
);

多边形绘制实例

包括三角形、正方形、五边形、六边形。

POINT aptTriangle[4] = { {50,2}, {98,86},  {2,86}, {50,2}}, 
	aptRectangle[5]  = { { 2,2}, {98,2},  {98,98},  {2,98}, {2,2}}, 
	aptPentagon[6]   = { {50,2}, {98,35}, {79,90}, {21,90}, {2,35}, {50,2}}, 
	aptHexagon[7]    = { {50,2}, {93,25}, {93,75}, {50,98}, {7,75}, {7,25}, {50,2}}; 

void Paint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;   // DC(可画图的内存对象) 的句柄
    HPEN hpen; // 画笔
	RECT rc;

	hdc = BeginPaint(hwnd, &ps); 
	// 初始化矩形 rc 用于表明绘图范围
	SetRect(&rc, 0, 0, 100, 100); 

	// 如果 rc 所包括的范围可见
	if (RectVisible(hdc, &rc)) 
		// 绘制三角形
		Polyline(hdc, aptTriangle, 4); 

	// 修改当前DC坐标系原点为 (100,0)
	SetViewportOrgEx(hdc, 100, 0, NULL); 
	if (RectVisible(hdc, &rc)) 
		// 绘制正方体
		Polyline(hdc, aptRectangle, 5); 

	// 修改当前DC坐标系原点为 (0,100)
	SetViewportOrgEx(hdc, 0, 100, NULL); 
	if (RectVisible(hdc, &rc)) 
		// 绘制五边形
		Polyline(hdc, aptPentagon, 6); 

	// 修改当前DC坐标系原点为 (100,100)
	SetViewportOrgEx(hdc, 100, 100, NULL); 
	if (RectVisible(hdc, &rc)) 
		// 绘制六边形
		Polyline(hdc, aptHexagon, 7); 

	EndPaint(hwnd, &ps); 
}

效果图:
绘制多边形

教程索引

上一节:Windows GDI 教程(二) 画笔的颜色与样式
下一节:Windows GDI 教程(四) 文字与字体