第一章 开始
有关TEXT(),这是一 个宏定义,也就是用define定义的。这是为了兼容 UNICODE字符集而做的改动。以后不管是什么时候,你最好把字符串都 用TEXT()括起来,有关UNICODE, 那是第二章的问题,所以在第一章这个问题根本就不成为问题。
头文件windows.h中包 含了其它的头文件,这些头文件中的一部分又包含了另外的一些头文件。
◆WINDEF.H
◆WINNT.H
◆WINBASE.H
◆ WINUSER.H
◆WINGDI.H
这些头文件定义了Windows的所有数据类型,函数调用,数据结构和常数标识符。
WinMain()前面的WINAPI在WINDEF.H中定义如下:
#define WINAPI __stdcall
第二章 Unicode
宽字符不一定是Unicode。Unicode是宽字符集的一种。
请记住,改成宽字节后字符串的字符长度不变,只是字 节长度改变了。千万不要混淆。
因为Unicode占用两倍 的存储空间,所以宽字节运行库中的函数比常规的函数大。所以最好是建立两个版本的程序,一个处理 ASCII字符串,另一 个处理Unicode字符串。但是这样以来又引来了另一个小问题。因为每一个实现特定功能的函数都有两个版本,所 以名字不好记。不管是ASCII版本还是 Unicode版本,都用相同的名字该多好啊?幸好这个问题已经得到了解决。解决办法是使用 Visual C++包含的TCHAR.H头文件 。该头文件不是标准C的一部分。为了与标准C的头文件分别开来,该头文件内定义的每个函数和宏定义的前面都有一条下划线。
TCHAR.H为需要字符串的标准运行库函数提供了一系列的替代名称 。有时这些名称被称为"通用"函 数名,因为它们既可以指向函数的Unicode版本,也可以指向 ASCII版本。
以 _tcslen()为例如果定义了_UNICODE的标识符,并且程序中包含了TCHAR.H,那么_tcslen()就定义为wcslen():
#define _tcslen wcslen
如果没有定义_UNICODE,则_tcslen()被定义为strlen()。
#define _tcslen strlen
TCHAR.H还用一个新的数据类型TCHAR来解决两种字符数据类型的问题。如果定义了_UNICODE 标识符,那么TCHAR就是 wchar_t:
typedef wchar_t TCHAR;
否则TCHAR就是 char:
typedef char TCHAR;
还记得第一章里出现过的TEXT()吗?那是为了兼容UNICODE字符集所做的改动。下面就来看看TEXT()在头文件中是怎么定 义的。
#define __T(x) L##x
此外还有两个宏与__T 定义相同:
#define _T(x) __T(x)
#define _TEXT(x) __T(x)
WINNT.H头文件中还定义了一个宏,该宏也跟 __T一样,将L添加到字符串前。
#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT (quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
第三章 HelloWin
在Windows中 "窗口"一词有确切的含义。一个 窗口就是屏幕上的一个矩形区域。它接收用户的输入,并以文本或图形的格式显示输出内容。
程序创建的每一个窗口都有一个相关的窗口过程。窗口过程 是一个函数。这个函数可以在程序中,也可以在动态链接库中。Windows通过调用窗口过程来处理窗口发送的消息。窗口过程根据此消息进行处理,然后将控制返回给 Windows。
API版本Hello程序
#include
#include
LRESULT CALLBACK WndProc(HWND hwnd,UINT uMsg, \
WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, \
HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
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="Hello";
RegisterClass(&wndclass);
HWND hwnd;
hwnd=CreateWindow("Hello","戴方勤 ",WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT, \
CW_USEDEFAULT,CW_USEDEFAULT, \
NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
UpdateWindow(hwnd);
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
case WM_PAINT:
HDC hdc;
PAINTSTRUCT ps;
hdc=BeginPaint(hwnd,&ps);
TextOut(hdc,0,0,"Hello World!",strlen("Hello World!"));
EndPaint(hwnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
MFC版本 Hello程序
#include
class CHelloWindow:public CFrameWnd
{
CStatic *cs;
public:
CHelloWindow()
{
Create(NULL,"戴方勤 ",WS_OVERLAPPEDWINDOW,CRect(0,0,200,200));
cs=new CStatic();
cs->Create("Hello World!",WS_CHILD|WS_VISIBLE \
|SS_CENTER,CRect(50,80,150,100),this);
}
};
class CHelloApp:public CWinApp
{
public:
virtual BOOL InitInstance()
{
m_pMainWnd=new CHelloWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return true;
}
};
CHelloApp theApp;
程序中有很多大写的标识符。这些标识符都是在 Windows的头文件中定义的。以上都是简单的数值常量。标识符的前缀 表示该常量所属的类别。
CS --- 类风格选项
CW --- 创建窗口选项
DT --- 绘制文本选项
IDI--- 图标ID号
IDC---光标ID号
MB ---消息框选项
SND---声音选项
WM ---窗口消息
WS ---窗口风格
MSG -- 消息结构
WNDCLASS -- 窗口类结构
PAINTSTRUCT -- 绘图结构
RECT -- 矩形结构
最后还有三个大写标识符,用于不同类型的“句柄”。
HINSTANCE --实例(程 序自身)句柄
HWND -- 窗口句 柄
HDC -- 设备描述表句柄
句柄在Windows中使用 非常频繁。也是非常重要的一个概念。我们还将遇到图标句柄HICON、 鼠标指针句柄HCURSOR、图形刷句柄HBRUSH。到底什么是句柄呢?
句柄是一种新的数据类型。菜单,窗口,图标,内存,设备 ,程序,位图等都被称为“对象”。也就是说菜单是菜单对象,窗口是窗口对象,内存是内存对象。对于这 样的称呼您一定要习惯。句柄可以代表一个对象。我们用句柄引用一个对象。例如我们可以用设备描 述表句柄引用一个设备(比如显示器)。当我们用一 个设备描述表句柄引用显示器时,这个句柄代表的就是显示器。我们通过这个句柄使 用显示器。
■常用的句柄类型
HANDLE------------------通用句柄类型
HWND --------------------标识一个窗口对象
HDC------------------ ---标识一个设备对象
HMENU-------------------标识一个菜单对象
HICON-------------------标识一 个图标对象
HCURSOR-----------------标识一个光标对象
HBRUSH------------------标识一个刷子对象
HPEN-- ------------------标识一个笔对象
HFONT------------------- 标识一个字体对象
HINSTANCE---------------标识一个应用程序模块的一个实例
HLOCAL------------------标识一个局部内存对象
HGLOBAL-----------------标识一 个全局内存对象
也就是说HWND代表的 是一个窗口对象,HDC代表的是一个设备对象。这个设备有可能是内存 ,也有可能是磁盘。句柄是一个数,通常为32位数,以十六进制形式表 示。
第四章 输出文字
绘制和更新
Windows是一个消息驱动系统。它通过把消息投入应用程序 消息队列中或者把消息发送给合适的窗口消息处理程序,将发生的各种事件通知给应用程序。 Windows通过发送WM_PAINT消息通 知窗口消息处理程序,窗口的部分显示区域需要绘制。
WM_PAINT消息
大多数Windows程序在 WinMain中进入消息循环之前的初始化期间都要呼叫函数 UpdateWindow。Windows利用这个 机会给窗口消息处理程序发送第一个WM_PAINT消息。这个消息通知窗口 消息处理程序:必须绘制显示区域。此后,窗口消息处理程序应在任何时刻都准备好处理其它 WM_PAINT消息,必要的话,甚至重新绘制窗口的整个显示区域。在发生 下面几种事件之一时,窗口消息处理程序会接收到一个WM_PAINT消息:
①在使用者移动窗口或显示窗口时,窗口中先前被隐藏的区 域重新可见。
②使用者改变窗口的大小(如果窗口类别样式有着 CS_HREDRAW和CS_VREDRAW位旗标 的设定)。
③程序使用ScrollWindow或ScrollDC函数滚动显示区域的一部分。
④程序使用InvalidateRect或InvalidateRgn函数刻意产生WM_PAINT消息。
在某些情况下,显示区域的一部分被临时覆盖, Windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功 。在以下情况下,Windows可能发送WM_PAINT消息:
⑤Windows擦除覆盖了部分窗口的对话框或消息框。
⑥菜单下拉出来,然后被释放。
⑦显示工具提示消息。
在某些情况下,Windows总是保存它所覆盖的显示区域,然后恢复它。这些情况是:
●鼠标光标穿越显示区域。
●图标拖过显示区域。
处理WM_PAINT消息要 求程序写作者改变自己向显示器输出的思维方式。程序应该组织成可以保留绘制显示区域需要的所有信息, 并且仅当「响应要求」-即Windows给窗口消息处理程序发送 WM_PAINT消息时才进行绘制。如果程序在其它时间需要更新其显示区域 ,它可以强制Windows产生一个WM_PAINT消息。这看来似乎是在屏幕上显示内容的一种舍近求远的方法。但您的程序结构可以从中受益。
有效矩形和无效矩形
尽管窗口消息处理程序一旦接收到WM_PAINT消息之后,就准备更新整个显示区域,但它经常只需要更新一个较小的区域(最常见 的是显示区域中的矩形区域)。显然,当对话框覆盖了部分显示区域时,情况即是如此。在擦除对话框之后 ,需要重画的只是先前被对话框遮住的矩形区域。
这个区域称为「无效区域」或「更新区域」。正是显示区域 内无效区域的存在,才会让Windows将一个WM_PAINT消息放在应用程序的消息队列中。只有在显示区域的某一部分失效时,窗口才会接受 WM_PAINT消息。
Windows内部为每个窗口保存一个「绘图信息结构」,这个 结构包含了包围无效区域的最小矩形的坐标以及其它信息,这个矩形就叫做「无效矩形」,有时也称为「无 效区域」。如果在窗口消息处理程序处理WM_PAINT消息之前显示区域中 的另一个区域变为无效,则Windows计算出一个包围两个区域的新的无 效区域(以及一个新的无效矩形),并将这种变化后的信息放在绘制信息结构中。Windows不会将多个WM_PAINT消息都放在消息队列中。
窗口消息处理程序可以通过呼叫 InvalidateRect使显示区域内的矩形无效。如果消息队列中已经包含一 个WM_PAINT消息,Windows将计算 出新的无效矩形。否则,它将一个新的WM_PAINT消息放入消息队列中。 在接收到WM_PAINT消息时,窗口消息处理程序可以取得无效矩形的坐标 (我们马上就会看到这一点)。通过呼叫GetUpdateRect,可以在任何 时候取得这些坐标。
在处理WM_PAINT消息 处理期间,窗口消息处理程序在呼叫了BeginPaint之后,整个显示区域 即变为有效。程序也可以通过呼叫ValidateRect函数使显示区域内的任 意矩形区域变为有效。如果这呼叫具有令整个无效区域变为有效的效果,则目前队列中的任何 WM_PAINT消息都将被删除。
设置背景模式
int SetBkMode(
HDC hdc; //显示设备句柄
Int //背景模式
);
OPAQUE:每次输出文本时,窗口的背景色变成当前文本背景 色
TRANSPARENT:背景不受影响,保持背景色不变
在缺省的情况下,背景模式为OPAQUE。
GDI 简介
设备内容
设备内容(简称为「DC」)实际上是GDI内部保存的数据结构。设备内容与特定的显 示设备(如视讯显示器或打印机)相关。对于视讯显示器,设备内容总是与显示器上的特定窗口相关。
设备内容中的有些值是图形「属性」,这些属性定义了 GDI绘图函数工作的细节。
当程序需要绘图时,它必须先取得设备内容句柄。在取得了 该句柄后,Windows用内定的属性值填入内部设备内容结构。在后面的 章节中您会看到,可以通过呼叫不同的GDI函数改变这些默认值。利用 其它的GDI函数可以取得这些属性的目前值。当然,还有其它的 GDI函数能够在窗口的显示区域真正地绘图。
当程序在显示区域绘图完毕后,它必须释放设备内容句柄。 句柄被程序释放后就不再有效,且不能再被使用。程序必须在处理单个消息处理期间取得和释放句柄。除了 呼叫CreateDC(函数,在本章暂不讲述)建立的设备内容之外,程序不 能在两个消息之间保存其它设备内容句柄。
取得设备内容句柄:方法一
在处理WM_PAINT消息 时,使用这种方法。它涉及BeginPaint和EndPaint两个函数,这两个函数需要窗口句柄(作为参数传给窗口消息处理程序)和 PAINTSTRUCT结构的变量(在WINUSER.H表头文件中定义)的地址为参数。Windows程序写作者通常把 这一结构变量命名为ps并且在窗口消息处理程序中定义它:
PAINTSTRUCT ps ;
在处理WM_PAINT消息 时,窗口消息处理程序首先呼叫BeginPaint。BeginPaint函数一般在准备绘制时导致无效区域的背景被擦除。该函数也填入ps结构的字段。BeginPaint传回的值是设备内容句 柄,这一传回值通常被保存在叫做hdc的变量中。它在窗口消息处理程 序中的定义如下:
HDC hdc ;
HDC数据型态定义为32 位的无正负号整数。然后,程序就可以使用需要设备内容句柄的TextOut等GDI函数。呼叫EndPaint即可释放设备内容句柄。
一般地,处理WM_PAINT消息的形式如下:
caseWM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
使用GDI函数
EndPaint (hwnd, &ps) ;
return 0 ;
在处理WM_PAINT消息 时,必须成对地呼叫BeginPaint和EndPaint。如果窗口消息处理程序不处理WM_PAINT消息,则它必须将 WM_PAINT消息传递给Windows中 DefWindowProc(内定窗口消息处理程序)。 DefWindowProc以下列代码处理WM_PAINT消息:
case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
这两个BeginPaint和 EndPaint呼叫之间中没有任何叙述,仅仅使先前无效区域变为有效。但 以下方法是错误的:
case WM_PAINT:
return 0 ; // WRONG !!!
Windows将一个WM_PAINT消息放到消息队列中,是因为显示区域的一部分无效。如果不呼叫BeginPaint和EndPaint(或者ValidateRect),则Windows不会使该区域变为有效。相反, Windows将发送另一个WM_PAINT消 息,且一直发送下去。
绘图信息结构
前面提到过,Windows 为每个窗口保存一个「绘图信息结构」,这就是PAINTSTRUCT,定义如 下:
typedef struct tagPAINTSTRUCT
{
HDC hdc ;
BOOL fErase ;
RECT rcPaint ;
BOOL fRestore ;
BOOL fIncUpdate ;
BYTE rgbReserved[32] ;
} PAINTSTRUCT ;
在程序呼叫BeginPaint时,Windows会适当填入该结构的各个字段值。使用者程序只 使用前三个字段,其它字段由Windows内部使用。hdc字段是设备内容句柄。在旧版本的Windows中, BeginPaint的传回值也曾是这个设备内容句柄。在大多数情况下, fErase被标志为FALSE(0),这意 味着Windows已经擦除了无效矩形的背景。这最早在 BeginPaint函数中发生(如果要在窗口消息处理程序中自己定义一些背 景擦除行为,可以自行处理WM_ERASEBKGND消息)。 Windows使用WNDCLASS结构的 hbrBackground字段指定的画刷来擦除背景,这个 WNDCLASS结构是程序在WinMain初 始化期间登录窗口类别时使用的。许多Windows程序使用白色画刷。以 下叙述设定窗口类别结构字段值:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
不过,如果程序通过呼叫Windows函数InvalidateRect使显示区域中的矩形失效,则该函数的 最后一个参数会指定是否擦除背景。如果这个参数为FALSE(即 0),则Windows将不会擦除背景 ,并且在呼叫完BeginPaint后PAINTSTRUCT结构的fErase字段将为TRUE(非零)。
PAINTSTRUCT结构的rcPaint字段是RECT型态的结构。PAINTSTRUCT中的rcPaint矩形不仅是无效矩形,它还是一个「剪取」 矩形。这意味着Windows将绘图操作限制在剪取矩形内(更确切地说, 如果无效矩形区域不为矩形,则Windows将绘图操作限制在这个区域内 )。
在处理WM_PAINT消息 时,为了在更新的矩形外绘图,可以使用如下呼叫:
InvalidateRect (hwnd, NULL, TRUE) ;
该呼叫在BeginPaint 呼叫之前进行,它使整个显示区域变为无效,并擦除背景。但是,如果最后一个参数等于FALSE,则不擦除背景,原有的东西将保留在原处。
通常这是Windows程序 在无论何时收到WM_PAINT消息而不考虑rcPaint结构的情况下简单地重画整个显示区域最方便的方法。
取得设备内容句柄:方法二
虽然最好是在处理WM_PAINT消息处理期间更新整个显示区域,但是您也会发现在处理非WM_PAINT消息处理期间绘制显示区域的某个部分也是非常有用的。或者您需要将设备内容句柄用于其它目的 ,如取得设备内容的信息。
要得到窗口显示区域的设备内容句柄,可以呼叫 GetDC来取得句柄,在使用完后呼叫ReleaseDC:
hdc = GetDC (hwnd) ;
使用GDI函数
ReleaseDC (hwnd, hdc) ;
与BeginPaint和 EndPaint一样,GetDC和 ReleaseDC函数必须成对地使用。如果在处理某消息时呼叫 GetDC,则必须在退出窗口消息处理程序之前呼叫 ReleaseDC。不要在一个消息中呼叫GetDC却在另一个消息呼叫ReleaseDC。
与从BeginPaint传回 设备内容句柄不同,GetDC传回的设备内容句柄具有一个剪取矩形,它 等于整个显示区域。可以在显示区域的某一部分绘图,而不只是在无效矩形上绘图(如果确实存在无效矩形 )。与BeginPaint不同,GetDC不 会使任何无效区域变为有效。如果需要使整个显示区域有效,可以呼叫
ValidateRect (hwnd, NULL) ;
一般可以呼叫GetDC和 ReleaseDC来对键盘消息(如在字处理程序中)和鼠标消息(如在画图 程序中)作出反应。此时,程序可以立刻根据使用者的键盘或鼠标输入来更新显示区域,而不需要考虑为了 窗口的无效区域而使用WM_PAINT消息。不过,一旦确实收到了 WM_PAINT消息,程序就必须要收集足够的信息后才能更新显示。
与GetDC相似的函数是 GetWindowDC。GetDC传回用于写 入窗口显示区域的设备内容句柄,而GetWindowDC传回写入整个窗口的 设备内容句柄。例如,您的程序可以使用从GetWindowDC传回的设备内 容句柄在窗口的标题列上写入文字。然而,程序同样也应该处理WM_NCPAINT (「非显示区域绘制」)消息。
TextOut:细节
TextOut是用于显示文字的最常用的GDI函数。语法是:
TextOut (hdc, x, y, psText, iLength) ;
以下将详细地讨论这个函数。
第一个参数是设备内容句柄,它既可以是 GetDC的传回值,也可以是在处理WM_PAINT消息时BeginPaint的传回值。
psText参数是指向字符串的指针,iLength是字符串中字符的个数。如果psText指向 Unicode字符串,则字符串中的字节数就是iLength值的两倍。字符串中不能包含任何ASCII控制字 符(如回车、换行、制表或退格),Windows会将这些控制字符显示为 实心块。Text0ut不识别作为字符串结束标志的内容为零的字节(对于 Unicode,是一个短整数型态的0 ),而需要由nLength参数指明长度。
系统字体
内定字体为「系统字体」,或用Windows表头文件中的标识符,即SYSTEM_FONT。系统字 体是Windows用来在标题列、菜单和对话框中显示字符串的内定字体。
系统字体是一种「点阵字体」,这意味着字符被定义为图素 块(在第十七章,将讨论TrueType字体,它是由轮廓定义的)。至于确 切的大小,系统字体的字符大小取决于视讯显示器的大小。系统字体设计为至少能在显示器上显示 25行80列文字。
字符大小
程序可以呼叫GetSystemMetrics函数以取使用者接口上各类视觉组件大小的信息,呼叫GetTextMetrics取得字体大小。GetTextMetrics传回设备内容中目前选取的字 体信息,因此它需要设备内容句柄。Windows将文字大小的不同值复制 到在WINGDI.H中定义的TEXTMETRIC型态的结构中。TEXTMETRIC结构有20个字段,我们只使用前七个:
typedef struct tagTEXTMETRIC
{
LONG tmHeight ;
LONG tmAscent ;
LONG tmDescent ;
LONG tmInternalLeading ;
LONG tmExternalLeading ;
LONG tmAveCharWidth ;
LONG tmMaxCharWidth ;
其它结构字段
}
TEXTMETRIC, * PTEXTMETRIC ;
这些字段值的单位取决于选定的设备内容映像方式。在内定 设备内容下,映像方式是MM_TEXT,因此值的大小是以图素为单位。
要使用GetTextMetrics函数,需要先定义一个结构变量(通常称为tm):
TEXTMETRIC tm ;
在需要确定文字大小时,先取得设备内容句柄,再呼叫 GetTextMetrics:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
ReleaseDC (hwnd, hdc) ;
此后,您就可以查看文字尺寸结构中的值,并有可能保存其 中的一些以备将来使用。
文字大小:细节
TEXTMETRIC结构提供了关于目前设备内容中选用的字体的丰 富信息。但是,字体的纵向大小只由5个值确定,其中4个值如图4-3所示。
图4-3
最重要的值是tmHeight,它是tmAscent和tmDescent的和。这两个值表示了基准在线下字符的最大纵向高度。「间距」(leading)指打印机在两行文字间插入的空间。在TEXTMETRIC结 构中,内部的间距包括在tmAscent中(因此也在tmHeight中),并且它经常是重音符号出现的地方。tmInternalLeading