创建窗口只需调用CreateWindow 函数即可,但实际上并不这么简单。 但实际上并非真得这么简单。虽然创建窗口的函数确实被命名为CreateWindow, 但您将发现 CreateWindow的第一个参数就是所谓的“窗口类名称”,并且该窗口类连接所谓的“窗口过程”。在我们调用CreateWindow之前,有一点背景知识会对您大有帮助。
WINDOWS应用程序的基本结构
进行Windows程序设计,实际上是在进行一种面向对象的程序设计(OOP)。这一点在Windows中使用得最多的对象身上表现最为明显。
这种对象正是Windows之所以命名 为“Windows”的原因,它具有人格化的特征,这就是那个叫做“窗口”的对象。
一、总体结构
桌面上最明显的窗口就是应用程序窗口。这些窗口含有显示程序名称的标题栏、菜单,甚至可能还有工具栏和滚动条。另一类窗口是对话框,它可以有标题栏也可以没有标题栏。
装饰对话框表面的还有各式各样的按钮、单选钮、复选框、列表框、滚动条和文本输入区域。其中每一个小的可视对象都是一个窗口。更确切地说,这些都称为“子窗口”或“控件窗口”或“子窗口控件”。
作为对象,用户会在屏幕上看到这些窗口,并通过键盘和鼠标直接与它们进行交互操作。更有趣的是,程序员的观点与用户的观点极其类似。窗口以“消息”的形式接收窗口的输入,窗口也用消息与其他窗口通讯。对消息的理解将是学习如何编写Windows程序必须逾越的障碍之一。
这有一个windows的消息示例:我们知道,大多数的windows程序都有大小合适的应用程序窗口。也就是说,您能够通过鼠标拖动窗口的边框来改变窗口的大小。通常,程序将通过改变窗口中的内容来响应这种大小的变化。是Windows本身而不是应用程序正在处理与用户重新调整窗口大小相关的全部代码。由于应用程序能改变其显示的格式,所以它也“知道”窗口大小改变了。
应用程序是如何知道用户改变了窗口的大小的呢?由于程序员习惯了常规的字符模式程序,操作系统没有设置将此类消息通知给用户的机制。问题的关键在于理解Windows所使用的体系结构。当用户改变窗口的大小时,Windows给程序发送一条消息指出新窗口的大小。然后程序就可以调整窗口中的内容,以反映大小的变化。“Windows给程序发送消息。”我们希望读者不要对这句话视而不见。它到底表达了什么意思呢?我们在这里讨论的是程序代码,而不是一个电子邮件系统。操作系统怎么给程序发送消息呢?其实,所谓“Windows给程序发送消息”,是指Windows调用程序中的一个函数,该函数的参数描述了这个特定消息。这种位于Windows程序中的函数被称为“窗口过程”。
无疑,读者对程序调用操作系统的思路是很熟悉的。例如,程序在打开磁盘文件时就要使用有关的系统调用。读者所不习惯的,可能是操作系统调用程序,而这正是Windows面向对象体系结构的基础。
程序创建的每一个窗口都有相关的窗口过程。这个窗口过程是一个函数,既可以在程序中,也可以在动态链接库中。Windows通过调用窗口过程来给窗口发送消息。窗口过程根据此消息进行处理,然后将控制返回给Windows。 更确切地说,窗口通常是在“窗口类”的基础上创建的。窗口类标识了处理窗口消息的窗口过程。使用窗口类使多个窗口能够基于同一个窗口类,并且使用同一个窗口过程。例如,所有Windows程序中的所有按钮均基于同一个窗口类。这个窗口类与一个位于Windows动态链接库(处理所有的按钮窗口消息)的窗口过程相联系。在面向对象的程序设计中,对象是代码与数据的组合。窗口是一种对象,其代码是窗口过程。数据是窗口过程保存的信息,以及Windows为每个窗口系统中那个窗口类保存的信息。窗口过程处理给窗口发送的消息。这些消息经常是告知窗口,用户正使用键盘或鼠标进行输入。这正是按钮窗口知道它被“按下”的奥妙所在。在窗口大小改变或窗口表面需要重画时,由其他消息通知窗口。
Windows程序开始执行后,Windows为该程序创建一个“消息队列”。这个消息队列用来存放该程序可能创建的各种不同窗口的消息。程序中有一小段代码,叫做“消息循环”,用来从队列中取出消息,并且将它们发送给相应的窗口过程。有些消息直接发送给窗口过程,不用放人消息队列中。如果您对这段有关Windows体系结构的过于简略的描述将信将疑,就让我们去看看在实际的程序中,窗口、窗口类、窗口过程、消息队列、消息循环和窗口消息是如何相互配合的。这或许对您会有些帮助。创建一个窗口首先需要注册一个窗口类,那需要一个窗口过程来处理窗口消息。这包 括一段几乎出现在每一个Window程序中的固定代码。程序2.1所示的HELLOWIN程序就是一个包括这段固定代码的简单程序。
二、程序2.1
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT,WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
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
TEXT ("The Hello Program"), // 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)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE:
PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
三、总体考虑
实际上,每一个Windows程序代码中都包括HELLOWIN.C程序的大部分。没人能真正记住编写此代码的全部语法;通常,Windows程序员在开始一个新程序时总是复制一个 现有的程序,然后再做相应的修改。上面提到,HELLOWIN将在其窗口的中央显示文本串。
那不完全正确。文本实际显示在程序客户区的中央,它在图中是标题栏和边界范围内的大片白色区域。这种特性对我们很重要;客户区就是程序自由绘图并且向用户交付可观输出的窗口区域。如果您认真思考一下,将会发现虽然只有80行代码,这个窗口却令人惊讶地具有许多功能。您可以用鼠标按住标题栏,在屏幕上移动窗口;可以按住边框,改变窗口的大小。在窗口大小改变时,程序自动地将“hello,windows 98!”文本串重新定位在客户区域的中央。您可以按最大化按钮,放大HELLOWIN以充满整个屏幕;也可以按最小化按钮,将程序压缩为一个图标。
您可以从系统菜单(标题栏最左端的小图际)中执行所有选项;也可以通过从系统莱单中选择Close选项,或者通过单未标题栏最右端的关闭按钮,或者通过双击标题栏最左端的图标来关闭窗口以终止程序的执行。
当然,我们首先要从整体上看一下。HELLOWIN.C也有一个WinMain函数,但它还有另外一个函数,名为WndProc。这就是窗口过程。注意,在HELLOWIN,C中没有调用 WndProc的代码。然而,在 WinMain中有对WndProc的声明,这就是函数要在程序顶部附近声明的原因。
1.Windows 函数调用
HELLOWIN 至少调用了18个Windows函数。下面以它们在
HELLOWIN.C 中出现的次序列出这些函数以及各自的简明描述:
LoadIcon 加载图标供程序使用;
LodCursor 加载鼠标指针供程序使用;
GetStockObject 获取一个图形对象,在这个例子中,是获取绘制窗口背景的刷子;
RegisterClass 为程序窗口注册窗口类;
MessageBox 显示消息框;
CreateWindow 根据窗口类创建一个窗口;
ShowWindow 在屏幕上显示窗口;
UpdateWindow 指示窗口刷新自身;
GetMessage 从消息队列中获取消息;
TranslateMessage 转换某些键盘消息;
DispatchMessage 将消息发送给窗口过程;
PlaySound 播放一个声音文件;
BeginPaint 开始窗口绘制;
GetClientRect 获取窗口客户区的尺寸;
DrawText 显示文本串;
EndPaint 结束窗口绘制;
PostQuitMessage 在消息队列中插入一条“退出”消息;
DefWindowProc 执行默认的消息处理。
这些函数均在平台SDK文档中说明,并在不同的头文件中声明,其中绝大多数在WINUSER.H中声明。
2.大写的标识符
大家可能注意到,在IIELIDWIN.C中有很多大写的标识符,这些标识符都是在Windows的头文件中定义的。其中有些标识符包含由两个字母或者三个字母组成并后跟一个下划线的前缀:
CS_HREDRAW
CS_VREDRAW
IDI_APPLICATION
IDC_ARROW
MB_ICONERROR
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
这些都是简单的数值常量。前缀指示该常量所属的类别,如下页表所示。
在编写 Windows 程序时,不用去记这些数值常量。实际上, Windows 中使用的每个数值常量在头文件中均有相应的标识符定义。
表3.1 | |
前缀 | 类别 |
CS | 类风格选项 |
CW | 创建窗口选项 |
DT | 绘制文本选项 |
IDI | 图标ID号 |
IDC | 光标ID号 |
MB | 消息框选项 |
SND | 声音选项 |
WM | 窗口消息 |
WS | 窗口风格 |
3. 新的数据类型
HELLOWIN.C中的其他标识符是新的数据类型,也在Window头文件中使用 typedef 语句或者#define语句加以定义。这最初是为了便于将Windows程序从原来的16位系统上移植到本来的基于32位(或者其他)技术的操作系统上。这种作法并不如当时每个人想象的那样顺利,但是这种概念基本上是正确的。
有时这些新的数据类型只是为了方便起见的一个缩写。例如,用于WinProc的第二个参数的 UINT数据类型只是一个 unsigned int(无符号整数),在 Windows 98中,这是一个32位的值。
用于WinMain的第三个参数的PSTR数据类型是指向一个字符串的指针,即是一个char*。
其他数据类型的含义不太明显。例如,WndProc的第三和第四个参数分别被定义WPARAM和LPARAM,这些名字的来源有点历史背景:当Window是一种16位系统时,WndProc的第三个参数被定义为一个WORD,这是一个历位的无符号短整数,而第四个参数被定义为一个LONG,这是一个32位有符号的长整数,从而导致了对单词“PARAM”加前缀“W”和“L”。当然,在32位的Windows中,WPARAM被定义为一个UINT,而LPARAM被定义为一个LONG (就是C中的long数据类型),因此窗口过程的这两个参数都是 32位的值。这也许有点奇怪,因为 WORD数据类型在 Windows 98中仍然被定义为16位的无符号整数,因此“PARAM”的“W”在某种意义上是不正确的名字。
WndProc函数返回一个类型为LRESULT的值,该值简单地被定义为一个LONG。WinMain函数被指定为一个WINAPI类型(在头文件中定义的所有Windows函数都被指定这种类型),而WndProc函数被指定为一个CALLBACK类型。这两个标识符都被定义为_stdcall,指在Windows本身和用户的应用程序之间发生的函数调用的特殊调用序列。
HELLOWIN还使用了Windows头文件中定义的4种数据结构(我们将在本章稍后加以讨论)。这些数据结构如表所示。
表中前面两个数据结构在WinMain中使用,分别定义了两个名为msg和wndclass的结构,后面两个数据结构在WndProc中使用,分别定义了ps和rect结构。
结构 | 含义 |
MSC | 消息结构 |
WNDCLASS | 窗口类结构 |
PAINTSTRUCT | 绘图结构 |
RECT | 矩形结构 |
4. 句柄简介
最后,还有3个大写标识符(见表),用于不同类型的“句柄”。
标识符 | 含义 |
HINSTANCE | 实例(程序自身)句柄 |
HWND | 窗口句柄 |
HDC | 设备描述表句柄 |
句柄在Windows中使用非常频繁。在本章结束之前,我们将遇到HICON(图标句柄)、 HCURSOR鼠标指针句柄)和HBRUSH(图形刷句柄)。
句柄是一个数(通常为32位),它代表一个对象。Windows中的句柄类似传统C或者MS-DOS程序设计中使用的文件句柄。程序几乎总是通过调用Windows函数获取句柄。程序在其他Window函数中使用这个句柄,以引用它代表的对象。句柄的实际值对程序来说是无关紧要的。但是,向您的程序提供句柄的Windows模块知道如何使用它来引用相应的对象。
5.匈牙利表示法
要在Windows上开发应用程序,就必须对Windows程序设计有所了解。
在编程时,变量、函数的命名是一个极其重要的问题。好的命名方法使变量易于记忆且程序可读性大大提高。 Microsoft采用匈牙利命名法来命名 Windows API函数和变量。匈牙利命名法是由Microsoft的著名开发人员,Excel的主要设计者查尔斯·西蒙尼在他的博士论文中提出来的,由于西蒙尼的国籍是匈牙利,所以这种命名法叫匈牙利命名法。
匈牙利命名法为C标识符的命名定义了一种非常标准化的方式,这种命名方式是以下面两条规则为基础的:
(1)标识符的名字以一个或者多个小写字母开头,用这些字母来指定数据类型。下表列出了常用的数据类型的标准前缀。
(2)在标识符内,前缀以后就是一个或者多个第一个字母大写的单词,这些单词清楚地指出了源代码内那个对象的用途。比如,m_szStudentName表示一个学生名字的类成员变量,数据类型是字符串型。
表1.2 在Windows里定义数据类型的一此标准前缀 | |
前缀 | 数据类型 |
c | 字符(char) |
s | 短整数(short) |
cb | 用于定义对象(一般为一个结构)尺寸的整数 |
n | 整数(integer) |
sz | 以'\0'结尾的字符串 |
b | 字节 |
i | int(整数) |
x | 短整数(坐标X) |
y | 短整数(坐标Y) |
f | BOOL |
w | 字(WORD,无符号短整数) |
l | 长整数(long) |
h | HANDLE(无符号int) |
m_ | 类成员变量 |
fn | 函数(function) |
dw | 双字(DWORD,无符号长整数) |
6. 头包含
Windows应用程序中使用的各种Windows API函数声明,宏定义,数据类型定义,windows.h是主要的包含文件,它包含了其他windows的头文件,这些头文件的某些也包含了其他头文件。这些头文件中最重要的和最基本的是:
WINDEF.H 基本类型定义;
WINNT.H 支持Unicode 的类型定义;
WINBASE.H 内核函数;
WINUSER.H 用户接口函数
WINGDU.H 图形设备接口函数。
这些头文件定义了Windows 的所有数据类型、函数调用、数据结构和常数标识符,它是Windows文档中的一个重要部分。使用 Visual C++ Developer Studio 的Edit 菜单中的Find in Files 搜索这些头文件非常方便。还可以在 Developer Studio中打开这些头文件,并直接阅读它们。
注册窗体类函数
窗口总是在窗口类的基础上创建的,窗口类用以标识处理窗口消息的窗口过程。可以在单个窗口类的基础上创建多个窗口。例如,Windows中的所有按钮窗口—包括下压按钮。复选框,以及单选按钮——都是基于同一窗口类创建的。窗口类定义了窗口过程和基于此类创建的窗口的其他一些特征。在创建窗口时,要定义一些窗口所独有的特征。
在为程序创建窗口之前,必须首先调用RegisterClass注册一个窗口类。该函数只需要一个参数,即一个指向类型为WNDCLASS的结构指针。
一.注册窗体类函数声明
建立窗体之前,首先要对决定窗体显示风格的窗体类进行注册。
注册窗体类函数声明如下:
ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx);
二.形参
lpwcx 是一个指向窗体类结构类型的指针变量。
窗体类结构类型定义如下:
typedef struct _WNDCLASSEX
{
UINT cbSize; // 窗体类结构类型长度;
UINT style; // 窗体风格;
WNDPROC lpfnWndProc; // 窗体过程指针;
int cbClsExtra; // 系统保留;
int cbWndExtra; // 系统保留;
HANDLE hInstance; // 实例句柄;
HICON hIcon; // 窗体图标;
HCURSOR hCursor; // 窗体光标;
HBRUSH hbrBackground; // 窗体客户区背景色;
LPCTSTR lpszMenuName; // 窗体菜单指针;
LPCTSTR lpszClassName; // 窗体类名称;
HICON hIconSm; // 窗体小图标。
}WNDCLASSEX;
1.窗体风格选项:
窗体风格各个选项可以用 OR (|) 运算符联合表示。
CS_BYTEALIGNCLIENT
CS_BYTEALIGNWINDOW
CS_CLASSDC
CS_DBLCLKS
CS_GLOBALCLASS
CS_HREDRAW
CS_NOCLOSE
CS_OWNDC
CS_PARENTDC
CS_SAVEBITS
CS_VREDRAW
2.窗体客户区背景色选项:
COLOR_ACTIVEBORDER
COLOR_ACTIVECAPTION
COLOR_APPWORKSPACE
COLOR_BACKGROUND
COLOR_BTNFACE
COLOR_BTNSHADOW
COLOR_BTNTEXT
COLOR_CAPTIONTEXT
COLOR_GRAYTEXT
COLOR_HIGHLIGHT
COLOR_HIGHLIGHTTEXT
COLOR_INACTIVEBORDER
COLOR_INACTIVECAPTION
COLOR_MENU
COLOR_MENUTEXT
COLOR_SCROLLBAR
COLOR_WINDOW
COLOR_WINDOWFRAME
COLOR_WINDOWTEXT
三.返回值
如果成功返回非0值,否则返回0。
建立窗体函数
窗口类定义了窗口的一般特征,因此可以使用同一窗口类创建许多不同的窗口。实际调用CreateWindow创建窗口时,可能指定有关窗口的更详细的信息。新的Window程序员有时会混淆窗口类和窗口之间的区别,以及为什么一个窗口的所有特征不能被一步到位地指定。实际上,以这种方式分开这些风格信息是非常方便的。例如,所有的按钮窗口都可以基于同样的窗口类来创建。与这个窗口类相关的窗口过程位于Windows内部。由窗口类来负责处理按钮的键盘和鼠标输入,并定义按钮在屏幕上的外观形象。从这一点看来,所有的按钮都是以同样的方式工作的。但是并非所有的按钮都是一样的。它们可以有不同的大小,不同的屏幕位置,以及不同的正文串。后面的这样一些特征是窗口定义的一部分,而不是窗口类定义的。 建立窗体函数用于建立平铺式,弹出式或子窗体。
一、函数声明
HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);
形参:
1.dwExStyle:扩展窗体风格;
扩展窗体风格各个选项可以用 OR (|) 运算符联合表示,各个选项如下:
WS_EX_ACCEPTFILES
WS_EX_APPWINDOW
WS_EX_CLIENTEDGE
WS_EX_CONTEXTHELP
WS_EX_CONTEXTHELP
WS_EX_CONTROLPARENT
WS_EX_DLGMODALFRAME
WS_EX_LEFT
WS_EX_LEFTSCROLLBAR
WS_EX_LTRREADING
WS_EX_MDICHILD
WS_EX_NOPARENTNOTIFY
WS_EX_OVERLAPPEDWINDOW
WS_EX_PALETTEWINDOW
WS_EX_RIGHT
WS_EX_RIGHTSCROLLBAR
WS_EX_RTLREADING
WS_EX_STATICEDGE
WS_EX_TOOLWINDOW
WS_EX_TOPMOST
WS_EX_TRANSPARENT
WS_EX_WINDOWEDGE
2.lpClassName:窗体类名称;
3.lpWindowName:窗体名称;
如果窗体名称用下列选项之一,则建立的是控件窗体。
BUTTON
COMBOBOX
EDIT
LISTBOX
MDICLIENT
RichEdit
RICHEDIT_CLASS
SCROLLBAR
STATIC
4.dwStyle:窗体风格;
窗体风格各个选项可以用 OR (|) 运算符联合表示,
各个选项如下:
WS_BORDER
WS_CAPTION
WS_CHILD
WS_CHILDWINDOW
WS_CLIPCHILDREN
WS_CLIPSIBLINGS
WS_DISABLED
WS_DLGFRAME
WS_GROUP
WS_HSCROLL
WS_ICONIC
WS_MAXIMIZE
WS_MAXIMIZEBOX
WS_MINIMIZE
WS_MINIMIZEBOX
WS_OVERLAPPED
WS_OVERLAPPEDWINDOW
WS_POPUP
WS_POPUPWINDOW
WS_SIZEBOX
WS_SYSMENU
WS_TABSTOP
WS_THICKFRAME
WS_TILED
WS_TILEDWINDOW
WS_VISIBLE
WS_VSCROLL
5.x:窗体左上角X坐标;
6.y:窗体左上角Y坐标;
7.nWidth:窗体宽度;
8.nHeight:窗体高度;
9.hWndParent:父窗体句柄;
10.hMenu:菜单句柄;
11.hInstance:实例句柄;
12.lpParam:系统保留。
返回值如果成功返回新窗体句柄,否则返回NULL。
二、实例分析
传递给 RegisterClass函数的信息在一个数据结构中说明,而传递给CreateWindow函数的信息作为函数的单独参数来说明。下面是HELLOWIN. C中的CreateWindow调用,每一个域都做了完整的注释:
此程序创建的窗口是一个普通的重叠式窗口。它含有一个标题栏,标题栏左边有一个系统菜单框,标题栏右边有缩小、放大和关闭图标,四周还有一个表示窗口大小的边框。这是标准风格的窗口,名为WS OVERLAPPEDWINDOW,该参数按“窗口风格”出现在CreateWindow中。
当创建一个“最高级”窗口,如应用程序窗口时,注释为“父窗口句柄”的参数设置为NULL。通常,如果有窗口之间存在父子关系,则子窗口总是出现在父窗口的上面。应用程序窗口出现在桌面窗口的上面,但不必为调用CreateWindow而找出桌面窗口的句柄。
因为窗口没有菜单,所以“窗口菜单句柄”也设置为NULL。“程序实例句柄”设置为实例句柄,它是作为WinMain的参数传递给这个程序的。最后,“创建参数”指针设置为NULL,可以用这个指针访问以后想要引用的程序中的数据。
CreateWindow调用返回被创建的窗口的句柄,该句柄存放在变量hwnd中,后者被定义为HWND类型(“窗口句柄类型”)。Windows中的每个窗口都有一个句柄,程序用句柄来引用窗口。许多Windows函数需要使用hwnd作为参数,这样,Window才能知道函数是针对哪个窗口的。如果一个程序创建了许多窗口,则每个窗口均有一个句柄。窗口句柄是Windows程序处理的最重要的句柄之一。
显示和更新窗体函数
在CreateWindow调用返回之后,Window内部已经创建了这个窗口。这就是说,Windows已经分配了一块内存,用来保存关于在CleateWindow调用中指定窗口的全部信息,再加上Windows在随后所能找到的所有其他信息。 然而,窗口并未在视频显示器上出现,这就需要再来两个调用,它们是:ShowWindow 和 UpdateWindow。
一、显示窗体客户区函数声明
BOOL ShowWindow (HWND hWnd,int nCmdShow);
形参:
1.hWnd:窗体句柄;(是刚刚用CreateWindow创建的窗口句柄)
2.nCmdShow:窗体显示方式,与WinMain函数定义的窗体显示方式相同。它确定最初如何在屏幕上显示窗口,是常规、最小化还是最大化。返回值:如果显示窗体是可见的,返回TRUE,否则返回FALSE。
二、更新窗体客户区函数声明
BOOL UpdateWindow (HWND hWnd);
形参:
hWnd:窗体句柄;
Handle to the window to be updated.
返回值:如果成功返回TRUE,否则返回FALSE。
ShowWindow函数在显示器上显示窗口。如果ShowWindow的第二个参数是SW_SHOWNORMAL,则窗口的客户区域就被窗口类中定义的背景刷子所覆盖。函数调用 UpdateWindow(hwnd); 导致客户区域被绘制。它通过给窗口过程(即HELLOWIN.C中的WndProc函数)发送一个WM_PAINT消息来做到这一点。后面,我们将说明WndProc如何处理这个消息。
建立消息循环
调用UpdateWindow之后,窗口就出现在视频显示器上。程序现在必须准备读入用户用键盘和鼠标输入的数据。Windows为当前运行的每个Windows程序维护一个“消息队列”。在发生输人事件之后,Windows将事件转换为一个“消息”,并将消息放入程序的消息队列中。程序通过执行一块被称为“消息循环”的代码从消息队列中取出消息:
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
msg变量是类型为MSG的结构,类型MSG在WINUSER.H中定义如下:
消息结构类型用于描述一个线程的消息队列中的消息。 其定义如下:
typedef struct tagMSG
{
HWND hwnd; // 接受消息窗体过程的窗体句柄;
UINT message; // 消息类型;
WPARAM wParam; // 消息附加值;
LPARAM lParam; // 消息附加值;
DWORD time; // 消息传送时间;
POINT pt; // 消息传送时光标在屏幕上的位置;
}MSG;
POINT结构类型也是一个结构,它在WINDEF.H
中定义如下:
typedef struct tagPOINT
{
LONG x; // 光标在屏幕上的X坐标;
LONG y; // 光标在屏幕上的Y坐标;
} POINT;
消息循环以CetMessage调用开始,它从消息队列中取出一个消息 获取消息函数:GetMessage(&msg,NULL,0,0)
这一调用传给Windows一个指向名为msg的MSG结构的指针。第二、第三和第四个参数设置为NULL或者0,表示程序接收它自己创建的所有窗口的所有消息。
获取消息函数用于从一个线程的消息队列中获取消息,其声明如下:
BOOL GetMessage(LPMSG lpMsg, HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
2.形参
A.lpMsg:消息结构类型变量指针;
B.hWnd:接受消息窗体过程的窗体句柄;
C.wMsgFilterMin:消息值下限过滤器;
D.wMsgFilterMax:消息值上限过滤器。
3.返回值
如果获取的消息是WM_QUIT消息,则返回非0值,否则返回0。
只要从消息队列中取出消息的message域不为WM_QUIT。GetMessage就返回一个非 0值。 WM_QUIT消息将导致 GetMessage返回 0。
TranslateMessage(&msg)将msg结构传给Windows,语句DispatchMessage(&msg);又将msg结构回传给Windows。然后,Wndows将该消息发送给适当的窗口过程,让它进行处理。这也就是说,Windows将调用窗口过程。在HELLOWIN中,这个窗口过程就是WndProc函数。处理完消息之后,WndProc返回到Windows。此时,Windows还停留在DispatchMessage调用中。在结束DispatchMessage调用的处理之后,Windows回到HELLLWIN,并且接着从下一个GetMessage调用开始消息循环。
翻译消息函数:
1.翻译消息函数声明
翻译消息函数用于把虚键值翻译成字符的ASCII值,
其声明如下:
BOOL TranslateMessage (CONST MSG *lpMsg);
2.形参
lpMsg:消息结构类型变量指针。
3.返回值
如果翻译的消息来源于某个线程的消息队列,则返回TRUE,否则返回FALSE。?
四.发送消息函数
1.发送消息函数声明
发送消息函数用于把消息发送窗体过程,其声明如下:
LONG DispatchMessage (CONST MSG *lpmsg);
2.形参
lpMsg:消息结构类型变量指针。
3.返回值接受消息窗体过程的函数指针。
窗体过程
以上我们所讨论的实在只是准备性的工作:注册窗口类,创建窗口,然后在屏幕上显示窗口,程序进入消息循环,从消息队列中取出一条消息。 实际的动作发生在窗口过程中。窗口过程确定了在窗口的客户区域中显示些什么,以及窗口怎样响应用户输入。
在HELLOWIN中,窗口过程是命名为WndProc的函数。窗口过程可任意命名(只要不和其他名字发生冲突)。一个Windows程序可以包含多个窗口过程。一个窗口过程总是与调用RegisterClass注册的特定窗口类相关联。CreateWindow函数根据特定窗口类创建一个窗口。但基于一个窗口类,可以创建多个窗口。
一、 窗口过程定义为:
LRESULT CALLBACK WndProc(HWND hwnd,UINT message, WPARAM wParam,LPARAM lParam)
注意,窗口过程的4个参数与MSG结构的前4个域是相同的。第一个参数hwnd是接收消息的窗口的句柄,它与CreateWindow函数的返回值相同。对于与HELLOWIN 相似的程序(只创建一个窗口),这个参数是程序所知道的唯一窗口句柄。如果程序是基于同一窗口类(同时也是同一窗口过程)创建多个窗口,则hwnd标识接收该消息的特定窗口。
第二个参数与MSG结构中的message域相同,它是标识该消息的数字。最后两个参数都是32位的消息参数,它提供关于消息的更多信息。这些参数包含每个消息类型的详细信息。有时消息参数是两个存放在一起的16位值,而有时消息参数又是一个指向正文串或数据结构的指针。
程序通常不直接调用窗口过程。窗口过程通常由Window本身调用。通过调用endMessage函数,程序能够直接调用它自己的窗口过程。我们将在后面的章节讨论SendMessage函数。
二、处理消息
窗口过程接收的每个消息均是用一个数值来标识的,也就是传给窗口过程的message参数。Windows头文件WINUSER.H为每个消息参数定义以“WM”(窗口消息)为前缀的标识符。
一般来说,Windows程序员使用switch和case结构来确定窗口过程接收的是什么消息,以及如何适当地处理它。窗口过程在处理消息时,必须返回几窗口过程不予处理的所有消息应该被传给名为DefWindoProc的Windows函数。从DefwindowProc返回的值必须由窗口过程返回。
在HELLOWIN中,WndProc只选择处理三种消息:WM_CREATE、WM_PAINT和WM_DESTROY。调用DefWindowProc来为窗口过程不予处理的所有消息提供默认处理,这是很重要的。除非能按常规运行,比如能在不工作时终止程序。
三、播放声音文件
窗口过程接收的第一个消息——也是WndProc选择处理的第一个消息—WM_CREATE。当Windows在WinMain中处理CreateWindow函数时,WndProc接收这个消息。 即当HELLOWIN调用CreateWindow时,WndProc将做一些它必须做的工作。在这些工作中,Windows调用WndProc,将第一个参数设置为窗口句柄,第二个参数设置为WM_CREATE。Windows然后可以从CreateWindow调用中返回到HELLOWIN,继续在WinMain中进行下一步的处理。
通常,窗口过程在WM_CREATE处理期间进行一次窗口初始化。HELLOWND对这条消息的处理中播放一个名为HELLOWND.WAV的声音文件。它使用简单的PlaySound函数来做到这一点。
PlaySound的第一个参数是声音文件的名称。第二个参数只有当声音文件是一种资源时才被使用。第三个参数指定一些选项。在这个例子中,指定第一个参数是一个文件名,并且异步地播放声音,即PlaySound函数调用在声音文件开始播放时立即返回,而不会等待它的完成。在这种方法下,程序能够继续初始化。WndProc通过从窗口过程中返回0,结束整个WM_CREATE的处理。
四、WM_PRINT消息
WndProc处理的第二个消息为WM_PAINT。这个消息在Windows程序设计中是很重要的。当窗口客户区域的一部分或者全部变为“无效”,以致于必须“刷新”时,将由这个消息通知程序。
客户区域怎么会变得无效呢?在最初创建窗口的时候,整个客户区域都是无效的,因为程序还没有在窗口上画什么东西。第一个WM_PAINT消息(通常发生在WinMain 中调用UpDateWindow时)指示窗口过程在客户区域上画一些东西。
在用户改变HELLOWIN窗口的大小后,客户区域重新变得无效。读者应该还记得,HELLOWIN中 wndclass结构的 style域设置为标志 CS_HREDRAW和 CS_VREDRAW, 这一风格指示Windows,在窗口大小改变后,使整个窗口无效。然后,窗口过程将收到一个WM_PAINT消息。
当用户将HELLOWIN最小化,然后再次将窗口恢复为以前的大小时,Windows将不会保存客户区域的内容。在图形环境下,这涉及的数据量很大。因此,Windows令窗口无效,窗口过程接收一个WM_PAINT消息,并自动恢复其窗口的内容。
在移动窗口以使其相互重叠时,Windows不保存一个窗口中被另一个窗口所遮盖的内容。在这一部分不再被遮盖之后,它就被标志为无效。窗口过程接收到一个WM_PAINT消息,以刷新窗口的内容。
对WM_PAINT的处理几乎总是从一个BeginPaint调用开始:hdc=BeginPaint(hwnd, &ps);而以一个EndPaint调用结束:EndPaint(hwnd,&ps);在这两个调用中,第一个参数都是程序的窗口句柄,第二个参数是指向类型为PAINTSTRUCT的结构指针。PAINTSTRUCT结构中包含一些窗口过程,可以用来刷新客户区域的内容。我们将在下面的章节中讨论该结构的各个域。现在我们只在 BeginPaint和 EndPaint函数中用到它。
在 BeginPaint 调用中,如果客户区域的背景还未被删除,则由 Windows 来删除。它使用注册窗口类的 WNDCIASS 结构的 hbrBackGround域中指定的刷子来删除背景。在 HELLOWIN 中,这是一个白色备用刷子。这意味着, Windows 将通过把窗口背景设置为白色来删除窗口背景。 BeginPaint 调用使整个客户区域有效,并返回一个 “ 设备描述表句柄 ” 。设备描述表是指物理输出设备(如视频显示器)及其设备驱动程序。在窗口的客户区域显示文本和图形需要设备描述表句柄。但是不能用从 BeginPaint 返回的设备描述表句柄在客户区域之外绘图,读者可以试一试。 EndPaint 释放设备描述表句柄,使之不再有效。如果窗口过程不处理 WM_PAINT 消息(这是非常少见的情况) ,它们必须被传送给 DefWindowProc 。 DefWindowProc 只是依次调 用 BeginPaint 和 EndPaint ,以使客户区域有效。
调用完BeginPaint之后, WndProc接着调用
GetClientRect:
GetClientRect(hwnd,&rect);
第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECF类型的rectangle结构。该结构有 4个 LONG域,分别为 left、top、right和 bottom。 GetClientRect将这个域设置为窗口客户区域的尺寸。left和top域通常设置为0,right和bottom域设置为客户区域的宽度和高度(像素点数)。WndProc除了将该RECT结构指针作为DrawText的第4个参数传递外,不再对它做
其他处理:
DrawText(hdc,TEXT(“Hello, Windows 98!”),-l,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTRE);
DrawText可以输出文本(正如其名字所表明的一样)。由于该函数要输出文本,第一个参数是从BeginPaint返回的设备描述表句柄,第二个参数是要输出的文本,第三个参数是-l,指示文本串是以字节0终结的。
DrawText最后一个参数是一系列位标志,它们均在WINUSER.H中定义。标志指示了文本必须显示在一行上,水平方向和垂直方向都位于第4个参数指定的矩形中央。因此,这个函数调用将导致串“HellO,Windows 98!”显示在客户区域的中央。
一旦客户区域变得无效(正如在改变大小时所发生的情况一样),WndProc就接收一个新的WM_PAINT消息。WndProc通过调用GetClientRect获取变化后的窗口大小,并在新窗口的中央显示文本。
五、WM_DESTROY消息
WM_DESTROY消息是另一个重要消息。这一个消息指示,Windows正在根据用户输入的命令来清除窗口。该消息是用户单击Close按钮或者在程序的系统菜单上选择Close时发生的。(在本章的后面,我们将详细讨论WM_DESTROY消息是如何生效的。)
HELLOWIN通过调用PustQuitMessage以标准方式响应 WM_DESTROY消息:
PustQuitMessage(0);该函数在程序的消息队列中插入一个WM_QUIT消息。前面提到过,GetMessage对于除了 WM_QUIT之外的从消息队列中取出的所有消息都返回非 0值。而当 GetMessage取到一个WM_ QUIT消息时,它返回0, 这将导致 WinMain退出消息循环,并终止程序。然后程序执行下面的语句:
return msg. wParam;
结构的wParam 域是传递给PostQuitMessage函数的值(通常是0)。然后返回语句将退出WinMain并终止程序。
其他函数
1.形参
A.hInst:主函数实例句柄;
B.lpszName:图像资源名称;
C.uType:图像资源类型;
IMAGE_BITMAP
IMAGE_CURSOR
IMAGE_ICON
D.cxDesired:图像宽度;
E.cyDesired:图像高度;
F.fuLoad:图像显示方式。
2.返回值
装入图像资源的句柄。
默认窗体过程函数
1.默认窗体过程函数声明
默认窗体过程函数用于处理用户不需要处理
的消息, 其声明如下:
LRESULT DefWindowProc(HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam);
2.形参
a.hWnd: 窗体句柄;
b.Msg:消息类型;
c.wParam:消息附加值;
d.lParam:消息附加值;
3.返回值
与所处理的消息有关。
实例
#include <windows.h>
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance,IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground = (HBRUSH)COLOR_WINDOW;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "SimpleWindowClass";
wcex.hIconSm = LoadIcon(hInstance,IDI_APPLICATION);
if(!RegisterClassEx(&wcex)) return FALSE;
hWnd = CreateWindow(
SimpleWindowClass,
Skeleton of Programming,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
if(!hWnd) return FALSE;ShowWindow(hWnd, nCmdShow);
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)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}