Windows【游戏】编程GDI是什么

前言

上一篇已经实现了基本的引擎类了,漫漫长路才刚刚开始。

先看看下面这个案例,将前面的代码进行一点修改。

1.在Violet.h开头的外部非成员函数声明区域加上下面的声明

void Draw_Line(HDC hdc);

2.在Violet.cpp的Paint函数里加上

	void MyEngine::Paint(HDC hdc) {
		//实际绘图
		Draw_Line(hdc);
	}

3.在main.cpp中加上其实现



void 	Draw_Line(HDC hdc){
	

		HPEN pen;
		int y = 1, x = 1;//row col
	

		//画笔类型
		int style[] = {
			PS_SOLID,                  //  ————
			PS_DASH,				   //  -----------
			PS_DOT,					   //  .............
			PS_DASHDOT,            //  _._._._._
			PS_DASHDOTDOT,	   //  _.._.._.._
			PS_NULL					   //  
		};


		//画彩色方块
		for (int red = 0; red < 256; red++) {
			for (int blue = 0; blue < 256; blue++) {
				//创建画笔
				pen = CreatePen(PS_SOLID, 1, RGB(red, 0, blue));
				//选中画笔
				SelectObject(hdc, pen);

				//开始绘制
				MoveToEx(hdc, x, y, NULL);
				LineTo(hdc, x++, y + 1);
				//删除画笔
				DeleteObject(pen);


			}
			x = 1;
			y += 1;

		}

		//画线段样式
		for (int i = 0; i < sizeof(style) / sizeof(style[0]); i++) {
			//创建画笔
			pen = CreatePen(style[i], 1, RGB(0, 0, 0));
			//选中画笔
			SelectObject(hdc, pen);

			//开始绘制
			MoveToEx(hdc, 30 * i + 380, 50, NULL);
			LineTo(hdc, 30 * i + 380, 200);

		}
		DeleteObject(pen);


		//创建画笔对象
		pen = CreatePen(PS_DASH, 1, RGB(0, 0, 255));
		//选中画笔
		SelectObject(hdc, pen);

		//移动点
		MoveToEx(hdc, 200, 200, NULL);
		//开始绘制
		LineTo(hdc, 400, 200);
		LineTo(hdc, 300, 50);
		LineTo(hdc, 200, 200);

		//删除画笔对象
		DeleteObject(pen);

	

}

显示效果:

在这里插入图片描述

  • 看到绘图的速度没?这么慢的技术简单了解一下就行了。

可以通过调试加断点的方法把上面这个案例的运行步骤找出来。

在运行主循环前,直到显示窗口后,就经常循环在回调函数这。
到了主循环后,同样不时地会运行进回调函数,就当回调函数是另外一个线程罢了。
在这里插入图片描述

这算是以我目前的水平对这个引擎框架的运行步骤做的解释了。

**

Graphic Device Interface

**
图形设备接口。
Windows中的图形主要由gdI32.dll动态链接库输出的函数来处理。这些动态链接库调用显示器、打印机等外部设备的驱动程序,之后由驱动程序来控制相应的硬件,将GDI命令转换为硬件能够理解的代码或者命令。
因此,GDI的主要目的之一是支持与设备无关的图形。

附 上 我 盗 的 图 附上我盗的图
在这里插入图片描述

Windows程序应该能够在Windows支持的任意一种图形输出设备上执行,GDI通过将应用程序和不同输出设备的特性隔离开的方法来达到这一目的。

GDI的局限

虽然可以在显示器上到处移动图形对象,但GDI通常是一个静态的显示系统,只有有限的动画支持。对于不能胜任的动画任务,需要去依赖GDI+、DirectX和OpenGL等。

GDI的函数有以下几类:

  1. 获取和释放设备环境的函数
  2. 获取有关设备环境信息的函数
  3. 绘图函数
  4. 设定和获取设备环境参数的函数
  5. 操作GDI对象的函数

那么,什么是设备环境???

又叫设备内容、设备上下文。实际上是GDI内部保存的数据结构。

设备环境与特定的显示设备(显示器或打印机)相关。对于显示器,设备环境总是与显示器上的特定窗口相关。设备环境中的有些内容是图形属性,这些属性定义了绘图函数工作的细节。

设备环境句柄是GDI函数的窗口通行证,可以以设备环境句柄为参数,调用相应的GDI函数,就可以在显示屏中特定窗口的显示区域上绘图。大部分GDI函数都将设备环境句柄作为第一个参数。当程序需要绘图时,它必须先取得设备环境句柄。取得后,windows用默认的属性值填入设备环境结构。下面的表显示了一些更改设备环境属性的函数:

在这里插入图片描述

GDI绘图

在Windows中使用GDI绘图,首先要进行一系列的准备工作,然后调用GDI绘图函数实现主要的绘图工作,最后进行相应的清理,恢复工作。

  1. 获取设备环境
  2. 根据绘图需求,对设备环境默认属性进行修改
  3. 调用绘图函数绘图
  4. 清理工具、恢复环境

1.获取设备环境

HDC BeginPaint(HWND hWnd,PAINTSTRUCT& ps);

BeginPaint函数将获得设备环境句柄返回,程序应该保留此句柄,之后通过它在窗口上绘图。第一个参数是窗口句柄,用来指定设备环境所关联的窗口。第二个参数是绘图信息结构指针,函数执行时,系统将当期窗口显示区域的无效矩形信息填充在此结构中。绘图时,Windows只在无效区域内绘图。

需要与EndPaint(HWND hWnd,PAINTSTRUCT& ps);配合使用。

并且,上面这种方法只能在WM_PAINT消息处理期间使用

HDC GetDC(HWND hwnd);

有时,程序需要在其他非WM_PAINT消息处理期间绘图,或者程序只需要获得设备环境,从而得到设备环境的某些信息。用到GetDC函数,在调用时需要将设备环境所关联的窗口句柄作为参数,调用后得到该窗口显示区域的设备环境,之后返回HDC类型的设备环境句柄。

int ReleaseDC(HWND hwnd,HDC hdc);

需要与ReleaseDC配合使用。

  • 与从BeginPaint传回设备环境句柄不同,GetDC传回的设备环境句柄具有一个剪取矩形,它等于整个显示区域。可以在显示区域的某一部分绘图,而不是在无效矩形上绘图。另外,与BeginPaint不同,GetDC不会使任何无效区域变为有效。

程序调用GetDC和ReleaseDC对键盘消息和鼠标消息做出反应,此时程序可以立即根据使用者的输入来更新显示区域,而不需要考虑为了窗口的无效区域而使用WM_PAINT。

所以此时绘图是直接在窗口上绘图,而没有将绘图结果保存为有效。
如果此时移动窗口、菜单下来等操作引起窗口更新,那么绘图就变得不正常。
解决方法是在绘图后手动将窗口区域变得有效。

ValidateRect(hwnd,NULL)
将hwnd窗口中的整个显示区域都标记为有效(有效表示不用再画)。

同理,也可以用类似的函数将所标识的区域作为无效区域而得到重绘。

BOOL InvalidateRect(HWND hwnd,RECT& rt,bFlag flag);
  • flag表示重绘时是否擦除背景,函数执行成功返回非0
下面画一个哆啦A梦

按照习惯,先贴代码(3月初开始的吧(逃))

1.声明下面的绘图函数

void Draw(HDC hdc,int cxClient,int cyClient);

2.修改HandleEvent内的代码

long MyEngine::HandleEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {


		static int cxClient, cyClient;

		switch(msg){
		case WM_CREATE:
			SetSleep(false);//激活游戏
			return 0;
		case WM_SIZE:
		{
			cyClient = HIWORD(lParam);
			cxClient = LOWORD(lParam);
		}
			return 0;
		case WM_SETFOCUS:
			return 0;
		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HWND hwnd = GetEngine()->GetWindow();
			HDC hdc = BeginPaint(hwnd, &ps);
			//绘图
			//Paint(hdc);
			Draw(hdc, cxClient, cyClient);
			EndPaint(hwnd, &ps);
		}
			return 0;
		case WM_DESTROY:
			End();
			PostQuitMessage(0);
			return 0;
		}

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

	}

3.在main.cpp中实现画一个哆啦A梦


void Draw(HDC hdc, int cxClient, int cyClient){
	
	TCHAR temp[128] = TEXT("I love fish.com!"), buff[128] = TEXT("");
	HBRUSH hOldBrush;
	RECT rect;
	HPEN hPen, hOldPen;
	POINT apt[128];




	HWND hwnd = vio::MyEngine::GetEngine()->GetWindow();
	GetClientRect(hwnd, &rect);
	//对齐
	SetTextAlign(hdc, TA_CENTER);
	TextOut(hdc, (rect.right - rect.left) / 2 + 150, (rect.bottom - rect.top) / 2, temp, wcslen(temp));


	//1 辅助线
	hPen = CreatePen(PS_DOT, 1, RGB(192, 192, 192));
	hOldPen = (HPEN)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);



	//2 头
	HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 159, 232));
	hOldBrush = (HBRUSH)SelectObject(hdc, hBlueBrush);
	Ellipse(hdc, cxClient / 2 - 120, cyClient / 2 - 200, cxClient / 2 + 120, cyClient / 2 + 40);
	SelectObject(hdc, hOldBrush);


	//3 脸
	Ellipse(hdc, cxClient / 2 - 100, cyClient / 2 - 160, cxClient / 2 + 100, cyClient / 2 + 40);

	//4 眼睛
	Ellipse(hdc, cxClient / 2 - 50, cyClient / 2 - 180, cxClient / 2, cyClient / 2 - 120);
	Ellipse(hdc, cxClient / 2, cyClient / 2 - 180, cxClient / 2 + 50, cyClient / 2 - 120);


	//5 眼珠
	hOldBrush = (HBRUSH)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);


	//6 眼睛里面的光
	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);




	//7 鼻子
	HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
	hOldBrush = (HBRUSH)SelectObject(hdc, hRedBrush);
	Ellipse(hdc, cxClient / 2 - 10, cyClient / 2 - 135, cxClient / 2 + 10, cyClient / 2 - 115);
	// 鼻子上的线
	MoveToEx(hdc, cxClient / 2, cyClient / 2 - 115, NULL);
	LineTo(hdc, cxClient / 2, cyClient / 2 - 30);
	SelectObject(hdc, hOldBrush);


	//8 嘴巴
	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);


	//9 胡子
	//左边胡子
	MoveToEx(hdc, cxClient / 2 - 70, cyClient / 2 - 115, NULL);
	LineTo(hdc, cxClient / 2 - 20, cyClient / 2 - 100);
	MoveToEx(hdc, cxClient / 2 - 80, cyClient / 2 - 85, NULL);
	LineTo(hdc, cxClient / 2 - 20, cyClient / 2 - 85);
	MoveToEx(hdc, cxClient / 2 - 70, cyClient / 2 - 55, NULL);
	LineTo(hdc, cxClient / 2 - 20, cyClient / 2 - 70);
	//右边胡子
	MoveToEx(hdc, cxClient / 2 + 70, cyClient / 2 - 115, NULL);
	LineTo(hdc, cxClient / 2 + 20, cyClient / 2 - 100);
	MoveToEx(hdc, cxClient / 2 + 80, cyClient / 2 - 85, NULL);
	LineTo(hdc, cxClient / 2 + 20, cyClient / 2 - 85);
	MoveToEx(hdc, cxClient / 2 + 70, cyClient / 2 - 55, NULL);
	LineTo(hdc, cxClient / 2 + 20, cyClient / 2 - 70);



	//10 身体的矩形
	hOldBrush = (HBRUSH)SelectObject(hdc, hBlueBrush);
	Rectangle(hdc, cxClient / 2 - 90, cyClient / 2, cxClient / 2 + 90, cyClient / 2 + 150);
	SelectObject(hdc, hOldBrush);

	//11 肚子的圆形
	Ellipse(hdc, cxClient / 2 - 70, cyClient / 2 - 20, cxClient / 2 + 70, cyClient / 2 + 120);
	// 覆盖脖子上的线
	hPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
	hOldPen = (HPEN)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);



	//12 项圈
	hOldBrush = (HBRUSH)SelectObject(hdc, hRedBrush);
	RoundRect(hdc, cxClient / 2 - 95, cyClient / 2 - 5, cxClient / 2 + 95, cyClient / 2 + 10,
		20, 20);
	SelectObject(hdc, hOldBrush);



	//13 铃铛
	HBRUSH hYellowBrush = CreateSolidBrush(RGB(255, 255, 0));
	hOldBrush = (HBRUSH)SelectObject(hdc, hYellowBrush);
	Ellipse(hdc, cxClient / 2 - 15, cyClient / 2, cxClient / 2 + 15, cyClient / 2 + 30);
	RoundRect(hdc, cxClient / 2 - 15, cyClient / 2 + 10, cxClient / 2 + 15, cyClient / 2 + 15,
		2, 2);
	SelectObject(hdc, hRedBrush);
	Ellipse(hdc, cxClient / 2 - 4, cyClient / 2 + 15, cxClient / 2 + 4, cyClient / 2 + 18);
	MoveToEx(hdc, cxClient / 2, cyClient / 2 + 16, NULL);
	LineTo(hdc, cxClient / 2, cyClient / 2 + 30);
	SelectObject(hdc, hOldBrush);


	//14 口袋
	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);


	//15裤裆
	Pie(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);
	//覆盖裤裆下的实线
	hPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
	hOldPen = (HPEN)SelectObject(hdc, hPen);
	MoveToEx(hdc, cxClient / 2 - 20, cyClient / 2 + 150, NULL);
	LineTo(hdc, cxClient / 2 + 20, cyClient / 2 + 150);
	SelectObject(hdc, hOldPen);


	//16 脚掌
	Ellipse(hdc, cxClient / 2 - 110, cyClient / 2 + 130, cxClient / 2 - 10, cyClient / 2 + 170);
	Ellipse(hdc, cxClient / 2 + 110, cyClient / 2 + 130, cxClient / 2 + 10, cyClient / 2 + 170);


	//17 手
	//左手
	hOldBrush = (HBRUSH)SelectObject(hdc, hBlueBrush);
	apt[0].x = cxClient / 2 - 90;
	apt[0].y = cyClient / 2 + 10;
	apt[1].x = cxClient / 2 - 130;
	apt[1].y = cyClient / 2 + 50;
	apt[2].x = cxClient / 2 - 110;
	apt[2].y = cyClient / 2 + 70;
	apt[3].x = cxClient / 2 - 90;
	apt[3].y = cyClient / 2 + 60;
	Polygon(hdc, apt, 4);
	SelectObject(hdc, hOldBrush);
	Ellipse(hdc, cxClient / 2 - 150, cyClient / 2 + 46, cxClient / 2 - 110, cyClient / 2 + 86);
	//右手
	hOldBrush = (HBRUSH)SelectObject(hdc, hBlueBrush);
	apt[0].x = cxClient / 2 + 90;
	apt[0].y = cyClient / 2 + 10;
	apt[1].x = cxClient / 2 + 130;
	apt[1].y = cyClient / 2 + 50;
	apt[2].x = cxClient / 2 + 110;
	apt[2].y = cyClient / 2 + 70;
	apt[3].x = cxClient / 2 + 90;
	apt[3].y = cyClient / 2 + 60;
	Polygon(hdc, apt, 4);
	SelectObject(hdc, hOldBrush);
	Ellipse(hdc, cxClient / 2 + 150, cyClient / 2 + 46, cxClient / 2 + 110, cyClient / 2 + 86);
	//覆盖手与身体之间的线
	hPen = CreatePen(PS_SOLID, 2, RGB(0, 159, 232));
	hOldPen = (HPEN)SelectObject(hdc, hPen);
	MoveToEx(hdc, cxClient / 2 - 90, cyClient / 2 + 10, NULL);
	LineTo(hdc, cxClient / 2 - 90, cyClient / 2 + 50);
	MoveToEx(hdc, cxClient / 2 + 90, cyClient / 2 + 10, NULL);
	LineTo(hdc, cxClient / 2 + 90, cyClient / 2 + 50);

	SelectObject(hdc, hOldBrush);



	//清理空间
	DeleteObject(hPen);
	DeleteObject(hBlueBrush);
	DeleteObject(hRedBrush);
	DeleteObject(hYellowBrush);

}

显示效果:
在这里插入图片描述

  • 上面这个绘制哆啦A梦的例子已经涵盖许多东西了。如何绘制图形,填充图形,字体和获取客户端窗口大小等。

创建GDI绘图工具

获得设备环境后,程序需要先创建相应的绘图工具,并将绘图工具选入设备环境,之后才能调用绘图函数进行绘图工作。

windows预定义了一些基本的绘图工具
画笔画线、画刷画填充图形。

可以通过下面这个函数获得工具

HGDIOBJ GetStockObject(int fnObject);//传入绘图工具ID

在这里插入图片描述

函数返回类型不是具体的工具类型,所以需要强制类型转换一下,例如

HPEN hPen=(HPEN)GetStockObject(WHITE_PEN);
HBRUSH hBrush=(HBRUSH)GetStockObject(LTGRAY_BRUSH);

另外,还可以通过下面这个函数创建一个有宽度、色彩的画笔

HPEN CreatePen(
	int fnPenStyle,//画笔类型
	int nWidth,//画笔宽度
	COLORREF crColor //画笔颜色
);

创建画刷
windows绘图对封闭区域用画刷指定的颜色、方式来填充处理用GetStockObject外,还可以通过创建画刷的函数。下面这个是创建实心画刷。

HBRUSH CreateSolidBrush(COLORREF crColor);

创建一种“影线画刷”,这种风格的画刷对填充条形图的内部和再绘图机上进行绘图最有用

HBRUSH  CreateHatchBrush(int iHatch, COLORREF color);

其画刷类型有下面6种

在这里插入图片描述

用自己创建的工具替换默认的工具,在绘图完成后再替换回来。最后还需要把创建的工具给删除,例如下面拿画笔举的例子。

 HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
 //绘图操作
 SelectObject(hdc, hOldPen);
 DeleteObject(hPen);

用简单的WIN32框架编程,暂时不用Violet引擎了。前面的例子同样可以在WIN32的简单的框架里运行。
下面是用提到的几个知识在WM_PAINT内部绘图的完整的案例
同样用Violet引擎,可以把BeginPaint和EndPaint内部的代码写到一个自定义函数内。

#include<Windows.h>



LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prehInstance, LPSTR lpCmdLine, int nShowCmd)
{

	static TCHAR szAppName[] = TEXT("MyWindow");
	WNDCLASS wndClass = { 0 };




	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("这个程序需要在 Windows NT 才能执行"), szAppName, MB_ICONERROR);
		return -1;
	}


	HWND hwnd = CreateWindow(szAppName, TEXT("Ilovefish.com"), WS_OVERLAPPEDWINDOW, //窗口格式
		CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, //窗口出现位置(左上角)和窗口宽高
		NULL, NULL,//菜单句柄
		hInstance, NULL);


	//MoveWindow(hwnd, 250, 80, 800, 600, true);	
	ShowWindow(hwnd, nShowCmd);
	UpdateWindow(hwnd);//操作系统会绕过消息机制,发送WM_PAINT消息过来


	MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);		//带着消息去找系统
		}
	}

	return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;
	static int cxClient, cyClient;
	HPEN hPen, hOldPen;
	RECT rect;
	HBRUSH hBrush,hOldBrush;



	switch (message)
	{
	case WM_PAINT:
	{
					 TCHAR temp[128] = TEXT("I love fish.com!"), buff[128] = TEXT("");
					 hdc = BeginPaint(hwnd, &ps);
					 GetClientRect(hwnd, &rect);//获得窗口矩形
					 //对齐
					 SetTextAlign(hdc, TA_CENTER);
					 TextOut(hdc, (rect.right - rect.left )/ 2, (rect.bottom - rect.top)/2, temp, wcslen(temp));
#if 0		
					 //画刷 --- 实际是位图
					// hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);//预设的画刷不用删除
					 hBrush = CreateSolidBrush(RGB(255, 255, 0));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Ellipse(hdc, (rect.right - rect.left ) / 4, (rect.bottom - rect.top) / 4, (rect.right - rect.left ) * 3 / 4, (rect.bottom - rect.top) * 3 / 4);
	
#endif
	
					 SetBkColor(hdc,RGB(0,0,0));//设置背景颜色
					 //0   HS_HORIZONTAL
					 hBrush = CreateHatchBrush(HS_HORIZONTAL,RGB(255, 0, 0));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Rectangle(hdc, (rect.right - rect.left) / 7, (rect.bottom - rect.top) / 7, (rect.right - rect.left) * 2 / 7, (rect.bottom - rect.top) * 2 / 7);
					 SelectObject(hdc, hOldBrush);


					 //1  HS_VERTICAL
					 hBrush = CreateHatchBrush(HS_VERTICAL, RGB(0, 255, 0));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Rectangle(hdc, (rect.right - rect.left) * 3 / 7, (rect.bottom - rect.top) / 7, (rect.right - rect.left) * 4 / 7, (rect.bottom - rect.top) * 2 / 7);
					 SelectObject(hdc, hOldBrush);


					 //2  HS_FDIAGONAL
					 hBrush = CreateHatchBrush(HS_FDIAGONAL, RGB(0, 0,255));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Rectangle(hdc, (rect.right - rect.left) * 5 / 7, (rect.bottom - rect.top) / 7, (rect.right - rect.left) * 6 / 7, (rect.bottom - rect.top) * 2 / 7);
					 SelectObject(hdc, hOldBrush);


					 //3  HS_BDIAGONAL
					 hBrush = CreateHatchBrush(HS_BDIAGONAL, RGB(255, 255, 0));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Rectangle(hdc, (rect.right - rect.left) / 7, (rect.bottom - rect.top) * 4/ 7, (rect.right - rect.left) * 2 / 7, (rect.bottom - rect.top) * 5 / 7);
					 SelectObject(hdc, hOldBrush);


					 //4 HS_CROSS
					 hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 255, 255));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Rectangle(hdc, (rect.right - rect.left) * 3 / 7, (rect.bottom - rect.top) * 4/ 7, (rect.right - rect.left) * 4 / 7, (rect.bottom - rect.top) * 5 / 7);
					 SelectObject(hdc, hOldBrush);


					 // 5 HS_DIAGCROSS
					 hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 0, 255));
					 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					 Rectangle(hdc, (rect.right - rect.left) * 5 / 7, (rect.bottom - rect.top)* 4 / 7, (rect.right - rect.left) * 6 / 7, (rect.bottom - rect.top) * 5 / 7);
					 SelectObject(hdc, hOldBrush);



					 //删除替换回来的画刷
					 DeleteObject(SelectObject(hdc, hOldBrush));
					 EndPaint(hwnd, &ps);
					 return 0;
	}
	case WM_CLOSE:
	{
					 //这里判断是否关闭窗口
					 // if (MessageBox(hwnd, TEXT("是否关闭?"), TEXT("请确认"), MB_YESNO) == IDYES){
					 DestroyWindow(hwnd);
					 // }
					 return 0;
	}
	case WM_DESTROY://窗口消失不等于被销毁
		//这里判断是否保存、释放etc
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}


在这里插入图片描述

文本输出
下面是通常使用的格式

DrawText(hdc, //设备环境句柄
	lpStr,   //字符串
	-1,  //字符串长度,-1表示函数会自动计算
	lpRect, //矩形范围
	DT_SINGLELINE | DT_CENTER | DT_VCENTER); //文本输出的格式组合

在这里插入图片描述
另一个文本输出的函数TextOut,能在特定的位置开始输出文本

TextOut(hdc,//设备环境句柄
	xStart, //指定输出位置
	yStart,
	lpStr, //字符串
	iCount); //字符串长度  用 lstrlen() 或 wcslen() 求得

影响文本的绘制效果的一些函数:

SetTextColor(hdc,RGB(255,0,0));//将文字颜色更改为红色
SetBkColor(hdc,RGB(0,255,0));//将背景颜色更改为绿色
SetBkMode(hdc,TRANSPARENT);//OPAQUE表示背景颜色不透明显示 , TRANSPARENT表示透明显示
#include<Windows.h>



LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prehInstance, LPSTR lpCmdLine, int nShowCmd)
{

	static TCHAR szAppName[] = TEXT("MyWindow");
	WNDCLASS wndClass = { 0 };




	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("这个程序需要在 Windows NT 才能执行"), szAppName, MB_ICONERROR);
		return -1;
	}


	HWND hwnd = CreateWindow(szAppName, TEXT("Ilovefish.com"), WS_OVERLAPPEDWINDOW, //窗口格式
		CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, //窗口出现位置(左上角)和窗口宽高
		NULL, NULL,//菜单句柄
		hInstance, NULL);


	//MoveWindow(hwnd, 250, 80, 800, 600, true);	
	ShowWindow(hwnd, nShowCmd);
	UpdateWindow(hwnd);//操作系统会绕过消息机制,发送WM_PAINT消息过来


	MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);		//带着消息去找系统
		}
	}

	return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	HBRUSH hBrush, hOldBrush;
	static TCHAR lpStr[] = TEXT("hello world!!!!!!!");
	static int len = lstrlen(lpStr);
	RECT rect1 = { 50,50,300,150 };
	RECT rect2 = { 350,50,500,150 };

	switch (message)
	{
	case WM_PAINT:
	{
		hdc = BeginPaint(hwnd, &ps);
		SaveDC(hdc);//保存初始的设备环境
		hBrush = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
		hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);

		//绘制rect1,并在其中绘制字符串,居中显示、单行显示
		Rectangle(hdc, rect1.left, rect1.top, rect1.right, rect1.bottom);
		DrawText(hdc, lpStr,-1,&rect1, DT_VCENTER | DT_SINGLELINE | DT_CENTER);

		//绘制rect2,并在其中绘制字符串,不设置任何显示格式
		Rectangle(hdc, rect2.left, rect2.top, rect2.right, rect2.bottom);
		DrawText(hdc, lpStr, -1, &rect2, 0);

		//在(50,200)位置绘制字符串
		TextOut(hdc, 50, 200, lpStr, len);

		//设置背景颜色,字体颜色
		//在(50,220)位置绘制字符串
		SetBkColor(hdc, RGB(220, 220, 220));
		SetTextColor(hdc, RGB(50, 50, 50));
		TextOut(hdc, 50, 220, lpStr, len);


		//设置背景颜色,字体颜色
	//在(250,220)位置绘制字符串
		SetBkColor(hdc, RGB(255, 255, 0));
		SetTextColor(hdc, RGB(255, 0, 0));
		TextOut(hdc, 250, 220, lpStr, len);



		//恢复黑色字体,白色背景
		RestoreDC(hdc, -1);


		//两次输出字符串,分别在(50,250)和(100,250),使得它们重叠
		TextOut(hdc, 50, 250, lpStr, len);
		TextOut(hdc, 100, 250, lpStr, len);


		//设置背景模式为透明
		SetBkMode(hdc, TRANSPARENT);
		TextOut(hdc, 50, 270, lpStr, len);
		TextOut(hdc, 100, 270, lpStr, len);

		DeleteObject(hBrush);
		EndPaint(hwnd, &ps);
		return 0;
	}
	case WM_CLOSE:
	{
		//这里判断是否关闭窗口
		// if (MessageBox(hwnd, TEXT("是否关闭?"), TEXT("请确认"), MB_YESNO) == IDYES){
		DestroyWindow(hwnd);
		// }
		return 0;
	}
	case WM_DESTROY://窗口消失不等于被销毁
		//这里判断是否保存、释放etc
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

在这里插入图片描述

格式化文本

HFONT CreateFont(
	int nHeight,  //字体高度
	int nWidth, //字体宽度
	int nEscapement,  //文本旋转角度
	int nOrientation,   //字符旋转角度
	int fnWeight , //字体是否是粗体
	DWORD fdwItalic,   //是否斜体
	DWORD fdwUnderline,  //是否增加下划线
	DWORD fdwStrikeOut,  //是否增加删除线
	DWORD fdwCharSet,  //字符集
	DWORD fdwOutputPrecision,  //输出精度
	DWORD fdwClipPrecision,  //裁剪精度
	DWORD fdwQuality,  //输出质量
	DWORD fdwPitchAndFamily,   //字体间距和字体族
	LPCTSTR lpszFace       //字体名称

);

在这个创建字体的函数中,参数fnWeight可以取值下面的表中的值
在这里插入图片描述

创建字体并输出:


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	static TCHAR lpStr[] = TEXT("hello world!!!!!!!    世界你好");
	static int len = lstrlen(lpStr);

	HFONT hFont, hOldFont;
	
	switch (message)
	{
	case WM_PAINT:
	{
		hdc = BeginPaint(hwnd, &ps);
		

		//创建新字体
		hFont = CreateFont(
			30,   					 //字体高度
			0,						 //根据高度来设置宽度
			100,					 //文本逆时针选择角度
			900,					 //每个字符逆时针选择角度
			700,					 //粗体
			0,						 //不设置斜体
			0,						 //没有下划线
			1,						 //添加删除线
			1,						 //默认字符集
			0,						 //默认输出精度
			0,						 //默认裁剪精度
			0,						 //默认输出质量
			0,						 //默认字体间距和字体族
			TEXT("隶书")		 //字体
		);							 //
		

		//将新字体选入设备环境,旧字体保存在hOldFont中
		hOldFont = (HFONT)SelectObject(hdc, hFont);


		TextOut(hdc, 50, 100, lpStr, len);

		//设置图形模式
		SetGraphicsMode(hdc, GM_ADVANCED);


		TextOut(hdc, 50, 150, lpStr, len);

		//恢复设备环境
		SelectObject(hdc, hOldFont);
		//删除字体
		DeleteObject(hFont);
		//释放设备环境
		EndPaint(hwnd, &ps);
		return 0;
	}
	case WM_CLOSE:
	{
		//这里判断是否关闭窗口
		// if (MessageBox(hwnd, TEXT("是否关闭?"), TEXT("请确认"), MB_YESNO) == IDYES){
		DestroyWindow(hwnd);
		// }
		return 0;
	}
	case WM_DESTROY://窗口消失不等于被销毁
		//这里判断是否保存、释放etc
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

在这里插入图片描述

最后一个任务

typedef struct tagTEXTMETRIC
{
    LONG        tmHeight;
    LONG        tmAscent;
    LONG        tmDescent;
    LONG        tmInternalLeading;
    LONG        tmExternalLeading;
    LONG        tmAveCharWidth;
    LONG        tmMaxCharWidth;
    LONG        tmWeight;
    LONG        tmOverhang;
    LONG        tmDigitizedAspectX;
    LONG        tmDigitizedAspectY;
    WCHAR       tmFirstChar;
    WCHAR       tmLastChar;
    WCHAR       tmDefaultChar;
    WCHAR       tmBreakChar;
    BYTE        tmItalic;
    BYTE        tmUnderlined;
    BYTE        tmStruckOut;
    BYTE        tmPitchAndFamily;
    BYTE        tmCharSet;
} 

对设备环境中的字体,其详细的信息可以调用GetTextMetrics得到(我经常贴的代码不是源码,有很多重定义和API类型等东西都删掉了)

下面是最后一个案例

#include<Windows.h>
#include<stdio.h>
#include<strsafe.h>


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prehInstance, LPSTR lpCmdLine, int nShowCmd)
{

	static TCHAR szAppName[] = TEXT("MyWindow");
	WNDCLASS wndClass = { 0 };




	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("这个程序需要在 Windows NT 才能执行"), szAppName, MB_ICONERROR);
		return -1;
	}


	HWND hwnd = CreateWindow(szAppName, TEXT("Ilovefish.com"), WS_OVERLAPPEDWINDOW, //窗口格式
		CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, //窗口出现位置(左上角)和窗口宽高
		NULL, NULL,//菜单句柄
		hInstance, NULL);


	//MoveWindow(hwnd, 250, 80, 800, 600, true);	
	ShowWindow(hwnd, nShowCmd);
	UpdateWindow(hwnd);//操作系统会绕过消息机制,发送WM_PAINT消息过来


	MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);		//带着消息去找系统
		}
	}

	return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	static TCHAR lpStr[] = TEXT("hello world!!!!!!!    世界你好");
	static int len = lstrlen(lpStr);

	HFONT hFont, hOldFont;
	TEXTMETRIC tm;//保存字体信息的结构体
	int x = 50, y = 100;//初始输出位置
	TCHAR lpMsg[100];//存放字体信息的字符串
	SIZE size;

	
	switch (message)
	{
	case WM_PAINT:
	{
		hdc = BeginPaint(hwnd, &ps);
		

		//创建新字体
		hFont = CreateFont(
			25,   					 //字体高度
			10,						 //字体宽度
			0,					 //文本逆时针选择角度
			0,					 //每个字符逆时针选择角度
			700,					 //粗体
			0,						 //不设置斜体
			0,						 //没有下划线
			0,						 //没有删除线
			CHINESEBIG5_CHARSET,						 //字符集
			0,						 //默认输出精度
			0,						 //默认裁剪精度
			0,						 //默认输出质量
			0,						 //默认字体间距和字体族
			NULL			 //默认字体
		);							 //
		

		//将新字体选入设备环境,旧字体保存在hOldFont中
		hOldFont = (HFONT)SelectObject(hdc, hFont);


		//取得新字体信息,保存在lpMsg中输出
		GetTextMetrics(hdc, &tm);
		StringCchPrintf(lpMsg,100,TEXT("新字体外行距%d,内行距%d,高%d"),tm.tmExternalLeading,tm.tmInternalLeading,tm.tmHeight);
		TextOut(hdc, 10, 50, lpMsg, lstrlen(lpMsg));

		
		TextOut(hdc, x, y,lpStr, len);


		//获得当前字体下字符串所占大小,保存在size中
		GetTextExtentPoint32(hdc, lpStr, len, &size);


		//利用size计算本行下的下一个位置
		TextOut(hdc, x + size.cx,y , lpStr, len);
		TextOut(hdc, x, y + size.cy, lpStr, len);
		TextOut(hdc, x, y + tm.tmHeight + tm.tmExternalLeading, lpStr, len);



		//恢复设备环境
		SelectObject(hdc, hOldFont);
		//删除字体
		DeleteObject(hFont);
		//释放设备环境
		EndPaint(hwnd, &ps);
		return 0;
	}
	case WM_CLOSE:
	{
		//这里判断是否关闭窗口
		// if (MessageBox(hwnd, TEXT("是否关闭?"), TEXT("请确认"), MB_YESNO) == IDYES){
		DestroyWindow(hwnd);
		// }
		return 0;
	}
	case WM_DESTROY://窗口消失不等于被销毁
		//这里判断是否保存、释放etc
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

在这里插入图片描述
如果在编程中有一些sprintf、swprintf之类的函数用不了,那说明已经被替换成新的安全的字符串处理函数了。一般放在< strsafe.h >里

参考:《Windows游戏编程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

念心科道尊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值