Windows GDI 教程(一) 一个简单的绘图程序

Windows GDI 教程(一) 一个简单的绘图程序

常见的图形编程库,除了 GDI 外还有 GDI+、OpenGL、DirectX等等,GDI 是其中最基础的一个库。所以 GDI 注定了不会有高级应用,有兴趣的就当刷低级怪吧。

在教程的最开始,需要简单的说明一些前置条件。

开发环境与前言

首先是标明开发环境:

操作系统:win7 (xp应该可以,win8未测试)

使用工具:visual studio 2010(或更高)

窗口创建

以前代码的前置问题,首先本教程内的 GDI 画图,在最开始部分主要是在窗口内部绘制(为避免混乱窗口外部,也就是整个桌面的绘制会在很后面的地方讨论)。因此,这里需要对于创建窗口一定的了解。为了让大家可以直接复制完代码就可以在一个文件里面运行,博主准备的代码是手动动态创建窗口的代码,所以这里创建窗口的代码有点长,不过大家不要怕,我们要关注的只是中间的一小部分。这里博主先把代码贴上:

#include

// 用于注册的窗口类名

const char g_szClassName[] = "myWindowClass";

/*

* 第四步,窗口过程

*/

LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch(msg)

{

// 窗口绘制消息

case WM_PAINT:

/*

* 我们只需要在这里调用我们的 GDI 绘制函数就可以,其他地方可以先无视

*/

break;

// 窗口关闭消息

case WM_CLOSE:

DestroyWindow(hwnd);

break;

// 窗口销毁消息

case WM_DESTROY:

PostQuitMessage(0); // 发送离开消息给系统

break;

// 其他消息

default:

// pass 给系统,咱不管

return DefWindowProc(hwnd, msg, wParam, lParam);

}

return 0;

}

/*

* 第一步,注册窗口类

*/

void RegisterMyWindow(HINSTANCE hInstance)

{

WNDCLASSEX wc;

// 1)配置窗口属性

wc.cbSize = sizeof(WNDCLASSEX);

wc.style = 0;

wc.lpfnWndProc = MyWindowProc; // 设置第四步的窗口过程回调函数

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

wc.lpszMenuName = NULL;

wc.lpszClassName = g_szClassName;

wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

// 2)注册

if(!RegisterClassEx(&wc))

{

MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);

exit(0); // 进程退出

}

}

/*

* 第二步,创建窗口

*/

HWND CreateMyWindow(HINSTANCE hInstance, int nCmdShow)

{

HWND hwnd;

hwnd = CreateWindowEx(

WS_EX_CLIENTEDGE,

g_szClassName,

TEXT("我的窗口名称"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, // 出现坐标 x,y 默认分配 窗口宽 400 高 300

NULL, NULL, hInstance, NULL);

if(hwnd == NULL)

{

MessageBox(NULL, TEXT("窗口创建失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);

exit(0); // 进程退出

}

// 显示窗口

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

return hwnd;

}

/*

* 主函数

*/

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

HWND hwnd;

MSG Msg;

// 第一步,注册窗口类

RegisterMyWindow(hInstance);

// 第二步:创建窗口

hwnd = CreateMyWindow(hInstance, nCmdShow);

// 第三步:消息循环

while(GetMessage(&Msg, NULL, 0, 0) > 0)

{

TranslateMessage(&Msg);

DispatchMessage(&Msg);

}

return Msg.wParam;

}

运行效果图:

创建一个空白窗口

这个创建窗口的代码很长,看起来有点吓人,但是学习 GDI 的过程中,这其中几乎是完全不需要记忆的,只要有一定的了解,然后会 copy 就可以了。当然如果你能懂也是更好,以上代码的出处为 《Windows SDK 教程(三) 一些细节以及动态创建控件》,有兴趣的可以去看看。

那么博主也说过了,最开始的时候,这段长长的代码其实注意一个地方就可以了,就是其中的第四步窗口过程中的一个小 case。

/*

* 第四步,窗口过程

*/

LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch(msg)

{

// 窗口绘制消息

case WM_PAINT:

/*

* 只有这一个 case 是我们 GDI 入门中需要注意的

*

* 当程序执行到这个地方的时候,意味着系统像我们的程序发送了 WM_PAINT 消息

* 也就是告诉我们的程序,可以开始绘制窗口的内容了。

*

*/

break;

// 其余略...

}

return 0;

}

这样看来,貌似我们要注意的地方确实很小吧。那么我接着往下走。

PS:默认情况下,系统只会向我们的程序发送一次 WM_PAINT 消息。如果想要再来一次,需要使用 SendMessage 函数,来自己向自己手动发送该消息。

坐标系

GDI 的绘图坐标系与普通的数学坐标系不同,原点 (0,0) 位于左上角。如图:

设备上下文(DC)

DC (设备上下文, Device Contexts)是 GDI 编程中一个很基础同时也很重要的概念。博主以前看过不少网上的资料以及书上的描述,总感觉他们说的都很奇怪。这里博主为了方便大家理解就说说自己的看法:

大家只要把 DC 当成一个保存图像的内存对象即可。当我们使用 GDI 提供的函数去操作 DC 的时候,也就意味着在使用函数去修改保存在这块内存上的图像。

BeginPaint 与 EndPaint

用于从目标窗口获取可画图的 DC,以及关闭这个 DC。

函数原型

HDC BeginPaint(

_In_ HWND hwnd, // 传入想要获取 DC 的窗口句柄

_Out_ LPPAINTSTRUCT lpPaint // 保存目标窗口的绘图信息

);

BOOL EndPaint(

_In_ HWND hWnd, // 目标窗口的句柄

_In_ const PAINTSTRUCT *lpPaint // 目标窗口的绘图信息

);

SelectObject

设置目标 DC 选中指定的对象(如画笔、画刷、图片等等)。

函数原型

HGDIOBJ SelectObject(

_In_ HDC hdc, // 目标 DC 的句柄

_In_ HGDIOBJ hgdiobj // 被选中的对象

);

CreatePen

创建一个画笔(pen)对象。

函数原型

HPEN CreatePen(

_In_ int fnPenStyle, // 样式

_In_ int nWidth, // 宽度

_In_ COLORREF crColor // 颜色

);

MoveToEx

移动绘制的初始位置。未移动则默认是 (0,0)。(C语言基础好的可以联想 fseek 函数)

函数原型

BOOL MoveToEx(

_In_ HDC hdc, // 操作目标DC的句柄

_In_ int X, // x 坐标

_In_ int Y, // y 坐标

_Out_ LPPOINT lpPoint // 保存移动后的当前坐标

);

LineTo

使用当前选中的对象(selected object、通常是画笔)从当前位置绘制一条直线到目标位置。

函数原型

BOOL LineTo(

_In_ HDC hdc, // 目标DC句柄

_In_ int nXEnd, // 目标位置 x 坐标

_In_ int nYEnd // 目标位置 y 坐标

);

绘制直线实例

#include

// 用于注册的窗口类名

const char g_szClassName[] = "myWindowClass";

void Paint(HWND hwnd)

{

// paint struct 绘图结构体,存储目标窗口可以绘图的客户端区域(client area)

PAINTSTRUCT ps;

HDC hdc; // DC(可画图的内存对象) 的句柄

HPEN hpen; // 画笔

// 通过窗口句柄获取该窗口的 DC

hdc = BeginPaint(hwnd, &ps);

// 创建画笔

hpen = CreatePen(PS_SOLID, 1, RGB(255,0,0));

// DC 选择画笔

SelectObject(hdc,hpen);

// (画笔)从初始点移动到 50,50

MoveToEx(hdc, 50, 50, NULL);

// (画笔)从初始点画线到 100,100

LineTo(hdc, 150, 100);

EndPaint(hwnd, &ps);

}

/*

* 第四步,窗口过程

*/

LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch(msg)

{

// 窗口绘制消息

case WM_PAINT:

Paint(hwnd); // 调用我们的 GDI 绘制函数

break;

// 其余略...

}

return 0;

}

// 其余略

运行效果图:

相关推荐

智能插座定时功能如何使用
365购物商城

智能插座定时功能如何使用

📅 09-28 👁️ 887
炒年糕不粘锅的做法
365购物商城

炒年糕不粘锅的做法

📅 08-14 👁️ 5794
长春市第七次全国人口普查公报2020七人普
英国beat365官方APP

长春市第七次全国人口普查公报2020七人普

📅 07-13 👁️ 8356
《永乐票务》怎么退票?退票方法介绍
365bet下载手机版

《永乐票务》怎么退票?退票方法介绍

📅 09-11 👁️ 9804
外星人电脑和雷神电脑哪个好
365购物商城

外星人电脑和雷神电脑哪个好

📅 08-10 👁️ 2712
大卫·比利亚国际比赛进球列表
英国beat365官方APP

大卫·比利亚国际比赛进球列表

📅 09-26 👁️ 3399
网布电脑椅
365购物商城

网布电脑椅

📅 07-22 👁️ 1242
栀“怎么读,栀夏是什么意思?
英国beat365官方APP

栀“怎么读,栀夏是什么意思?

📅 09-02 👁️ 7214
不自驾也能玩遍新西兰? 16天1万5全景巴士游
英国beat365官方APP

不自驾也能玩遍新西兰? 16天1万5全景巴士游

📅 08-19 👁️ 3658