Windows API 中的 GDI
最后修改于 2023 年 10 月 18 日
图形设备接口(GDI)是用于处理图形的接口。它用于与图形设备(如显示器、打印机或文件)交互。GDI 允许程序员在屏幕或打印机上显示数据,而无需关心特定设备的细节。GDI 将程序员与硬件隔离。从程序员的角度来看,GDI 是一组用于处理图形的 API 函数。GDI 由 2D 矢量图形、字体和图像组成。要开始绘制图形,我们必须获取设备上下文(DC)对象。
当窗口需要重绘时,会生成 WM_PAINT
消息。程序员在窗口的客户区上绘制;包括标题栏在内的周围边框由操作系统自动绘制。
HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint);
BeginPaint
函数准备指定的窗口进行绘制,并用有关绘制的信息填充 PAINTSTRUCT
结构。它返回一个设备上下文的句柄。设备上下文是一个我们执行绘制操作的对象。
BOOL EndPaint(HWND hWnd, const PAINTSTRUCT *lpPaint);
每个绘制操作都以 EndPaint
结束。每次调用 BeginPaint
函数都需要使用此函数,但仅在绘制完成后。
像素
像素是可以在视频显示系统中单独处理的图像的最小元素。SetPixel
是一个在窗口上绘制单个像素的函数。
COLORREF SetPixel(HDC hdc, int x, int y, COLORREF crColor);
该函数的第一个参数是设备上下文的句柄。接下来的两个参数是点的 x 和 y 坐标。最后一个参数是要用于绘制点的颜色。如果函数成功,则返回值是函数将像素设置为的 RGB 值。
#include <windows.h> #include <time.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void DrawPixels(HWND hwnd); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Pixels"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Pixels", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 250, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } srand(time(NULL)); return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: DrawPixels(hwnd); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void DrawPixels(HWND hwnd) { PAINTSTRUCT ps; RECT r; GetClientRect(hwnd, &r); if (r.bottom == 0) { return; } HDC hdc = BeginPaint(hwnd, &ps); for (int i=0; i<1000; i++) { int x = rand() % r.right; int y = rand() % r.bottom; SetPixel(hdc, x, y, RGB(255, 0, 0)); } EndPaint(hwnd, &ps); }
在我们的示例中,我们在窗口的客户区上随机显示 1000 个红色像素。
wc.style = CS_HREDRAW | CS_VREDRAW;
这两个标志导致窗口在调整大小时重绘。
srand(time(NULL));
srand
函数为随机数生成器播种。
case WM_PAINT: DrawPixels(hwnd); break;
绘制是在对 WM_PAINT
消息的响应中执行的。实际的绘制被委托给 DrawPixels
函数。
HDC hdc = BeginPaint(hwnd, &ps);
BeginPaint
函数准备指定的窗口进行绘制。它用有关绘制的信息填充 PAINTSTRUCT
结构。它返回指定窗口的显示设备上下文的句柄。
GetClientRect(hwnd, &r);
我们检索窗口客户区的坐标。我们随机绘制在窗口上,我们需要知道我们目前可以在哪里绘制。
for (int i=0; i<1000; i++) { int x = rand() % r.right; int y = rand() % r.bottom; SetPixel(hdc, x, y, RGB(255, 0, 0)); }
在一千个点上随机绘制在窗口上。SetPixel
函数使用选择的颜色在指定位置绘制一个像素。
EndPaint(hwnd, &ps);
在绘制结束时,我们调用 EndPaint
函数。该函数释放 BeginPaint
检索的显示设备上下文。

线
直线是一个基本的图形图元。它使用两个函数绘制:MoveToEx
和 LineTo
。
BOOL MoveToEx(HDC hdc, int x, int y, LPPOINT lpPoint);
MoveToEx
函数将当前位置更新到指定点,并可选地返回以前的位置。
BOOL LineTo(HDC hdc, int nXEnd, int nYEnd);
LineTo
函数从当前位置到(但不包括)指定点绘制一条线。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Lines"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Lines", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 200, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch(msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); MoveToEx(hdc, 50, 50, NULL); LineTo(hdc, 250, 50); HPEN hWhitePen = GetStockObject(WHITE_PEN); HPEN hOldPen = SelectObject(hdc, hWhitePen); MoveToEx(hdc, 50, 100, NULL); LineTo(hdc, 250, 100); SelectObject(hdc, hOldPen); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
示例绘制了两条线;一条黑色,一条白色。
MoveToEx(hdc, 50, 50, NULL); LineTo(hdc, 250, 50);
在点 (50, 50) 和 (250, 50) 之间绘制一条线。使用默认的 BLACK_PEN
。
HPEN hWhitePen = GetStockObject(WHITE_PEN);
GetStockObject
函数检索内置白色画笔的句柄,该画笔使用 WHITE_PEN
值指定。没有必要(但没有害处)通过调用 DeleteObject
删除库存对象。
HPEN hOldPen = SelectObject(hdc, hWhitePen);
SelectObject
函数将一个对象选入指定的设备上下文 (DC)。新对象替换相同类型的先前对象。
SelectObject(hdc, hOldPen);
我们恢复到旧的 BLACK_PEN
画笔。

矩形
要绘制矩形,我们使用 Rectangle
函数。
BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
该函数的第一个参数是设备上下文的句柄。接下来的两个参数是矩形左上角的 x 和 y 坐标。最后两个参数是矩形右下角的 x, y 坐标。如果函数失败,则返回值是零。如果它成功,则返回值是非零。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Rectangle"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Rectangle", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 200, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch(msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); Rectangle(hdc, 50, 50, 200, 100); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
矩形的轮廓使用当前画笔绘制。背景使用当前画刷绘制。
Rectangle(hdc, 50, 50, 200, 100);
矩形使用 Rectangle
函数绘制。我们使用两个点绘制矩形:左上点和右下点。

贝塞尔曲线
贝塞尔曲线是由数学公式定义的曲线。用于绘制曲线的数学方法是由 Pierre Bézier 在 20 世纪 60 年代后期为雷诺汽车的制造而创建的。
BOOL PolyBezier(HDC hdc, const POINT *lppt, DWORD cPoints);
该函数的第一个参数是设备上下文的句柄。第二个参数是指向 POINT
结构数组的指针,该结构包含曲线的端点和控制点。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"BezierCurve"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Beziér curve", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 500, 200, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; POINT points[4] = { 20, 40, 320, 200, 330, 110, 450, 40 }; switch(msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); PolyBezier(hdc, points, 4); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
在示例中,我们使用 PolyBezier
函数绘制一条曲线。
POINT points[4] = { 20, 40, 320, 200, 330, 110, 450, 40 };
这些点形成贝塞尔曲线。第一个点是起始点。接下来的两个点是控制点。最后一个点是曲线的终点。
PolyBezier(hdc, points, 4);
PolyBezier
函数绘制曲线。

画笔
画笔是一个基本的图形对象。它用于绘制直线、曲线和矩形、椭圆、多边形或其他形状的轮廓。
有两种类型的画笔:修饰画笔和几何画笔。修饰画笔是简单的画笔,宽度固定为 1。它们具有三个属性:宽度、样式和颜色。它们比几何画笔更有效。修饰画笔可以使用 CreatePen
、CreatePenIndirect
或 ExtCreatePen
函数创建。
几何画笔比修饰画笔更复杂。它们有七个属性:宽度、样式、颜色、图案、阴影、端盖和连接样式。几何画笔使用 ExtCreatePen
函数创建。
HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);
CreatePen
函数创建一个具有指定样式、宽度和颜色的逻辑画笔。
HPEN ExtCreatePen(DWORD dwPenStyle, DWORD dwWidth, const LOGBRUSH *lplb, DWORD dwStyleCount, const DWORD *lpStyle);
ExtCreatePen
函数创建一个逻辑修饰或几何画笔。第一个参数是类型、样式、端盖和连接属性的组合。第二个参数是画笔的宽度。第三个参数是指向 LOGBRUSH
结构的指针。该结构定义了物理画刷的样式、颜色和图案。第四个参数是 lpStyle
数组的长度,以 DWORD
为单位。如果 dwPenStyle
不是 PS_USERSTYLE
,则此值必须为零。样式计数限制为 16。最后一个参数是指向一个数组的指针。第一个值指定用户定义样式中第一个短划线的长度,第二个值指定第一个空格的长度,依此类推。如果 dwPenStyle
不是 S_USERSTYLE
,则此指针必须为 NULL
。
创建画笔后,我们使用 SelectObject
函数将其选入应用程序的设备上下文。从这一点开始,应用程序在其客户区中使用此画笔进行任何直线绘制操作。
画笔样式
画笔样式是应用于线条对象的特定图案。有预定义的画笔样式,例如 PS_SOLID
、PS_DASH
、PS_DOT
或 PS_DASHDOT
。也可以创建自定义画笔样式。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void DrawLines(HWND); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Pen styles"; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Pen styles", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 180, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: DrawLines(hwnd); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void DrawLines(HWND hwnd) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HPEN hPen1 = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); HPEN hPen2 = CreatePen(PS_DASH, 1, RGB(0, 0, 0)); HPEN hPen3 = CreatePen(PS_DOT, 1, RGB(0, 0, 0)); HPEN hPen4 = CreatePen(PS_DASHDOT, 1, RGB(0, 0, 0)); HPEN hPen5 = CreatePen(PS_DASHDOTDOT, 1, RGB(0, 0, 0)); HPEN holdPen = SelectObject(hdc, hPen1); MoveToEx(hdc, 50, 30, NULL); LineTo(hdc, 300, 30); SelectObject(hdc, hPen2); MoveToEx(hdc, 50, 50, NULL); LineTo(hdc, 300, 50); SelectObject(hdc, hPen2); MoveToEx(hdc, 50, 70, NULL); LineTo(hdc, 300, 70); SelectObject(hdc, hPen3); MoveToEx(hdc, 50, 90, NULL); LineTo(hdc, 300, 90); SelectObject(hdc, hPen4); MoveToEx(hdc, 50, 110, NULL); LineTo(hdc, 300, 110); SelectObject(hdc, holdPen); DeleteObject(hPen1); DeleteObject(hPen2); DeleteObject(hPen3); DeleteObject(hPen4); DeleteObject(hPen5); EndPaint(hwnd, &ps); }
在我们的示例中,我们使用五种不同的画笔样式绘制了五条不同的线。
case WM_PAINT: DrawLines(hwnd); break;
实际的绘制被委托给 DrawLines
函数。
HPEN hPen1 = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
CreatePen
函数创建一个具有指定样式、宽度和颜色的逻辑画笔。PS_SOLID
代表实线画笔。我们使用 RGB
宏为画笔生成颜色。
SelectObject(hdc, hPen1);
要激活画笔,我们调用 SelectObject
函数。
MoveToEx(hdc, 50, 30, NULL); LineTo(hdc, 300, 30);
要绘制线条,我们使用 MoveToEx
和 LineTo
函数。
DeleteObject(hPen1); DeleteObject(hPen2); DeleteObject(hPen3); DeleteObject(hPen4); DeleteObject(hPen5);
最后,我们清理资源。

线条连接
线条可以使用三种不同的连接样式连接:PS_JOIN_BEVEL
、PS_JOIN_MITEl
和 PS_JOIN_ROUND
。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void DoDrawing(HWND); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Pens"; wc.hInstance = hInstance; wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Line joins", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 450, 200, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: DoDrawing(hwnd); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void DoDrawing(HWND hwnd) { LOGBRUSH brush; COLORREF col = RGB(0, 0, 0); DWORD pen_style = PS_SOLID | PS_JOIN_MITER | PS_GEOMETRIC; brush.lbStyle = BS_SOLID; brush.lbColor = col; brush.lbHatch = 0; PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HPEN hPen1 = ExtCreatePen(pen_style, 8, &brush, 0, NULL); HPEN holdPen = SelectObject(hdc, hPen1); POINT points[5] = { { 30, 30 }, { 130, 30 }, { 130, 100 }, { 30, 100 }, { 30, 30}}; Polygon(hdc, points, 5); pen_style = PS_SOLID | PS_GEOMETRIC | PS_JOIN_BEVEL; HPEN hPen2 = ExtCreatePen(pen_style, 8, &brush, 0, NULL); SelectObject(hdc, hPen2); DeleteObject(hPen1); POINT points2[5] = { { 160, 30 }, { 260, 30 }, { 260, 100 }, { 160, 100 }, {160, 30 }}; MoveToEx(hdc, 130, 30, NULL); Polygon(hdc, points2, 5); pen_style = PS_SOLID | PS_GEOMETRIC | PS_JOIN_ROUND; HPEN hPen3 = ExtCreatePen(pen_style, 8, &brush, 0, NULL); SelectObject(hdc, hPen3); DeleteObject(hPen2); POINT points3[5] = { { 290, 30 }, { 390, 30 }, { 390, 100 }, { 290, 100 }, {290, 30 }}; MoveToEx(hdc, 260, 30, NULL); Polygon(hdc, points3, 5); SelectObject(hdc, holdPen); DeleteObject(hPen3); EndPaint(hwnd, &ps); }
在示例中,我们显示了矩形形状上的三种线条连接类型。
pen_style = PS_SOLID | PS_GEOMETRIC | PS_JOIN_BEVEL; HPEN hPen2 = ExtCreatePen(pen_style, 8, &brush, 0, NULL);
ExtCreatePen
函数创建一个具有 PS_JOIN_BEVEL
连接的实心几何画笔。
POINT points2[5] = { { 160, 30 }, { 260, 30 }, { 260, 100 }, { 160, 100 }, {160, 30 }}; MoveToEx(hdc, 130, 30, NULL); Polygon(hdc, points2, 5);
从提供的点,我们使用 Polygon
函数创建一个矩形形状。

画刷
画刷是一个基本的图形对象。它用于绘制图形形状的背景,例如矩形、椭圆或多边形。画刷可以是纯色、阴影或自定义位图图案。
纯色画刷
纯色画刷是一种颜色。它使用 CreateSolidBrush
函数创建。
HBRUSH CreateSolidBrush(COLORREF crColor);
CreateSolidBrush
函数创建一个具有指定纯色的画刷。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void DrawRectangles(HWND); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Brush"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Solid Brush", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 220, 240, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: DrawRectangles(hwnd); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void DrawRectangles(HWND hwnd) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HPEN hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0)); HPEN holdPen = SelectObject(hdc, hPen); HBRUSH hBrush1 = CreateSolidBrush(RGB(121, 90, 0)); HBRUSH hBrush2 = CreateSolidBrush(RGB(240, 63, 19)); HBRUSH hBrush3 = CreateSolidBrush(RGB(240, 210, 18)); HBRUSH hBrush4 = CreateSolidBrush(RGB(9, 189, 21)); HBRUSH holdBrush = SelectObject(hdc, hBrush1); Rectangle(hdc, 30, 30, 100, 100); SelectObject(hdc, hBrush2); Rectangle(hdc, 110, 30, 180, 100); SelectObject(hdc, hBrush3); Rectangle(hdc, 30, 110, 100, 180); SelectObject(hdc, hBrush4); Rectangle(hdc, 110, 110, 180, 180); SelectObject(hdc, holdPen); SelectObject(hdc, holdBrush); DeleteObject(hPen); DeleteObject(hBrush1); DeleteObject(hBrush2); DeleteObject(hBrush3); DeleteObject(hBrush4); EndPaint(hwnd, &ps); }
在示例中,我们创建了 4 个矩形,分别用 4 种不同的纯色填充。
HBRUSH hBrush1 = CreateSolidBrush(RGB(121, 90, 0));
在这里,我们创建一个纯色画刷。
HBRUSH holdBrush = SelectObject(hdc, hBrush1);
一个新画刷被选入设备上下文。

阴影画刷
有六种预定义的阴影画刷可用。在我们的示例中,我们展示了所有这些。
HBRUSH CreateHatchBrush(int fnStyle, COLORREF clrref);
CreateHatchBrush
函数创建一个具有指定阴影图案和颜色的画刷。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void DrawRectangles(HWND hwnd); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_VREDRAW | CS_HREDRAW; wc.lpszClassName = L"Brush"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Hatch brushes", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 220, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: DrawRectangles(hwnd); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } void DrawRectangles(HWND hwnd) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HPEN hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0)); HPEN holdPen = SelectObject(hdc, hPen); HBRUSH hBrush1 = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 0, 0)); HBRUSH hBrush2 = CreateHatchBrush(HS_FDIAGONAL, RGB(0, 0, 0)); HBRUSH hBrush3 = CreateHatchBrush(HS_CROSS, RGB(0, 0, 0)); HBRUSH hBrush4 = CreateHatchBrush(HS_HORIZONTAL, RGB(0, 0, 0)); HBRUSH hBrush5 = CreateHatchBrush(HS_DIAGCROSS, RGB(0, 0, 0)); HBRUSH hBrush6 = CreateHatchBrush(HS_VERTICAL, RGB(0, 0, 0)); HBRUSH holdBrush = SelectObject(hdc, hBrush1); DWORD col = GetSysColor(COLOR_BTNFACE); SetBkColor(hdc, col); Rectangle(hdc, 30, 30, 100, 80); SelectObject(hdc, hBrush2); Rectangle(hdc, 110, 30, 180, 80); SelectObject(hdc, hBrush3); Rectangle(hdc, 190, 30, 260, 80); SelectObject(hdc, hBrush4); Rectangle(hdc, 30, 110, 100, 160); SelectObject(hdc, hBrush5); Rectangle(hdc, 110, 110, 180, 160); SelectObject(hdc, hBrush6); Rectangle(hdc, 190, 110, 260, 160); SelectObject(hdc, holdPen); SelectObject(hdc, holdBrush); DeleteObject(hPen); DeleteObject(hBrush1); DeleteObject(hBrush2); DeleteObject(hBrush3); DeleteObject(hBrush4); DeleteObject(hBrush5); DeleteObject(hBrush6); EndPaint(hwnd, &ps); }
此示例与上一个示例非常相似。我们仅使用一个新函数调用 CreateHatchBrush
。
HBRUSH hBrush1 = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 0, 0));
创建一个对角阴影画刷。
HBRUSH holdBrush = SelectObject(hdc, hBrush1);
画刷被选入设备上下文。将返回旧画刷的句柄。
DeleteObject(hBrush1);
画刷对象被删除。

自定义画刷
可以使用 CreatePatternBrush
函数创建自定义画刷。
HBRUSH CreatePatternBrush(HBITMAP hbmp);
该函数接受要用于创建画刷的位图的句柄。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Custom brush"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Custom brush", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 200, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static HBITMAP hBtm; UINT bits[8] = { 0x111111ff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; switch(msg) { case WM_CREATE: hBtm = CreateBitmap(8, 8, 1, 1, (LPBYTE) bits); break; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); HBRUSH hCustomBrush = CreatePatternBrush(hBtm); HBRUSH hOldBrush = SelectObject(hdc, hCustomBrush); SelectObject(hdc, GetStockObject(NULL_PEN)); Rectangle(hdc, 20, 20, 250, 160); SelectObject(hdc, hOldBrush); DeleteObject(hCustomBrush); SelectObject(hdc, GetStockObject(BLACK_PEN)); EndPaint(hwnd, &ps); break; case WM_DESTROY: DeleteObject(hBtm); PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
该示例绘制一个矩形;其内部填充自定义画刷图案。
hBtm = CreateBitmap(8, 8, 1, 1, (LPBYTE) bits);
我们使用 CreateBitmap
函数创建一个位图图案。
HBRUSH hCustomBrush = CreatePatternBrush(hBtm);
CreatePatternBrush
函数从提供的位图创建一个画刷对象。
HBRUSH hOldBrush = SelectObject(hdc, hCustomBrush);
我们使用 SelectObject
函数选择自定义画刷。
SelectObject(hdc, GetStockObject(NULL_PEN));
我们不绘制矩形的轮廓。当我们选择 NULL_PEN
时,不会绘制轮廓。
Rectangle(hdc, 20, 20, 250, 160);
矩形使用 Rectangle
函数绘制;它的内部用选定的自定义画刷绘制。

形状
形状是更复杂的几何对象。我们在以下示例中绘制各种几何形状。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Shapes"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Shapes", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 390, 230, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; const POINT polygon[10] = { 30, 145, 85, 165, 105, 110, 65, 125, 30, 105 }; switch(msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); Ellipse(hdc, 30, 30, 120, 90); RoundRect(hdc, 150, 30, 240, 90, 15, 20); Chord(hdc, 270, 30, 360, 90, 270, 45, 360, 45); Polygon(hdc, polygon, 5); Rectangle(hdc, 150, 110, 230, 160); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
在我们的示例中,我们创建了一个椭圆、一个圆角矩形、一个弦、一个多边形和一个矩形。
Ellipse(hdc, 30, 30, 120, 90);
Ellipse
函数绘制一个椭圆。Ellipse
的参数是边界矩形左上角和右下角的 x 和 y 坐标。椭圆在该矩形内绘制。
RoundRect(hdc, 150, 30, 240, 90, 15, 20);
RoundRect
函数绘制一个圆角矩形。RoundRect
的参数是边界矩形左上角和右下角的 x 和 y 坐标。最后两个参数是用于绘制圆角的椭圆的宽度和高度。
Chord(hdc, 270, 30, 360, 90, 270, 45, 360, 45);
Chord
函数绘制一个弦。弦是由椭圆和线段的交集限定的区域。前四个参数是边界矩形左上角的 x 和 y 坐标以及右下角的 x 和 y 坐标。接下来的四个参数是定义弦开始的径向的 x 和 y 坐标以及定义弦结束的径向的 x 和 y 坐标。
Polygon(hdc, polygon, 5);
Polygon
函数绘制一个由两条或更多条通过直线连接的顶点组成的多边形。多边形是指向 POINT
结构数组的指针,该数组指定多边形的顶点。最后一个参数是数组中的点数。
Rectangle(hdc, 150, 110, 230, 160);
Rectangle
函数绘制一个矩形。该函数的参数是矩形左上角和右下角的 x 和 y 坐标。

星形
在下面的示例中,我们使用 Polyline
函数绘制一个星形。
BOOL Polyline(HDC hdc, const POINT *lppt, int cPoints);
Polyline
函数通过连接指定数组中的点来绘制一系列线段。该函数的第一个参数是设备上下文的句柄。第二个参数是指向 POINT
结构数组的指针。第三个参数是数组中的点数。这个数字必须大于或等于二。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Star"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Star", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 300, 250, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; POINT points[11] = { { 10, 85 }, { 85, 75 }, { 110, 10 }, { 135, 75 }, { 210, 85 }, { 160, 125 }, { 170, 190 }, { 110, 150 }, { 50, 190 }, { 60, 125 }, { 10, 85 } }; switch(msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); Polyline(hdc, points, 11); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
该示例绘制一个星形对象。
POINT points[11] = { { 10, 85 }, { 85, 75 }, { 110, 10 }, { 135, 75 }, { 210, 85 }, { 160, 125 }, { 170, 190 }, { 110, 150 }, { 50, 190 }, { 60, 125 }, { 10, 85 } };
这是一个星形的 POINTS
数组。
Polyline(hdc, points, 11);
Polyline
函数绘制星形。

文本
TextOutW
函数使用当前选定的字体、背景颜色和文本颜色,在指定位置写入一个字符串。
BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cchString);
该函数的第一个参数是设备上下文的句柄。接下来的两个参数是系统用于对齐字符串的参考点的 x 和 y 坐标。第三个参数是指向要绘制的字符串的指针。最后一个参数是字符串的长度。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg ; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Sonnet 55"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Sonnet 55", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 390, 350, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; DWORD color; HFONT hFont, holdFont; static wchar_t *ver1 = L"Not marble, nor the gilded monuments"; static wchar_t *ver2 = L"Of princes, shall outlive this powerful rhyme;"; static wchar_t *ver3 = L"But you shall shine more bright in these contents"; static wchar_t *ver4 = L"Than unswept stone, besmear'd with sluttish time."; static wchar_t *ver5 = L"When wasteful war shall statues overturn,"; static wchar_t *ver6 = L"And broils root out the work of masonry,"; static wchar_t *ver7 = L"Nor Mars his sword, nor war's quick fire shall burn"; static wchar_t *ver8 = L"The living record of your memory."; static wchar_t *ver9 = L"'Gainst death, and all oblivious enmity"; static wchar_t *ver10 = L"Shall you pace forth; your praise shall still find room"; static wchar_t *ver11 = L"Even in the eyes of all posterity"; static wchar_t *ver12 = L"That wear this world out to the ending doom."; static wchar_t *ver13 = L"So, till the judgment that yourself arise,"; static wchar_t *ver14 = L"You live in this, and dwell in lovers' eyes."; switch(msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); color = GetSysColor(COLOR_BTNFACE); SetBkColor(hdc, color); hFont = CreateFontW(15, 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0, 0, 0, 0, 0, L"Georgia"); holdFont = SelectObject(hdc, hFont); TextOutW(hdc, 50, 20, ver1, lstrlenW(ver1)); TextOutW(hdc, 50, 40, ver2, lstrlenW(ver2)); TextOutW(hdc, 50, 60, ver3, lstrlenW(ver3)); TextOutW(hdc, 50, 80, ver4, lstrlenW(ver4)); TextOutW(hdc, 50, 100, ver5, lstrlenW(ver5)); TextOutW(hdc, 50, 120, ver6, lstrlenW(ver6)); TextOutW(hdc, 50, 140, ver7, lstrlenW(ver7)); TextOutW(hdc, 50, 160, ver8, lstrlenW(ver8)); TextOutW(hdc, 50, 180, ver9, lstrlenW(ver9)); TextOutW(hdc, 50, 200, ver10, lstrlenW(ver10)); TextOutW(hdc, 50, 220, ver11, lstrlenW(ver11)); TextOutW(hdc, 50, 240, ver12, lstrlenW(ver12)); TextOutW(hdc, 50, 260, ver13, lstrlenW(ver13)); TextOutW(hdc, 50, 280, ver14, lstrlenW(ver14)); SelectObject(hdc, holdFont); DeleteObject(hFont); EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
我们使用 TextOutW
函数在窗口上绘制几行诗句。
color = GetSysColor(COLOR_BTNFACE); SetBkColor(hdc, color);
默认情况下,如果我们在窗口的客户区上绘制一些文本,则背景设置为白色。我们可以通过使用 SetBkColor
函数设置背景颜色来更改此设置。我们使用了典型的 Windows 灰色。GetSysColor
函数用于获取在按钮、标题或窗口控件的背景中使用的系统颜色。
hFont = CreateFontW(15, 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0, 0, 0, 0, 0, L"Georgia"); holdFont = SelectObject(hdc, hFont);
在这里,我们使用 CreateFontW
函数创建一个字体对象。该函数有 14 个参数;我们不必指定所有这些参数。我们仅指定字体大小、字体粗细和字体名参数。
TextOutW(hdc, 50, 20, verse1, lstrlenW(verse1));
文本使用 TextOutW
函数绘制到窗口上。字符串的长度由 lstrlenW
函数确定。

绘制位图
位图是一个图形对象,用于创建、操作和将图像存储为磁盘上的文件。BMP 是 Windows 的原生位图格式,用于存储几乎任何类型的位图数据。
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSW wc = {0}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpszClassName = L"Draw Bitmap"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); RegisterClassW(&wc); CreateWindowW(wc.lpszClassName, L"Draw Bitmap", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 280, 220, NULL, NULL, hInstance, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap; HDC hdc; PAINTSTRUCT ps; BITMAP bitmap; HDC hdcMem; HGDIOBJ oldBitmap; switch(msg) { case WM_CREATE: hBitmap = (HBITMAP) LoadImageW(NULL, L"C:\\prog\\slovakia.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); if (hBitmap == NULL) { MessageBoxW(hwnd, L"Failed to load image", L"Error", MB_OK); } break; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); hdcMem = CreateCompatibleDC(hdc); oldBitmap = SelectObject(hdcMem, hBitmap); GetObject(hBitmap, sizeof(bitmap), &bitmap); BitBlt(hdc, 5, 5, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, oldBitmap); DeleteDC(hdcMem); EndPaint(hwnd, &ps); break; case WM_DESTROY: DeleteObject(hBitmap); PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); }
该示例在窗口上绘制斯洛伐克国旗。图片为 BMP 文件格式。
static HBITMAP hBitmap;
HBITMAP
是一个位图对象的句柄。
BITMAP bitmap;
BITMAP
结构定义位图的类型、宽度、高度、颜色格式和位值。
hBitmap = (HBITMAP) LoadImageW(NULL, L"C:\\prog\\slovakia.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
LoadImageW
函数从磁盘加载 BMP 图像。它返回位图的句柄。
GetObject(hBitmap, sizeof(bitmap), &bitmap);
GetObject
函数将有关位图的信息存储在提供的 BITMAP
结构中。
hdcMem = CreateCompatibleDC(hdc);
CreateCompatibleDC
函数创建一个与应用程序当前屏幕兼容的内存设备上下文。
oldBitmap = SelectObject(hdcMem, hBitmap);
SelectObject
函数将一个对象选入内存设备上下文。必须将位图选入内存设备上下文,然后才能将其用于任何事情。
BitBlt(hdc, 5, 5, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
BitBlt
函数执行位块传输,将与像素矩形相对应的颜色数据从指定的源设备上下文传输到目标设备上下文。
SelectObject(hdcMem, oldBitmap);
在完成使用新对象进行绘制后,应用程序应始终用原始的默认对象替换新对象。
DeleteDC(hdcMem);
与内存设备上下文关联的资源被释放。

在 Windows API 教程的这一部分,我们做了一些绘制。