Windows Api第二篇

Windows的GDI图形

Windows图形

大多数 Windows 程式在 WinMain 中进入讯息回圈之前的初始化期间都要呼 叫函式 UpdateWindow。通过呼叫 GetUpdateRect,可以在任何时候取得这些座标。 装置内容(简称为「DC」)实际上是 GDI 内部保存的资料结构。装置内容 与特定的显示设备(如视讯显示器或印表机)相关。程式必须在处理单个讯息处理期间取得和 释放代号。除了呼叫 CreateDC(函式,在本章暂不讲述)建立的装置内容之外, 程式不能在两个讯息之间保存其他装置内容代号。

取得装置内容代号

方法一

在处理 WM_PAINT 讯息时,使用这种方法。它涉及 BeginPaint 和 EndPaint两个函式,这两个函式需要视窗代号(作为参数传给视窗讯息处理程式)和 PAINTSTRUCT结构的变数(在WINUSER.H表头档案中定义)的地址为参数

1
2
case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; 使用GDI函式 EndPaint (hwnd, &ps) ;
return 0 ;

绘图资讯结构,Windows 为每个视窗保存一个「绘图资讯结构」,这就是 PAINTSTRUCT,定义如下:

1
2
3
4
5
6
7
8
9
typedef struct tagPAINTSTRUCT
{
HDC hdc ;
BOOL fErase ;
RECT rcPaint ;
BOOL fRestore ;
BOOL fIncUpdate ;
BYTE rgbReserved[32] ;
} PAINTSTRUCT ;

在程式呼叫 BeginPaint 时,Windows 会适当填入该结构的各个栏位值。使 用者程式只使用前三个栏位,其他栏位由 Windows 内部使用.Windows 程式使用白色画刷
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

方法二

要得到视窗显示区域的装置内容代号,可以呼叫 GetDC 来取得代号,在使 用完後呼叫 ReleaseDC

1
2
hdc = GetDC (hwnd) ; 使用GDI函式
ReleaseDC (hwnd, hdc) ;

与 BeginPaint 和EndPaint一样,GetDC 和ReleaseDC 函式必须成对地使用。 如果在处理某讯息时呼叫 GetDC,则必须在退出视窗讯息处理程式之前呼叫 ReleaseDC。不要在一个讯息中呼叫 GetDC 却在另一个讯息呼叫 ReleaseDC。

TextOut 是用於显示文字的最常用的 GDI 函式。语法是:
TextOut (hdc, x, y, psText, iLength) ;

系统字体
呼叫 GetTextMetrics 取得字体大小。GetTextMetrics 传回装置内容中 目前选取的字体资讯,因此它需要装置内容代号。Windows 将文字大小的不同值 复制到在 WINGDI.H 中定义的 TEXTMETRIC 型态的结构中。TEXTMETRIC 结构有 20 个栏位,我们只使用前七个:

1
2
3
4
5
6
7
8
9
10
typedef struct tagTEXTMETRIC {
LONG tmHeight ;
LONG tmAscent ;
LONG tmDescent ;
LONG tmInternalLeading ;
LONG tmExternalLeading ;
LONG tmAveCharWidth ;
LONG tmMaxCharWidth ;
其他结构栏位
} TEXTMETRIC, * PTEXTMETRIC ;

要使用 GetTextMetrics 函式,需要先定义一个结构变数(通常称为 tm):
TEXTMETRIC tm ;
在需要确定文字大小时,先取得装置内容代号,再呼叫 GetTextMetrics:

1
2
3
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
ReleaseDC (hwnd, hdc) ;

最重要的值是 tmHeight,它是 tmAscent 和 tmDescent 的和。这两个值表示 了基准线上下字元的最大纵向高度

格式化文字

假设要编写一个 Windows 程式,在显示区域显示几行文字,这需要先取得 字元宽度和高度。您可以在视窗讯息处理程式内定义两个变数来保存平均字元 宽度(cxChar)和总的字元高度(cyChar)
static int cxChar, cyChar ;
下面是取得系统字体的字元宽度和高度的 WM_CREATE 程式码:

1
2
3
4
5
6
7
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
return 0 ;

您会发现常常需要显示格式化的数字跟简单的字串。您不能使惯用的工具(可爱的 printf 函式)来完成这项工作,但是可以使用 sprintf 和 Windows 版的 sprintf——wsprintf。这些函式与 printf 相似,只是把格式化字串放到字串中。然後,可以用 TextOut 将字串输出到显示器上。 非常方便的是,从 sprintf 和 wsprintf 传回的值就是字串的长度。您可以将这 个值传递给 TextOut 作为 iLength 参数。下面的程式码显示了 wsprintf 与 TextOut 的典型组合:

1
2
3
4
int iLength ;
TCHAR szBuffer [40] ;
iLength = wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"), iA, iB, iA + iB) ;
TextOut (hdc, x, y, szBuffer, iLength) ;

也可以这样
TextOut (hdc, x, y, szBuffer, wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"), iA, iB, iA + iB)) ;

综合使用

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
#include <windows.h>
#include "sysmets.h" //自己定义的,书上有
#define NUMLINES ((int)(sizeof sysmetrics / sizeof sysmetrics[0]))
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("SysMets");
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.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, // window class name
TEXT("Get System Metrics No.1"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // 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;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar;
HDC hdc;
int i;
TCHAR szBuffer[10];
PAINTSTRUCT ps;
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);
return 0;
case WM_PAINT:
for (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));
SetTextAlign(hdc, TA_RIGHT | TA_TOP);
TextOut(hdc,
22 * cxCaps + 40 * cxChar,
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);
}

卷动列

卷动列是图形使用者介面中最好的功能之一,它很容易使用,而且提供了 很好的视觉回馈效果。您可以使用卷动列显示任何东西–无论是文字、图形、 表格、资料库记录、图像或是网页,只要它所需的空间超出了视窗的显示区域 所能提供的空间,就可以使用卷动列。 卷动列既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。

在应用程式中包含水平或者垂直的卷动列,程式写作者只需要在 CreateWindow 的第三个参数中包括视窗样式(WS)识别字 WS_VSCROLL(垂直卷 动)和/或 WS_HSCROLL(水平卷动)即可。

卷动列的范围和位置

每个卷动列均有一个相关的「范围」(这是一对整数,分别代表最小值和最大值)和「位置」(它是卷动方块在此范围内的位置)。当卷动方块在卷动 列的顶部(或左部)时,卷动方块的位置是范围的最小值;在卷动列的底部(或 右部)时,卷动方块的位置是范围的最大值。 在内定情况下,卷动列的范围是从 0(顶部或左部)至 100(底部或右部), 但将范围改变为更方便於程式的数值也是很容易的:
SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
参数 iBar 为 SB_VERT 或者 SB_HORZ,iMin 和 iMax 分别是范围的最小值和 最大值。如果想要 Windows 根据新范围重画卷动列,则设置 bRedraw 为 TRUE(如 果在呼叫 SetScrollRange 後,呼叫了影响卷动列位置的其他函式,则应该将 bRedraw 设定为 FALSE 以避免过多的重画)。
您可以使用 SetScrollPos 在卷动列范围内设置新的卷动方块位置:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
以下是程式写作者应该完成的工作

1
2
3
4
初始化卷动列的范围和位置
处理视窗讯息处理程式的卷动列讯息
更新卷动列内卷动方块的位置
更改显示区域的内容以回应对卷动列的更改

实践

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
111
112
113
114
115
116
117
#include <windows.h>
#include "sysmets.h" //自己定义的,书上有
#define NUMLINES ((int)(sizeof sysmetrics / sizeof sysmetrics[0]))
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("SysMets2");
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.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, // window class name
TEXT("Get System Metrics No.2"), // window caption
WS_OVERLAPPEDWINDOW | WS_VSCROLL, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // 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;
}
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 (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));
SetTextAlign(hdc, TA_RIGHT | TA_TOP);
TextOut(hdc, 22 * cxCaps + 40 * cxChar, 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);
}

来源:《Windows程序设计》(第五版)

Donate
-------------本文结束感谢您的阅读-------------