4、文本输出

无效矩形

需要重绘的区域被称为“无效区域”,windows会计算出一个包含无效区域的最小矩形,这个最小矩形被称为“无效矩形”,调用windows函数可以获取到这个无效矩形。
当窗口中出现无效区域时,windows把WM_PAINT消息发送到窗口消息队列。消息队列中不会出现多条WM_PAINT消息,windows会根据新出现的无效区域,计算一个新的无效矩形。

设备环境句柄

1、BeginPaint和EndPaint
调用GDI函数基本都需要一个设备环境句柄参数。获取设备环境句柄的一个方法是调用BeginPaint,需要和EndPaint搭配使用。BeginPaint会使无效区域变成有效,所以BeginPaint一般在处理WM_PAINT消息中使用,否则windows会一直发送WM_PAINT消息。
除了设备环境句柄,BeginPaint还通过输出参数获取一个PAINTSTRUCT变量:

typedef struct tagPAINTSTRUCT {
    HDC         hdc;
    BOOL        fErase;
    RECT        rcPaint;
    BOOL        fRestore;
    BOOL        fIncUpdate;
    BYTE        rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
  • hdc:设备环境句柄。
  • fErase:指示BeginPaint是否擦除了无效区域背景,0表示背景被擦除,非0则表示未擦除。
// 令整个客户区无效,并且其后的BeginPaint会发送WM_ERASEBKGND来擦除背景,fErase将是0
InvalidateRect(hwnd, NULL, TRUE);
// BeginPaint不会擦除背景,fErase将是非0
InvalidateRect(hwnd, NULL, TRUE);
  • rcPaint:无效矩形。使用BeginPaint返回的设备环境句柄进行绘图时,绘制被限制在这个裁剪矩形中。

2、GetDC和ReleaseDC
使用GetDC返回的设备环境句柄可以在整个客户区内进行绘图,但是GetDC不会使无效区域有效化。因此GetDC一般不在WM_PAINT消息处理中使用,而用于处理键盘消息和键盘消息。

3、GetWindowDC和ReleaseDC
使用GetWindowDC返回的设备环境句柄可以在整个窗口内进行绘图。如果需要在非客户区进行绘图,需要处理WM_NCPAINT消息。

4、CreateDC和DeleteDC
根据名字获取特定设备的设备环境句柄。(BeginPaint、GetDC和GetWindowDC都是根据窗口句柄来获取特定窗口的设备环境句柄)

5、设备环境是和特定区域绑定的,绘图范围被限制在这个区域内。BeginPaint返回的和无效矩形关联,GetDC返回的和客户区关联,GetWindowDC返回的和整个窗口区域关联。

6、必须在同一条消息处理中获取和释放设备环境句柄,不能在消息之间传递设备环境句柄(CreateDC是例外)。

显示多行(列)文本

显示多行(列)文本时,一个需要解决的问题是如何确定文本位置。windows提供的TEXTMETRIC结构体记录了当前设备环境相关的字符尺寸信息和文本显示格式,利用这些尺寸信息可以计算出文本显示位置。

typedef struct tagTEXTMETRICW
{
    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;			// 1:变宽字体 0:等宽字体
    BYTE        tmCharSet;
} TEXTMETRICW, *PTEXTMETRICW, NEAR *NPTEXTMETRICW, FAR *LPTEXTMETRICW;

TEXTMETRIC几个纵向尺寸间关系

TEXTMETRIC几个纵向尺寸间关系
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int cxChar, cxCaps, cyChar;
	HDC hdc;
	int i;
	PAINTSTRUCT ps;
	TCHAR szBuffer[10];
	TEXTMETRIC tm;

	switch (message)
	{
	case WM_CREATE:
		hdc = GetDC(hwnd);
		
		GetTextMetrics(hdc, &tm);
		cxChar = tm.tmAveCharWidth;	
		// 对于变宽字符,字符宽度取1.5 * tm.tmAveCharWidth
		cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
		cyChar = tm.tmHeight + tm.tmExternalLeading;
		ReleaseDC(hwnd, hdc);
		return 0;

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

		for (int i = 0; i < NUMLINES; i++)
		{
			TextOut(hdc, 0, cyChar * i, sysmetrics[i].szLabel, lstrlen(sysmetrics[i].szLabel));
			TextOut(hdc, 22 * cxCaps, cyChar * i, sysmetrics[i].szDesc, lstrlen(sysmetrics[i].szDesc));

			// 设置右对齐,TextOut的x参数指定最后一个字符位置
			SetTextAlign(hdc, TA_RIGHT | TA_TOP);

			// 这里利用了函数参数从右向左入栈的规则(函数调用约定)
			TextOut(hdc, 22 * cxCaps + 40 * cxCaps, cyChar * i, szBuffer,
				wsprintf(szBuffer, TEXT("%5d"), GetSystemMetrics(sysmetrics[i].iIndex)));

			SetTextAlign(hdc, TA_LEFT | TA_TOP);
		}

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

滚动条

窗口无法显示所有文本内容时,可以给窗口添加一个滚动条。添加滚动条最直接的方法是改变窗口风格:垂直滚动条WS_VSCROLL和水平滚动条WS_HSCROLL。
使用滚动条,我们需要负责更新滚动条的位置以及更新显示的内容,如果有需要还可以设置滚动条范围。
操作滚动条时,Windows向窗口过程发送WM_VSCROLL或WM_HSCROLL,其wParam的低位字表示滚动条的动作,称为“通知码”。使用宏HIWORD或LOWORD可以取到消息参数的高位字或低位字。

// 滚动条通知码
#define SB_LINEUP           0
#define SB_LINELEFT         0
#define SB_LINEDOWN         1
#define SB_LINERIGHT        1
#define SB_PAGEUP           2
#define SB_PAGELEFT         2
#define SB_PAGEDOWN         3
#define SB_PAGERIGHT        3
#define SB_THUMBPOSITION    4				// 拖动滑块结束,松开鼠标时,wParam的高位字表示滑块最终位置
#define SB_THUMBTRACK       5				// 鼠标拖动滑块移动时,wParam的高位字表示滑块当前位置
#define SB_TOP              6
#define SB_LEFT             6
#define SB_BOTTOM           7
#define SB_RIGHT            7
#define SB_ENDSCROLL        8

相关API:SetScrollRange、SetScrollPos、GetScrollPos、GetScrollRange

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int cxChar, cxCaps, cyChar, cyClient, iVscrollPos;
	HDC hdc;
	int i, y;
	PAINTSTRUCT ps;
	TCHAR szBuffer[10];
	TEXTMETRIC tm;

	switch (message)
	{
	case WM_CREATE:
		hdc = GetDC(hwnd);

		GetTextMetrics(hdc, &tm);
		cxChar = tm.tmAveCharWidth;
		cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
		cyChar = tm.tmHeight + tm.tmExternalLeading;
		ReleaseDC(hwnd, hdc);

		SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - 1, FALSE);
		SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
		return 0;

	case WM_SIZE:
		cyClient = HIWORD(lParam);
		return 0;

	case WM_VSCROLL:
		switch (LOWORD(wParam))
		{
		case SB_LINEUP:
			iVscrollPos -= 1;
			break;
		case SB_LINEDOWN:
			iVscrollPos += 1;
			break;
		case SB_PAGEUP:
			iVscrollPos -= cyClient / cyChar;
			break;
		case SB_PAGEDOWN:
			iVscrollPos += cyClient / cyChar;
			break;
		case SB_THUMBPOSITION:
			iVscrollPos = HIWORD(wParam);
			break;
		default:
			break;
		}

		iVscrollPos = max(0, min(iVscrollPos, NUMLINES - 1));

		if (iVscrollPos != GetScrollPos(hwnd, SB_VERT))
		{
			SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
			InvalidateRect(hwnd, NULL, TRUE);
		}
		return 0;

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

		for (int i = 0; i < NUMLINES; i++)
		{
			y = cyChar * (i - iVscrollPos);
			TextOut(hdc, 0, y, sysmetrics[i].szLabel, lstrlen(sysmetrics[i].szLabel));
			TextOut(hdc, 22 * cxCaps, y, sysmetrics[i].szDesc, lstrlen(sysmetrics[i].szDesc));

			// 设置右对齐,TextOut的x参数指定最后一个字符位置
			SetTextAlign(hdc, TA_RIGHT | TA_TOP);

			// 这里利用了函数参数从右向左入栈的规则(函数调用约定)
			TextOut(hdc, 22 * cxCaps + 40 * cxCaps, y, szBuffer,
				wsprintf(szBuffer, TEXT("%5d"), GetSystemMetrics(sysmetrics[i].iIndex)));

			SetTextAlign(hdc, TA_LEFT | TA_TOP);
		}

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

更好的滚动条

SetScrollInfo和GetScrollInfo也可以用于设置、获取滚动条信息,能够提供SetScrollRange、SetScrollPos、GetScrollPos、GetScrollRange相同甚至更好的功能。SetScrollInfo和GetScrollInfo通过SCROLLINFO 结构体来操作滚动条。

typedef struct tagSCROLLINFO
{
    UINT    cbSize;					// 结构体大小
    UINT    fMask;					// 描述了需要设置、获取哪些值的标志
    int     nMin;					// 滚动条范围
    int     nMax;					// 滚动条范围
    UINT    nPage;					// 页面大小
    int     nPos;					// 滑块当前位置
    int     nTrackPos;				// 拖动中的滑块位置
}   SCROLLINFO, FAR *LPSCROLLINFO;
typedef SCROLLINFO CONST FAR *LPCSCROLLINFO;

// fMask可以是下列值及其"位或"的组合
#define SIF_RANGE           0x0001
#define SIF_PAGE            0x0002
#define SIF_POS             0x0004
#define SIF_DISABLENOSCROLL 0x0008					// 禁用滚动条
#define SIF_TRACKPOS        0x0010
#define SIF_ALL             (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS)
  • nPage:默认情况,滚动条滑块大小是固定的。一个好的滑块应该能反映当前显示内容在全部内容中的占比。通过修改页面大小可以改变滑块大小。
  • nTrackPos:通知码为SB_THUMBPOSITION或SB_THUMBTRACK的滚动条消息,wParam的高位字指示了滑块位置,只有16位。SCROLLINFO中的nTrackPos提供了32位的滑块位置信息。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值