4.2第一个窗口程序

创建Win32工程和MessageBox函数

前面讲的程序都是使用控制台界面来接受输入、显示输出的。要想使用窗口界面与用户交互必须首先创建一个Win32工程。
(1)运行VC++6.0,选择菜单命令“File/New…”,在弹出的New对话框中打开Projects选项卡,选项卡左侧列表框中有多种工程类型,要创建Win32应用程序,选中 Win32 Application选项,然后在右侧的“Project Name”中输入工程名04Win32AppDemo,在“Location”中输入存放工程文件的路径。
(2)单击OK按钮,出现要求选择 Win32应用程序类型的对话框。VC将根据不同选择自动产生不同的程序代码,在这里选中第二项“A simple Win32 application"。
(3)点击Finish按钮,完成工程创建。
现在打开由VC自动生成的04Win32AppDemo.cpp文件会发现以前熟知的main 函数没有了,变成了WinMain函数,这就是程序的入口地址。

StdAfx.h

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers

#include <windows.h>

// TODO: reference additional headers your program requires here

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)

StdAfx.cpp

// stdafx.cpp : source file that includes just the standard includes
//	04Win32AppDemo.pch will be the pre-compiled header
//	stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

04Win32AppDemo.cpp

///
// 04Win32AppDemo.cpp文件
#include "stdafx.h"

int APIENTRY WinMain(HINSTANCE hInstance,	// 本模块的实例句柄
	HINSTANCE hPrevInstance,	// Win16留下的废物,现在已经不用了
	LPSTR     lpCmdLine,	// 命令行参数
	int       nCmdShow)	// 主窗口初始化时的显示方式
{
	int nSel = ::MessageBox(NULL, "Hello, Win32 Application",
		"04Win32AppDemo", MB_YESNOCANCEL | MB_ICONQUESTION | MB_DEFBUTTON3); //弹出小窗口
	if (nSel == IDYES)		// 用户选择了“是”按钮
	{
	}
	else if (nSel == IDNO)		// 用户选择了“否”按钮
	{
	}
	else if (nSel == IDCANCEL)	// 用户选择了“取消”按钮
	{
	}

	return 0;
}

在这里插入图片描述

GUI应用程序的入口函数是WinMain,这是一个自定义的回调函数。APIENTRY是_stdcall的宏定义,说明WinMain函数采用的是Windows标准调用方式。系统传递给WinMain 函数的几个参数含义如下:

● hInstance指定了当前模块的实例句柄。其实在Win32下,模块的实例句柄和模块句柄是一样的,只是说法不同,所以可以通过以下语句获得当前可执行模块的实例句柄。

hInstance = (HINSTANCE)GetModuleHandle(NULL); //取得应用程序的实例句柄(模块句柄)

GetModuleHandle函数的惟一参数是模块的名称,函数会返回这个模块的句柄。模块句柄的值就是该模块在内存中的首地址。如果为GetModuleHandle传递NULL的话,函数返回的是可执行文件所在模块的模块句柄,而不管是在哪个模块中做这个调用的。

● lpCmdLine是命令行参数。其值由CreateProcess 函数的第二个参数指定。通常应用程序在初始化时检查这个参数,以决定是否打开特定文档。

● nCmdShow指定了窗口初始化时的显示方式。这个值也是由CreateProcess 函数传递的。一般以这个值为参数调用ShowWindow就可以了,此函数用于设置窗口的显示状态,过一会儿再讨论它。

MessageBox 函数的作用是弹出一个小的对话框向用户显示短信息,并将用户最终的选择返回给调用者。其函数原形如下。

int MessageBox(
	HWND hWnd, //一个窗口句柄,它指定了哪一个窗口将拥有要创建的消息框
	LPCTSTR lpText, //将要显示的消息
	LPCTSTR lpCaption, //对话框标题
	UINT uType //指定对话框的内容和行为
);

窗口句柄 HWND惟一地标识了一个窗口,大多数管理窗口的函数都使用它。04Win32AppDemo程序为这个参数传递了NULL,表明没有窗口拥有弹出的对话框。第4个参数uType指定了对话框的内容和行为,其值可以是来自下列各组标志的一个标志组合:

(1)为了指定希望在消息框中显示的按钮,要指定下组中的一个值:

MB_OK                       消息框包含一个按钮:确定。这是默认按钮。
MB_OKCANCEL                 消息框包含两个按钮:确定和取消
MB_ABORTRETRYIGNORE         消息框包含3个按钮:终止、重试和忽略
MB_YESNOCANCEL              消息框包含3个按钮:是、否和取消
MB_YESNO                    消息框包含两个按钮:是和否
MB_RETRYCANCEL              消息框包含两个按钮:重试和取消

(2)为了在对话框中显示一个图标,要指定下组中的一个值:

MB_ICONHAND                 一个停止标志图标:X
MB_ICONQUESTION             一个询问标志图标:?
MB_ICONEXCLAMATION          一个感叹号图标:!

(3)为了指示默认的选中按钮,要指定下组中的一个值:

MB_DEFBUTTON1               第一个按钮是选中按钮
MB_DEFBUTTON2               第二个按钮是选中按钮
MB_DEFBUTTON3               第三个按钮是选中按钮
MB_DEFBUTTON4               第四个按钮是选中按钮

在本程序中,MessageBox返回数值 IDOK,这个宏在WINUSER.H中定义,等于1。根据在消息框中显示的其他按钮,MessageBox函数还可返回IDYES、IDNO、IDCANCEL、IDABORT、IDRETRY或IDIGNORE。

int nSel = ::MessageBox(NULL, "Hello, Win32 Application",
		"04Win32AppDemo", MB_YESNOCANCEL | MB_ICONQUESTION | MB_DEFBUTTON3); //弹出小窗口
if (nSel == IDYES)		// 用户选择了“是”按钮
{
}
else if (nSel == IDNO)		// 用户选择了“否”按钮
{
}
else if (nSel == IDCANCEL)	// 用户选择了“取消”按钮
{
}

Windows的消息驱动

创建窗口后,就要对窗口的行为负责。比如,当用户拖拽窗口的标题栏时,应该跟随鼠标移动这个窗口,当用户点击最小化按钮时,应该最小化这个窗口等。如果不这么做,程序将失去窗口界面的友好性。但是,如何能够知道用户在窗口上的动作呢?

是操作系统告诉程序的。Windows不断向应用程序发送消息,通知发生了什么事情。比如用户改变了窗口的大小,Windows就向这个程序发送一个消息,指明窗口新的大小。

当Windows向程序发送消息时,它调用程序中的一个函数,这个函数的参数精确地描述了Windows 发送的消息。在程序中称这个函数为窗口函数(Window Procedure)或消息处理函数。它是一个自定义的回调函数,原形如下。

LRESULTCALLBACK WindowsProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM IParam);

CALLBACK宏是_stdcall的意思,说明采用Windows标准方式传递参数。hWnd参数标识了消息到达的窗口;uMsg 参数是一个被命名的常量(消息ID号),它指定了所发的消息,当窗口函数接受到消息时,它使用消息ID号来决定如何处理这个消息:wParam和 lParam是消息的两个参数,其值取决于uMsg。

例如,一般的应用程序在接受到WM_CLOSE消息后会去试图销毁自己的窗口,所以可以通过向窗口发送ID号为WM_CLOSE的消息来关闭它。下面代码为了关闭记事本程序,向它的主窗口发送了WM_CLOSE消息(04TelIToClose工程)。

StdAfx.h

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers

#include <windows.h>

// TODO: reference additional headers your program requires here

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)

StdAfx.cpp

// stdafx.cpp : source file that includes just the standard includes
//	04Win32AppDemo.pch will be the pre-compiled header
//	stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

04TellToClose.cpp

///
// 04TellToClose.cpp文件

#include "stdafx.h"
#include <windows.h>

int main(int argc, char* argv[])
{
	// 查找标题为“无标题 - 记事本”的窗口 
	// 也可以使用类名来查找,如::FindWindow("Notepad", NULL);
	HWND hWnd = ::FindWindow(NULL, "无标题 - 记事本");
	if (hWnd != NULL)
	{
		// 向目标窗口发送WM_CLOSE消息
		::SendMessage(hWnd, WM_CLOSE, 0, 0);
	}
	return 0;
}

FindWindow 函数用于查找窗口类名称和窗口标题与指定字符串相匹配的窗口。记事本程序的窗口类名称为“Notepad”,所以可以将它传递给Find Window查找记事本程序的主窗口,如果找到,返回的是记事本程序的主窗口句柄,否则返回NULL。

在运行这个程序前,要先打开系统自带的记事本程序。04TellToClose 会向这个记事本窗口发送WM_CLOSE消息,记事本主窗口的窗口函数接受到这个消息后就会关闭自己。

SendMessage函数用于向窗口发送消息,直到目标窗口处理完这个消息后才返回。函数的四个参数与窗口函数WindowProc的四个参数相对应。

上例是一个应用程序向另一个应用程序发送消息的过程,系统向应用程序发送消息的过程是相似的。系统为应用程序传递所有的输入到它的各个窗口,每个窗口都关联一个窗口函数,每当这个窗口有输入时,系统调用该函数。窗口函数处理输入,然后再将控制权交给系统。

写控制台程序的时候,如果想从用户取得输入,只要调用一个类似scanf的函数就行了,这些个函数会在取得输入后才返回。基于窗口界面的 Windows应用程序是事件驱动的(event-driven)。为了取得输入,它们并不做显式地函数调用,而是等待系统传递输入给它们。

创建窗口

下面先看一个最简单的窗口程序的源代码(04FirstWindow工程下),它的作用是弹出一个典型的Windows窗口。这些代码可以做为今后用API 写Windows程序的基本框架。本章以后的例子都是在这个框架的基础上扩充而来的。

StdAfx.h

// stdafx.h : include file for standard system include files,
//  or project specific include files that are used frequently, but
//      are changed infrequently
//

#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers

#include <windows.h>

// TODO: reference additional headers your program requires here

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)

StdAfx.cpp

// stdafx.cpp : source file that includes just the standard includes
//	04Win32AppDemo.pch will be the pre-compiled header
//	stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

04FirstWindow.cpp

///
// 04FirstWindow.cpp文件

#include "stdafx.h"

// 窗口函数的函数原形
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
	char szClassName[] = "MainWClass";
	WNDCLASSEX wndclass;

	// 用描述主窗口的参数填充WNDCLASSEX结构
	wndclass.cbSize = sizeof(wndclass);	// 结构的大小
	wndclass.style = CS_HREDRAW | CS_VREDRAW;	// 指定如果大小改变就重画
	wndclass.lpfnWndProc = MainWndProc;	// 窗口函数指针
	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 = szClassName;	// 窗口类的名称	
	wndclass.hIconSm = NULL;		// 没有类的小图标

	// 注册这个窗口类
	::RegisterClassEx(&wndclass);

	// 创建主窗口
	HWND hwnd = ::CreateWindowEx(
		0,			// dwExStyle,扩展样式	
		szClassName,		// lpClassName,类名			
		"My first Window!",	// lpWindowName,标题		
		WS_OVERLAPPEDWINDOW,	// dwStyle,窗口风格	
		CW_USEDEFAULT,		// X,初始 X 坐标		
		CW_USEDEFAULT,		// Y,初始 Y 坐标		
		CW_USEDEFAULT,		// nWidth,宽度			
		CW_USEDEFAULT,		// nHeight,高度			
		NULL,			// hWndParent,父窗口句柄			
		NULL,			// hMenu,菜单句柄		
		hInstance,		// hlnstance,程序实例句柄		
		NULL);			// lpParam,用户数据			

	if (hwnd == NULL)
	{
		::MessageBox(NULL, "创建窗口出错!", "error", MB_OK);
		return -1;
	}

	// 显示窗口,刷新窗口客户区
	::ShowWindow(hwnd, nCmdShow);
	::UpdateWindow(hwnd);

	// 从消息堆中取出消息
	MSG msg;
	while (::GetMessage(&msg, NULL, 0, 0))
	{
		// 转化键盘消息
		::TranslateMessage(&msg);
		// 将消息发送到相应的窗口函数
		::DispatchMessage(&msg);
	}

	// 当GetMessage返回0时程序结束
	return msg.wParam;
}


LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	char szText[] = "最简单的窗口程序!";
	switch (message)
	{
	case WM_PAINT: // 窗口客户区需要重画
	{
		HDC hdc;
		PAINTSTRUCT ps;

		// 使无效的客户区变的有效,并取得设备环境句柄
		hdc = ::BeginPaint(hwnd, &ps);
		// 显示文字
		::TextOut(hdc, 10, 10, szText, strlen(szText));
		::EndPaint(hwnd, &ps);
		return 0;
	}
	case WM_DESTROY: // 正在销毁窗口

		// 向消息队列投递一个WM_QUIT消息,促使GetMessage函数返回0,结束消息循环
		::PostQuitMessage(0);
		return 0;
	}

	// 将我们不处理的消息交给系统做默认处理
	return ::DefWindowProc(hwnd, message, wParam, lParam);
}

在这里插入图片描述

这是本书的第一个窗口程序,也是所有窗口程序的模板。以后要写一个新的程序,把上面的代码拷贝过来再添加特定功能的实现代码即可。

分析以上程序,可以得出在桌面上显示一个窗口的具体步骤,这就是主程序的结构流程。
(1)注册窗口类(RegisterClassEx)
(2)创建窗口(CreateWindowEx)
(3)在桌面显示窗口(ShowWindow)
(4)更新窗口客户区(UpdateWindow)
(5)进入无限的消息获取和处理的循环。首先是获取消息(GetMessage ),如果有消息到达,则将消息分派到回调函数处理(DispatchMessage),如果消息是WM_QUIT,则 GetMessage函数返回FALSE,整个消息循环结束。消息具体的处理过程是在MainWndProc函数中进行的。

分析主程序代码

本小节分析第一个窗口程序的实现细节。进程在创建窗口之前必须要注册一个窗口类。窗口类是系统在创建窗口时作为模板使用的属性集合。每个窗口都是特定窗口类的一员。

注册窗口
注册窗口类的API函数是RegisterClassEx,最后的“Ex”是扩展的意思,因为它是Win16的RegisterClass 函数的扩展。一个窗口类定义了窗口的一些主要属性,如:图标、光标、背景色和负责处理消息的窗口函数等。这些属性定义在WNDCLASSEX结构中。

typedef struct _WNDCLASSEXA {
    UINT        cbSize; //WNDCLASSEX结构的大小
    UINT        style; //从这个窗口类派生的窗口具有的风格
    WNDPROC     lpfnWndProc; //windows procedure 窗口消息处理函数指针
    int         cbClsExtra; //指定紧跟在窗口类结构后的附加字节数
    int         cbWndExtra; //指定紧跟在窗口实例后的附加字节数
    HINSTANCE   hInstance; //本模块的实例句柄
    HICON       hIcon; //窗口左上角图标的句柄
    HCURSOR     hCursor; //光标的句柄
    HBRUSH      hbrBackground; //背景画刷的句柄
    LPCSTR      lpszMenuName; //菜单名
    LPCSTR      lpszClassName; //该窗口类的名称
    HICON       hIconSm; //小图标句柄
} WNDCLASSEXA;

RegisterClassEx的惟一参数是这个结构的地址。注册窗口类后就可以将类名和其窗口函数、类的风格他其它的类属性联系起来。当进程在CreateWindowEx 函数中指定一个类名的时候,系统就用这个窗口函数、风格和与此类名相关的其他属性创建窗口。

04FirstWindow程序中WNDCLASSEX结构各字段的含义如下:

(1)指定窗口类风格。

wndclass style = CS_HREDRAW | CS_VREDRAW; //指定如果大小改变就重画

CS_HREDRAW|CS_VREDRAW风格指定如果窗口客户区的宽度或高度改变了,则重画整个窗口。前缀CS_意为class style,在 WINUSER.H中定义了全部可选样式。

#define CS_VREDRAW       0x0001
#define CS_HREDRAW       0x0002
#define CS_DBLCLKS       0x0008
#define CS_OWNDC         0x0020
#define CS_CLASSDC       0x0040
....

预定义的值实际上使用不重复的数据位,所以可以组合起来同时使用而不会引起混淆。

(2)指定窗口消息处理函数地址。

wndclass.lpfnWndProc = MainWndProc; //窗口函数指针

WNDCLASSEX结构的成员lpfnWndProc指定了基于此类的窗口的窗口函数,当窗口收到消息时Windows即自动调用这个函数通知应用程序。在程序的开头已经声明了函数的原型。

LRESULTCALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

(3)把本程序的实例句柄( WinMain的参数之一)传给hInstance成员。

wndclass.hInstance = hInstance; //程序实例句柄

(4)设置图标和光标。

wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION); //使用预定义图标
wndclass.hCursor = ::LoadCurson(NULL, IDC_ARROW); //使用预定义的光标

hlcon和 hCursor为要装载的图标和光标的句柄。Loadlcon函数装载了一个预定义的图标(命名为 IDI_APPLICATION ),LoadCursor函数装载了一个预定的光标(命名为IDC_ARROW)。如果要装载自定义的图标或光标的话,应该先向工程中添加一个资源脚本(详见4.3节),然后再通过菜单命令“Insert/Resource…”添加这些资源。

(5)指定窗口重画客户区时使用的画刷。

wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); //使用白色背景画刷

WHITE_BRUSH是一个Windows预定义的画刷对象类型,GetStockObject 函数取得这个画刷对象的句柄,传递给hbrBackground成员。

也可以自己创建一个画刷对象,以便指定喜欢的颜色做为窗口的背景色。例如,下面的代码将窗口的背景色设为了天蓝色。

wndclass.hbrBackground = ::CreateSolidBrush(RGB(0xa6, 0xca, 0xf0)); //创建一个纯色的刷子
...
::DeleteObject(wndclass.hbrBackground); //最后别忘了删除创建的刷子,释放资源

关于这些代码的更详细的叙述,请参考4.4节,现在还不是讲它们的时候。

(6)指定窗口类名称。

wndclass.lpszClassName = szClassName; // 窗口类的名称

在程序的开始已经定义了类名char szClassName[]="MainWClass";。这样设置此域,以后所有基于此类创建的窗口都要引用这个类名。

填充完WNDCLASSEX结构,就可以进行注册了。RegisterClassEx 函数调用失败将返回0。

::RegisterClassEx(&wndclass);

创建窗口
要创建窗口,用注册的窗口类的类名调用CreateWindowEx函数即可。

HWND hwnd = ::CreateWindowEx(
	0,			// dwExStyle,扩展样式	
	szClassName,		// lpClassName,类名			
	"My first Window!",	// lpWindowName,标题		
	WS_OVERLAPPEDWINDOW,	// dwStyle,窗口风格	
	CW_USEDEFAULT,		// X,初始 X 坐标		
	CW_USEDEFAULT,		// Y,初始 Y 坐标		
	CW_USEDEFAULT,		// nWidth,宽度			
	CW_USEDEFAULT,		// nHeight,高度			
	NULL,			// hWndParent,父窗口句柄			
	NULL,			// hMenu,菜单句柄		
	hInstance,		// hlnstance,程序实例句柄		
	NULL);			// lpParam,用户数据	

函数调用成功将返回窗口句柄,失败返回 NULL。第四个参数dwStyle 的值是ws_OVERLAPPEDWINDOW,即重叠式窗口(Overlapped Window)。由它指定的窗口有标题栏,系统菜单,可以改变大小的边框,最大化、最小化和关闭按钮。这是一个标准窗口的样式。

下面列出了一些常见风格的定义,它们是以ws (Windows Style 的缩写)为前缀的预定义的值:

WS_BORDER       创建一个单边框的窗口
WS_CAPTION      创建一个有标题框的窗口(包括 WS_BODER 风格)
WS_CHIlD        创建一个子窗口。这个风格不能与 WS_POPVP 风格合用
WS_DISABLED     创建一个初始状态为禁止的子窗口。一个禁止状态的窗日不能接受来自用户的输入信息
WS_DLGFRAME     创建一个带对话框边框风格的窗口。这种风格的窗口不能带标题条
WS_HSCROLL      创建一个有水平滚动条的窗口
WS_VSCROLL      创建一个有垂直滚动条的窗口
WS_ICONIC       创建一个初始状态为最小化状态的窗口。与 WS_MINIMIZE 风格相同
WS_MAXIMIZE     创建一个具有最大化按钮的窗口。
                该风格不能和 WS_EX_CONTEXTHELP 风格同时出现,同时必须指定 WS_SYSMENU 风格
WS_OVERLAPPED   产生一个层叠的窗口。一个层叠的窗口有一个标题条和一个边框。
                与 WS_TILED 风格相同
WS_OVERLAPPEDWINDOW 创建一个具有 WS_OVERLAPPED,WS_CAPTION, 
                    WS_SYSMENU ,WS_THICKFRAME,WS_MINIMIZEBOX,
                    WS_MAXMIZEBOX 风格的层叠窗口
WS_POPUP        创建一个弹出式窗口。该风格不能与 WS_CHLD 风格同时使用
WS_POPUPWINDOW  创建一个具有 WS_BORDER,WS_POPUP,WS_SYSMENU 风格的
                窗口,WS_CAPTION 和 WS_POPUPWINDOW 必须同时设定才能
                使窗口某单可见
WS_SIZEBOX      创建一个可调边框的窗口,与 WS_THICKFRAME 风格相同 
WS_SYSMENU      创建一个在标题条上带有窗口菜单的窗口,必须同时设定 WS_CAPTION 风格
WS_THICKFRAME   创建一个具有可调边框的窗口,与 WS_SIZEBOX 风格相同
WS_VISIBLE      创建一个初始状态为可见的窗口

在桌面显示窗口

::ShowWindow(hwnd, nCmdShow);

ShowWindow函数用于设置指定窗口的显示状态,上面代码中的nCmdShow是系统传递给WinMain函数的参数。函数的第二个参数可以有多个不同的取值(详细情况请以函数名为索引查看SDK文档),例如下面的代码将隐藏句柄hWnd 指定的窗口。

::ShowWindow(hWnd, SW_HIDE); //nCmdShow 参数的取值可以是SW_SHOW、SW_HIDE、SW_MINIMIZE 等

更新窗口客户区

::UpdateWindow(hwnd);

如果指定窗口的更新区域不为空的话,UpdateWindow 函数通过向这个窗口发送一个WM_PAINT消息更新它的客户区。当窗口显示在屏幕上时,窗口的客户区被在WNDCLASSEX中指定的刷子擦去了,调用UpdateWindow函数将促使客户区重画,以显示其内容。

进入无限的消息循环
程序下面将进入无限的循环中。在调用UpdateWindow 函数之后,整个窗口已经显示在桌面上,程序必须准备从用户接收键盘和鼠标输入了。Windows为每个线程维护了一个消息队列,每当有一个输入发生,Windows就把用户的输入翻译成消息放在消息队列中。利用GetMessage函数可以从调用线程的消息队列中取出一个消息来填充MSG结构。

::GetMessage(&msg, NULL, 0, 0);

如果消息队列中没有消息(即没有用户输入),这个函数会一直等待下去,直到有消息进入到消息队列为止。msg是一个MSG结构类型的变量,这个结构定义了消息的所有属性。

typedef struct tagMSG {
    HWND        hwnd; //消息要发向的窗口句柄
    UINT        message; //消息标识符,以WM_ 开头的预定义值(意为Window Message)
    WPARAM      wParam; //消息的参数之一
    LPARAM      lParam; //消息的参数之二
    DWORD       time; //消息放入消息队列的时间
    POINT       pt; //这是一个POINT 数据结构,表示消息放入消息队列时的鼠标位置
} MSG, *PMSG;

GetMessage函数从消息队列中取得的消息如果不是 WM_QUIT,则返回非零值。一个WM_QUIT消息会促使GetMessage函数返回0,从而结束消息循环。

::TranslateMessage(&msg);

此调用把键盘输入翻译成为可传递的消息。

::DispatchMessage(&msg);

DispatchMessage函数分发一个消息到对应窗口的窗口函数。在上面的例子中,窗口函数是MainWndProc。MainWndProc处理消息后把控制权交给Windows,此时 DispatchMessage函数仍然在继续工作,当它返回时,消息循环从调用GetMessage函数开始进入下一轮循环。

处理消息的代码

到现为止所描述的不过是一个开头。窗口类已经注册了,窗口已经创建了,也显示在屏幕上了,整个程序也进入了消息循环开始从消息队列中取消息了。

但真正的工作是在 MainWndProc函数中完成的。这个消息处理函数决定了在窗口客户区显示的内容,决定了窗口是如何响应用户输入的。

消息处理函数接收到的所有消息都被标识为一个数字,这就是MainWndProc 的第一个参数uMsg。这些数字在WINUSER.H文件中都是以WM_为前缀定义的。

通常Windows程序设计者用一个switch和case结构来决定消息处理函数收到了什么消息,以及如何处理这个消息。所有消息处理函数不处理的消息都必须传给一个名为DefWindowProc的函数让 Windows做默认处理,从DefWindowProc函数返回的值也必须从消息处理函数返回。

在上面的程序中,MainWndProc仅选择了WM_PAINT和 WM_DESTROY两个消息进行处理。一般情况下,消息处理函数结构化为。

switch (message)
{
case WM_PAINT: // 窗口客户区需要重画
    【处理WM_PAINT消息】
	return 0;
case WM_DESTROY: // 正在销毁窗口
	【处理WM_PAINT消息】
	return 0;
}
// 将我们不处理的消息交给系统做默认处理
return ::DefWindowProc(hwnd, message, wParam, lParam);

必须要把所有不处理的消息交给DefWindowProc函数处理,也要把它的返回值返回给Windows,否则Windows 就失去了与应用程序通信的途径,也就不能再控制窗口的行为了,这是不合法的。

WM_PAINT消息通知应用程序窗口客户区有一块或者全部变成无效,必须刷新。这意味着窗口客户区的内容必须被重画。

客户区怎么会变成无效呢?当窗口第一次被创建时,整个客户区是无效的,因为还没有向上面画任何东西。第一个 WM_PAINT消息被发送到窗口处理函数时,程序有机会向客户区画一些内容。

当改变窗口大小的时候,客户区变成无效。在填写WNDCLASSEX结构的style成员时,将它设置为CS_HREDRAW和CS_VREDRAW,这就直接促使在改变窗口大小时Windows将整个窗口变为无效。

最小化窗口,再将它恢复到以前的大小时,Windows没有保存整个客户区的内容。在图形操作环境下,需要保留的数据太多了。同样地,Windows使这个窗口无效,窗口处理函数就会收到一个WM_PAINT消息,自己负责恢复客户区的内容。

当围着屏幕移动窗口,直到窗口被覆盖时,Windows并没有保存被覆盖的区域。这个区域再次显示时,它就被标识为无效。窗口处理函数会收到一个WM_PAINT消息来重画窗口的内容。

处理WM_PAINT消息时总是以调用BeginPaint函数开始。

hdc = ::BeginPaint(hwnd, &ps); 
//以一个EndPaint 函数调用结束 
::EndPaint (hwnd, &ps) ;

这两个函数中,第一个参数是窗口句柄,第二个参数是指向PAINTSTRUCT结构的指针,这个结构包含了一些可以在重画客户区时使用的信息(详见4.3节)。现在仅仅在 BeginPaint和 EndPaint 两个函数中使用它。

在调用BeginPaint函数的时候,如果客户区的背景还没有被擦掉的话,Windows将擦除它,擦除背景时使用的刷子由WNDCLASSEX结构的 hbrBackground成员指定。对 BeginPaint 函数的调用将使整个客户区有效,然后返回设备环境句柄。在窗口的客户区显示图形和文字时,需要使用这个设备环境句柄(详细介绍请参考4.4.1小节)。使用BeginPaint函数返回的设备环境句柄,不能在客户区外进行绘画。EndPaint 函数负责释放设备环境句柄,使它变得不再能用。

如果窗口函数不处理WM_PAINT消息,必须把它们传给DefWindowProc函数进行默认处理。DefWindowProc函数会调用BeginPaint和 EndPaint 函数使客户区有效。

调用BeginPaint函数后,程序调用了TextOut函数。

::TextOut(hdc, 10, 10, szText, strlen(szText));

此函数用于在 hdc 指定的设备(这里是显示器)上显示文字。(10,10)为坐标位置,szText为要显示的文字,strlen(szText)语句计算出了文本占用的字节数。

每当客户区变成无效,消息处理函数WndProc都会收到一个新的 WM_PAINT消息。响应此消息的代码取得设备环境句柄后,再一次将szText的内容显示在指定位置。

WM_DESTROY是窗口函数必须处理的消息。当用户关闭窗口,而且此窗口已经响应了用户的请求正在关闭时,消息处理函数就会收到一个WM_DESTROY消息。当接受到这个消息的时候,说明窗口正在销毁。MainWndProc函数调用PostQuitMessage 函数来响应此消息。

::PostQuitMessage(0);

这个函数向程序的消息队列中插入一个WM_QUIT消息。在以前已经提到,GetMessage函数如果从消息队列中取得的消息是WM_QUIT,它将返回0,从而促使WinMain函数离开消息循环,然后应用程序执行以下代码。

//当 GetMessage 返回0 时程序结束
return msg.wParam;

此时, msg.wParam的值是传给PostQuitMessage函数的参数的值。return语句将使WinMain函数返回,程序运行结束。

如果接收到WM_DESTROY消息后没有向消息队列投递WM_QUIT消息,那么,窗口虽然销毁了,但GetMessage函数不会返回零,所以消息循环还在继续,程序还没有结束。要想真正关闭它,只能使用任务管理器了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值