Windows程序设计上


第一章 开始

有关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

 


豫ICP备12024565号-1   E-mail:admin@hlc8.com