在使用Win32编程时,我们常常要输出文本到窗口上,Windows所有的文本字符或者图形输出都是通过图形设备接口(GDI)进行的,Windows的三大核心组件之一的GDI32.dll封装了所有的文本和图像输出。
- GDI基本知识
Windows下要绘图和输出文本,都是通过GDI(Graphics Device Interface,图形设备接口)完成的,GDI是windows在绘制图文时的设备上下文环境,包括画笔、画刷、字体、位图等多种与绘制有关的对象。设备环境(DC)在绘制中起至关重要的作用。几乎所有的绘制(包括图形和文本)都与设备环境相关,注意“环境”的意义,就跟我们在画布上绘画和写字一样,绘制时的画布是哪个,用的什么笔,什么颜色,填充整个画布时用的什么刷子等等,这就是我们的绘制时的环境,而Windows绘图的DC设备上下文就是一样的道理。设备环境句柄(HDC)就是用来描述DC的句柄,可以说,只要有了这个句柄,就具备了在窗口上输出图形和文本的条件。你获得了窗口客户区的HDC,就可以在窗口客户区上画;你获得了窗口的非客户区HDC,就可以在它上面画;你获得了桌面HDC,就可以直接在桌面上画……
获取设备环境句柄的方法有两种:一是处理WM_PAINT消息时,通过BeginPaint函数返回。另外一种就是通过GetDC、GetWindowDC的API函数获取。
- 通过WM_PAINT消息获取DC
Windows在检测到需要重新绘制或者刷新窗口时,会主动要求处理WM_PAINT消息。比如在如下情况下就会主动求处理:
- 用户移动一个窗口,导致原来被盖住的部分窗口显示出来。
- 用户调整窗口的大小,并且窗口风格类型设置为CS_HREDRAW和CS_VREDRAW。
- 程序调用ScrollWindow或者ScrollDC函数滚动客户区。
- 程序调用InvalidateRect或者InvalidateRgn函数,该函数显示生产一条WM_PAINT消息。
我们可以在该消息中完成图文绘制,该消息的处理具有特定的格式,必须在实际绘制前调用BeginPaint,在绘制完成后调用EndPaint函数,也就是说我们需要把所有绘制的功能都放到这两个函数之间,并且HDC也只能在这之间使用,不能保存起来在其它地方使用。使用WM_PAINT有一个好处,就是windows会自己计算哪些区域需要更新,也就是说只有真正变化的地方才会更新,这样更新的代价会降低到最小。
- 通过API函数获取HDC
我们还可以通过GetDC、GetWindowDC函数来获取HDC,但是要注意,通过这个来获取的HDC,可以保存起来在其它时候使用,但是要记住一旦窗口有更新,必须想办法重新绘制,否则就会消失了。最后在使用完毕后需要调用ReleaseDC来释放,否则会造成资源泄露。
- 创建特定字体
我们平时最常见的文本输出是不需要自己创建字体的,因为常见的对象都有系统预定义好的。如果想输出点特殊(非系统预定义的)字体,就需要我们创建并自动选入设备环境。创建字体主要有CreateFont和CreateFontIndirect,这两个函数的参数都很多,基本一样,具体用法看后面的实例。
- 实现文本绘制
有了上面的基础,我们就可以通过Windows的API来完成文本输出了,常用的文本输出函数有TextOut、DrawText、DrawTextExt、ExtTextOut等,这些函数基本都有相似的参数,比如hdc,坐标位置,字符串。下面TextOut、DrawText、ExtTextOut为例来说明如何在Windows窗口中如何输出文本,其它请查看MSDN的用法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include <windows.h> #include <tchar.h> static TCHAR szAppName[] = TEXT("Textout"); static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { 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 szAppName, // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position 400, // initial x size 300, // 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; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT ps; switch (message) { case WM_CREATE: return 0; case WM_PAINT: { RECT rect = {10, 30, 100, 50}; TCHAR str[] = TEXT("English and 中文"); hDC = BeginPaint(hWnd, &ps); TextOut(hDC, 10, 10, str, _tcslen(str)); SetTextColor(hDC, RGB(255,0,0)); DrawText(hDC, str, -1, &rect, DT_LEFT|DT_VCENTER); SetTextColor(hDC, RGB(0,255,0)); INT dx[] = {8,8,8,8,16,8,8,8,16,8,8,8,10}; ExtTextOut(hDC, 10, 50, 0, &rect, str, _tcslen(str), dx); SetTextColor(hDC, RGB(0,0,255)); rect.right = 110; rect.top = 70; rect.bottom = 82; ExtTextOut(hDC, 10, rect.top, ETO_CLIPPED, &rect, str, _tcslen(str), dx); HFONT hFont = CreateFont(96, // nHeight, 所创建字体的字符高度 0, // nWidth, 字体的字符平均宽度 200, // nEscapement, 字符输出方向与水平向右的方向所成角度,以0.1度为单位 0, // nOrientation, 字符与基线的角度,以0.1度为单位 FW_BOLD, // nWeight, 字符颜色的深浅度 TRUE, // bItalic, 斜体属性标志(FALSE:正常字体,TRUE:斜体) FALSE, // bUnderline, 下划线属性标志(FALSE:无下划线,TRUE:有下划线) FALSE, // cStrikeOut, 删除线属性标志(FALSE:无删除线,TRUE:有删除线) ANSI_CHARSET, // nCharSet, 字符集标识0:ANSI字符集,1:系统缺省字符集 OUT_DEFAULT_PRECIS, // nOutPrecision, 输出精度 CLIP_DEFAULT_PRECIS, // nClipPrecision, 剪切精度 DEFAULT_QUALITY, // nQuality, 输出品质 DEFAULT_PITCH|FF_SWISS, // nPitchAndFamily, 字符间距 TEXT("Arial")); // lpszFacename, 现有系统TrueType字体名称 HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); SetBkMode(hDC, TRANSPARENT); SetTextColor(hDC, RGB(0x00, 0xFF, 0xFF)); TextOut(hDC, 0, 150, TEXT("创建Font"), 6); DeleteObject(hFont); EndPaint(hWnd, &ps); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0 ; } return DefWindowProc (hWnd, message, wParam, lParam); } |
程序运行,点击鼠标左键后效果如下:
程序中的DrawText、ExtTextOut能设置文本输出的矩形范围,超出部分是看不见的,从运行结果我们也可以看出有两行显示不全,就是由于设置的显示范围小的缘故。
另外ExtTextOut函数还可以设置字符的间距,运行结果的第三行就是这种自己设置间距不一样的结果。
本程序还用CreateFont函数创建了一个斜体、右上排列的文本串。通过上例,我们把常用的文本输出作为实例展示给大家,只要好好对照实例代码,在结合MSDN的说明,再加上本系列的第一篇的Windows编程基本框架,一定可以掌握好Windows编程的基本文本输出。