本文为一Windows编程API绘图实战小例子,学习自哔哩哔哩小甲鱼的《Windows程序设计SDK》。最终效果展示:
源码在最后,跟着小甲鱼老师敲一遍代码,这些API能记住不少。
一、大体脉络
二、相关知识细则(来自鱼C工作室)
1.WM_SIZE 消息
- 当主窗口的客户区部分大小改变时,操作系统将给应用程序发送 WM_SIZE 消息
- 应用程序通过窗口过程接收该消息
#define WM_SIZE 0x0005
- wParam:指出窗口的新状态
- wParam 参数可以是下列值之一:
值 |
含义 |
SIZE_MAXHIDE(4) |
当该应用程序的其他窗口被最大化的时候,消息被发送往所有的弹出窗口 |
SIZE_MAXIMIZED(2) |
该窗口被最大化 |
SIZE_MAXSHOW(3) |
当该应用程序的其他窗口已经恢复到原来大小的时候,消息被发送往所有的弹出窗口 |
SIZE_MINIMIZED(1) |
该窗口被最小化 |
SIZE_RESTORED(0) |
该窗口的大小发生变化,但不是最大化(SIZE_MAXIMIZED)或最小化(MINIMIZED) |
- lParam 参数的低 16 位指定了新窗口的宽度;
- lParam 参数的高 16 位制定了新窗口的高度。
- 温馨提醒:可以通过 LOWORD 宏和 HIWORD 宏来获取 lParam 参数的低 16 位和高 16 位。
- 返回值:如果窗口过程响应该消息,必须返回 0。
2.RGB宏
- RGB 宏有三个参数(byRed, byGreen, byBlue),功能是将这三个参数转换为 COLORREF 颜色值。
- 注释:COLORREF 颜色被定义为 DWORD 类型(4 个字节),用于表示 RGB 颜色。
COLORREF RGB(
BYTE byRed,
BYTE byGreen,
BYTE byBlue
);
参数 |
含义 |
byRed |
红色的颜色值 |
byGreen |
绿色的颜色值 |
byBlue |
蓝色的颜色值 |
- 小甲鱼忍不住罗嗦补充一句:色彩中不能再分解的基本色称之为原色,红绿蓝即三原色,将它们按照不同比例混合,可以搭配出所有的颜色。
- 返回值:返回三个参数转换后的 COLORREF 颜色值
- 备注:
- 每个颜色可以指定的颜色值是 0 ~ 255,三个参数同时为 0,即黑色,同时为 255 即白色。
- 通过 GetRValue、GetGValue 和 GetBValue 宏可以分别从 COLORREF 颜色值中获得红色、绿色、蓝色的颜色值。
3.LOWORD 和 HIWORD 宏
- 获得指定 32 位数据的低 16 位数据和高 16 位数据。
- 小甲鱼温馨提醒:不要使用 LOWORD 和 HIWORD 宏去获取鼠标的坐标,因为在多显示器的情况下会得到错误的坐标。应该使用 GET_X_LPARAM 和 GET_Y_LPARAM 宏来获取。
WORD LOWORD(
DWORD dwValue
);
……
WORD HIWORD(
DWORD dwValue
);
- LOWORD(lParam) 返回 lParam 的低 16 位数据;
- HIWORD(lParam) 返回 lParam 的高 16 位数据。
4.POINT 结构
- POINT 结构定义了一个点的 x 坐标和 y 坐标。
- 结构原型:
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
成员 |
含义 |
x |
被定义的点的 x 坐标 |
y |
被定义的点的 y 坐标 |
5.MoveToEx
- 函数功能:MoveToEx 函数将当前绘图位置移动到某个具体的点,同时也可获得之前位置的坐标。
- API 函数原型:注释:_In_ 说明该参数是输入的,_opt_ 说明该参数是可选参数。
BOOL MoveToEx(
_In_ HDC hdc,
_In_ int X,
_In_ int Y,
_Out_ LPPOINT lpPoint
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
X |
指定新位置的 X 轴坐标,按逻辑单位表示坐标 |
Y |
指定新位置的 Y 轴坐标,按逻辑单位表示坐标 |
lpPoint |
1. 一个 POINT 结构的指针,用于获得之前位置的坐标 2. 如果这个值是 NULL,则不会获得之前位置的坐标 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
- MoveExTo 函数将影响所有的绘图函数。
- 在默认的设备环境中,点 (0, 0) 为最初设定的当前位置。
6.LineTo
- LineTo 函数使用当前画笔绘制一条线,线段从当前位置连到一个指定的点 (x, y)。
- 当这个函数调用完毕后,当前位置变成 (x, y)。
- 小甲鱼提示:所绘制的线段并不包含指定的点 (x, y)。
- API 函数原型:注释:_In_ 说明该参数是输入的,_opt_ 说明该参数是可选参数。
BOOL LineTo(
_In_ HDC hdc,
_In_ int nXEnd,
_In_ int nYEnd
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
nXEnd |
1. 线段终点X坐标位置,采用逻辑坐标表示。 2. 这个点不会实际画出来,因为它不属于线段的一部份 |
nYEnd |
1. 线段终点Y坐标位置,采用逻辑坐标表示。 2. 这个点不会实际画出来,因为它不属于线段的一部份 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
7.Rectangle
- Rectangle 函数用于绘制一个矩形。
- 该矩形用当前画笔绘制轮廓,用当前画刷填充。
- API 函数原型:注释:_In_ 说明该参数是输入的。
BOOL Rectangle(
_In_ HDC hdc,
_In_ int xLeft,
_In_ int yTop,
_In_ int xRight,
_In_ int yBottom
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
xLeft |
指定矩形左上角的逻辑 x 坐标 |
yTop |
指定矩形左上角的逻辑 y 坐标 |
xRight |
指定矩形右下角的逻辑 x 坐标 |
yBottom |
指定矩形右下角的逻辑 y 坐标 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
- 该函数既不使用当前位置,也不修改当前位置。
- 写过图形程序的程序员通常熟悉边界偏差(off-by-one)的问题:一些图形程序系统画出的图形包含了右坐标和底坐标表示的点,一些则只画到右坐标和底坐标表示的点之前的一点。Windows 使用后一种方法。
- 如果使用 PS_NULL 画笔,则矩形的尺寸高和宽比实际少一个像素。
8.Ellipse
- Ellipse 函数用于绘制一个椭圆,椭圆的中心是限定矩形的中心。
- 该椭圆用当前画笔绘制轮廓,用当前画刷填充。
- API 函数原型:注释:_In_ 说明该参数是输入的。
BOOL Ellipse(
_In_ HDC hdc,
_In_ int xLeft,
_In_ int yTop,
_In_ int xRight,
_In_ int yBottom
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
xLeft |
指定椭圆限定矩形左上角的逻辑 x 坐标 |
yTop |
指定椭圆限定矩形左上角的逻辑 y 坐标 |
xRight |
指定椭圆限定矩形右下角的逻辑 x 坐标 |
yBottom |
指定椭圆限定矩形右下角的逻辑 y 坐标 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
9.RoundRect
- RoundRect 函数用于绘制一个带圆角的矩形。
- 该矩形用当前画笔绘制轮廓,用当前画刷填充。
- API 函数原型:注释:_In_ 说明该参数是输入的。
BOOL RoundRect(
_In_ HDC hdc,
_In_ int xLeft,
_In_ int yTop,
_In_ int xRight,
_In_ int yBottom,
_In_ int xCornerEllipse,
_In_ int yCornerEllipse
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
xLeft |
指定限定矩形的左上角的逻辑 x 坐标 |
yTop |
指定限定矩形的左上角的逻辑 y 坐标 |
xRight |
指定限定矩形的右下角的逻辑 x 坐标 |
yBottom |
指定限定矩形的右下角的逻辑 y 坐标 |
xCornerEllipse |
指定用来画圆角的椭圆的宽 |
yCornerEllipse |
指定用来画圆角的椭圆的高 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
10.Arc
- API 函数原型:注释:_In_ 说明该参数是输入的。
BOOL Arc(
_In_ HDC hdc,
_In_ int xLeft,
_In_ int yTop,
_In_ int xRight,
_In_ int yBottom,
_In_ int xStart,
_In_ int yStart,
_In_ int xEnd,
_In_ int yEnd
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
xLeft |
指定限定矩形的左上角的逻辑 x 坐标 |
yTop |
指定限定矩形的左上角的逻辑 y 坐标 |
xRight |
指定限定矩形的右下角的逻辑 x 坐标 |
yBottom |
指定限定矩形的右下角的逻辑 y 坐标 |
xStart |
指定圆弧开始的逻辑 x 坐标 |
yStart |
指定圆弧开始的逻辑 y 坐标 |
xEnd |
指定圆弧结束的逻辑 x 坐标 |
yEnd |
指定圆弧结束的逻辑 y 坐标 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
- 点 (xLeft, yTop) 和点 (xRight, yBottom) 指定限定矩形的位置。
- 由指定的限定矩形形成的椭圆定义弧的曲线。
- 弧的起始点 (xStart, yStart) 开始和终点 (xEnd, yEnd) 并不在椭圆上,而是定义为在椭圆的中心的延长线上(不懂的鱼油请看上图)。
- 如果椭圆的起始点和终点是同一个点,那么将绘制整个椭圆。
11.Pie
- Pie 函数绘制一个由弧以及椭圆中心构成的扇形。
- 该扇形用当前画笔绘制轮廓,用当前画刷填充。
- API 函数原型:注释:_In_ 说明该参数是输入的。
BOOL Pie(
_In_ HDC hdc,
_In_ int xLeft,
_In_ int yTop,
_In_ int xRight,
_In_ int yBottom,
_In_ int xStart,
_In_ int yStart,
_In_ int xEnd,
_In_ int yEnd
);
参数 |
含义 |
hdc |
指定设备环境句柄 |
xLeft |
指定限定矩形的左上角的逻辑 x 坐标 |
yTop |
指定限定矩形的左上角的逻辑 y 坐标 |
xRight |
指定限定矩形的右下角的逻辑 x 坐标 |
yBottom |
指定限定矩形的右下角的逻辑 y 坐标 |
xStart |
指定圆弧开始的逻辑 x 坐标 |
yStart |
指定圆弧开始的逻辑 y 坐标 |
xEnd |
指定圆弧结束的逻辑 x 坐标 |
yEnd |
指定圆弧结束的逻辑 y 坐标 |
- 如果函数调用成功,返回值是非 0;
- 如果函数调用失败,返回值是 0。
- 扇形对应的曲线(弧)由限定矩形画出的椭圆所指定。
- 曲线(弧)的起始点 (xStart, yStart) 开始和终点 (xEnd, yEnd) 并不在椭圆上,而是定义为在椭圆的中心的延长线上(不懂的鱼油请看上图)。
- 该函数既不使用当前位置,也不修改当前位置。
三、程序源码
#include
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hprevInstance,PSTR szCmdLine,int iCmdShow)
{
static TCHAR szAppName[] = TEXT("MyWindows");
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, // window class name
TEXT("哆啦A梦"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
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)
{
HDC hdc;
PAINTSTRUCT ps;
HPEN hPen, hOldPen;
HBRUSH hBlueBrush, hOldBrush, hRedBrush, hYellowBrush;
POINT apt[128];
static int cxClient, cyClient;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
//辅助线
hPen = CreatePen(PS_DOT, 1, RGB(192, 192, 192));
hOldPen = SelectObject(hdc, hPen);
MoveToEx(hdc, cxClient / 2, 0, NULL);
LineTo(hdc, cxClient / 2, cyClient);
MoveToEx(hdc, 0, cyClient / 2, NULL);
LineTo(hdc, cxClient, cyClient / 2);
SelectObject(hdc, hOldPen);
//头 直径240
hBlueBrush = CreateSolidBrush(RGB(0, 159, 232)); //借
hOldBrush = SelectObject(hdc, hBlueBrush);
Ellipse(hdc, cxClient / 2 - 120, cyClient / 2 - 200, cxClient / 2 + 120, cyClient / 2 + 40);
SelectObject(hdc, hOldBrush); //还
//脸 直径200
Ellipse(hdc, cxClient / 2 - 100, cyClient / 2 - 160, cxClient / 2 + 100, cyClient / 2 + 40);
//眼睛 长60 宽50
Ellipse(hdc, cxClient / 2 - 50, cyClient / 2 - 180, cxClient / 2, cyClient / 2 - 120);
Ellipse(hdc, cxClient / 2 + 50, cyClient / 2 - 180, cxClient / 2, cyClient / 2 - 120);
//眼珠
hOldBrush = SelectObject(hdc, GetStockObject(BLACK_BRUSH));
Ellipse(hdc, cxClient / 2 - 20, cyClient / 2 - 160, cxClient / 2 - 5, cyClient / 2 - 140);
Ellipse(hdc, cxClient / 2 + 20, cyClient / 2 - 160, cxClient / 2 + 5, cyClient / 2 - 140);
SelectObject(hdc, hOldBrush);
hOldBrush = SelectObject(hdc, GetStockObject(WHITE_BRUSH));
Ellipse(hdc, cxClient / 2 - 15, cyClient / 2 - 155, cxClient / 2 - 10, cyClient / 2 - 145);
Ellipse(hdc, cxClient / 2 + 15, cyClient / 2 - 155, cxClient / 2 + 10, cyClient / 2 - 145);
SelectObject(hdc, hOldBrush);
//鼻子
hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
hOldBrush = SelectObject(hdc, hRedBrush);
Ellipse(hdc, cxClient / 2 - 10, cyClient / 2 - 135, cxClient / 2 + 10, cyClient / 2 -115);
SelectObject(hdc, hOldBrush);
MoveToEx(hdc, cxClient / 2, cyClient / 2 - 115, NULL);
LineTo(hdc, cxClient / 2, cyClient / 2 - 30);
//嘴巴
Arc(hdc, cxClient / 2 - 70, cyClient / 2 - 120, cxClient / 2 + 70, cyClient / 2 - 30,
cxClient / 2 - 60, cyClient / 2 - 50, cxClient / 2 + 60, cyClient / 2 - 50);
//胡须 中上下
MoveToEx(hdc, cxClient / 2 + 20, cyClient / 2 - 85, NULL);
LineTo(hdc, cxClient / 2 + 75, cyClient / 2 - 85);
MoveToEx(hdc, cxClient / 2 - 20, cyClient / 2 - 85, NULL);
LineTo(hdc, cxClient / 2 - 75, cyClient / 2 - 85);
MoveToEx(hdc, cxClient / 2 + 20, cyClient / 2 - 95, NULL);
LineTo(hdc, cxClient / 2 + 70, cyClient / 2 - 105);
MoveToEx(hdc, cxClient / 2 - 20, cyClient / 2 - 95, NULL);
LineTo(hdc, cxClient / 2 - 70, cyClient / 2 - 105);
MoveToEx(hdc, cxClient / 2 + 20, cyClient / 2 - 75, NULL);
LineTo(hdc, cxClient / 2 + 70, cyClient / 2 - 65);
MoveToEx(hdc, cxClient / 2 - 20, cyClient / 2 - 75, NULL);
LineTo(hdc, cxClient / 2 - 70, cyClient / 2 - 65);
//身体
hOldBrush = SelectObject(hdc, hBlueBrush);
Rectangle(hdc, cxClient / 2 - 100, cyClient / 2 - 10, cxClient / 2 + 100, cyClient / 2 + 150);
SelectObject(hdc, hOldBrush);
//肚皮
Ellipse(hdc, cxClient / 2 - 70, cyClient / 2 - 20, cxClient / 2 + 70, cyClient / 2 + 120);//圆
hPen = CreatePen(PS_DOT, 1, RGB(255, 255, 255));//擦除肚皮上面嘴巴下面的圆弧
hOldPen = SelectObject(hdc, hPen);
Arc(hdc, cxClient / 2 - 70, cyClient / 2 - 20, cxClient / 2 + 70, cyClient / 2 + 120,
cxClient / 2 + 60, cyClient / 2 - 10, cxClient / 2 - 60, cyClient / 2 - 10);
SelectObject(hdc, hOldPen);
//围脖
hOldBrush = SelectObject(hdc, hRedBrush);
RoundRect(hdc, cxClient / 2 - 102, cyClient / 2 - 12, cxClient / 2 + 102, cyClient / 2 + 5, 20,20);
SelectObject(hdc, hOldBrush);
//铃铛
hYellowBrush = CreateSolidBrush(RGB(255, 255, 0));
hOldBrush = SelectObject(hdc, hYellowBrush);
Ellipse(hdc, cxClient / 2 - 12, cyClient / 2 - 4, cxClient / 2 + 12, cyClient / 2 + 20);
RoundRect(hdc, cxClient / 2 - 12, cyClient / 2 + 3, cxClient / 2 + 12, cyClient / 2 + 6, 20, 20);
SelectObject(hdc, hRedBrush);
Ellipse(hdc, cxClient / 2 - 5, cyClient / 2 + 8, cxClient / 2 + 5, cyClient / 2 + 18);
SelectObject(hdc, hOldBrush);
//裤腿
Ellipse(hdc, cxClient / 2 - 20, cyClient / 2 + 130, cxClient / 2 + 20, cyClient / 2 + 170);//画圆
hPen = CreatePen(PS_DOT, 1, RGB(255, 255, 255)); //擦除肚皮下面嘴巴上面的圆弧
hOldPen = SelectObject(hdc, hPen);
Arc(hdc, cxClient / 2 - 20, cyClient / 2 + 130, cxClient / 2 + 20, cyClient / 2 + 170,
cxClient / 2 - 20, cyClient / 2 + 150, cxClient / 2 + 20, cyClient / 2 + 150);
SelectObject(hdc, hOldPen);
//口袋
Pie(hdc, cxClient / 2 - 50, cyClient / 2, cxClient / 2 + 50, cyClient / 2 + 100,
cxClient / 2 - 50, cyClient / 2 + 50, cxClient / 2 + 50, cyClient / 2 + 50);
/*Pie(hdc, cxClient / 2 - 50, cyClient / 2, cxClient / 2 + 50, cyClient / 2 + 100,
cxClient / 2 - 50, cyClient / 2 + 50, cxClient / 2 + 50, cyClient / 2 + 50);
画弧线时是逆时针的*/
//脚掌
/*Ellipse(hdc, cxClient / 2 - 100, cyClient / 2 + 130, cxClient / 2 - 20, cyClient / 2 + 170);
Ellipse(hdc, cxClient / 2 + 20, cyClient / 2 + 130, cxClient / 2 + 100, cyClient / 2 + 170);*/
Ellipse(hdc, cxClient / 2 - 110, cyClient / 2 + 135, cxClient / 2 - 10, cyClient / 2 + 165);
Ellipse(hdc, cxClient / 2 + 10, cyClient / 2 + 135, cxClient / 2 + 110, cyClient / 2 + 165);
//胳膊
hOldBrush = SelectObject(hdc, hBlueBrush);
apt[0].x = cxClient / 2 - 100;
apt[0].y = cyClient / 2;
apt[1].x = cxClient / 2 - 150;
apt[1].y = cyClient / 2 + 60;
apt[2].x = cxClient / 2 - 140;
apt[2].y = cyClient / 2 + 80;
apt[3].x = cxClient / 2 - 100;
apt[3].y = cyClient / 2 + 60;
//点是按顺序连接的,下面是错误用例
/*apt[0].x = cxClient / 2 - 100;
apt[0].y = cyClient / 2;
apt[1].x = cxClient / 2 - 150;
apt[1].y = cyClient / 2 + 60;
apt[3].x = cxClient / 2 - 140;
apt[3].y = cyClient / 2 + 80;
apt[2].x = cxClient / 2 - 100;
apt[2].y = cyClient / 2 + 60;*/
Polygon(hdc, apt, 4);
SelectObject(hdc, hOldBrush);
Ellipse(hdc, cxClient / 2 - 168, cyClient / 2 + 60, cxClient / 2 - 138, cyClient / 2 + 90); //左手掌
hOldBrush = SelectObject(hdc, hBlueBrush);
apt[0].x = cxClient / 2 + 100;
apt[0].y = cyClient / 2;
apt[1].x = cxClient / 2 + 150;
apt[1].y = cyClient / 2 + 60;
apt[2].x = cxClient / 2 + 140;
apt[2].y = cyClient / 2 + 80;
apt[3].x = cxClient / 2 + 100;
apt[3].y = cyClient / 2 + 60;
Polygon(hdc, apt, 4);
SelectObject(hdc, hOldBrush);
Ellipse(hdc, cxClient / 2 + 168, cyClient / 2 + 60, cxClient / 2 + 138, cyClient / 2 + 90); //右手掌
//去多余线,增强立体感
hPen = CreatePen(PS_SOLID, 2, RGB(0, 159, 232));
hOldPen = SelectObject(hdc, hPen);
MoveToEx(hdc, cxClient / 2 - 100, cyClient / 2, NULL);;
LineTo(hdc, cxClient / 2 - 100, cyClient / 2 + 50);;
MoveToEx(hdc, cxClient / 2 + 100, cyClient / 2, NULL);;
LineTo(hdc, cxClient / 2 + 100, cyClient / 2 + 50);
SelectObject(hdc, hOldPen);
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}