《Windows程序设计》读书笔三 窗口与消息

第三章 窗口于消息

前面的例子都使用MessageBox来创建窗口 ,单他所创建的窗口灵活性有限。

3.1 窗口的创建

只要调用CreateWindow函数即可

3.1.1 系统结构概述

一个应用程序窗口可能包含,标题栏,菜单栏,工具栏,滚动条。另外还有一种类型的窗口是对话框,这种窗口可以不带标题栏

还可能包含,按钮,单选按钮,复选框,列表框,滚动条,文本框等。每一个这些对象都被称为 子窗口,或者 控件窗口


当用户改变窗口尺寸时,Windows便向应用程序发送一条携带新窗口尺寸相关的信息,接着应用程序对自身内容进行调整以反映出窗口尺寸的变化。


“Windows向应用程序发送了一条消息”,windows调用了该程序内部的一个函数---这个函数是由你所写,而且是整个程序的核心。此函数的参数描述了由windows

所发送的由你的程序所接受的特定消息。这个函数被称为“窗口过程”。 windows会调用应用程序的这个函数,这种思路正式windows体系结构的基础。


应用程序所创建的每一个窗口都有一个与之对应的窗口过程。可以是应用程序中的某一个函数也可以是DLL库中的函数。窗口过程依据这些消息做相应的处理,然后将控制权返回给windows


更准确的说,窗口总是依赖窗口类来创建。窗口类标示了用于处理传递给窗口的消息的窗口过程。多个窗口可以共享一个窗口类,使用相同的窗口过程。


当windows程序创建以后,windows首先为该程序创建一个消息队列。该消息队列中存放着应用程序可能创建的所有窗口的消息。windows应用程序中通常都包含一小段消息循环的代码,该代码用于从消息队列中检索信息,并将其分发给窗口过程。


窗口, 窗口类,窗口过程,消息队列,消息循环以及窗口消息整合到实际应用中的一个例子

3.1.2 

需要在工程中加载 Winmm.lib  使用PlaySound API函数, 在VS2015中禁用

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //window procedure.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static		TCHAR szAppName[] = TEXT("HelloWin");
	HWND		hwnd;
	MSG			msg;
	WNDCLASS	wndClass;		//The window Class

	wndClass.style			= CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc	= WndProc;// assign the window procedure to windows class.
	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;

	//Register the Window Class to the Windows System. 
	if (!RegisterClass(&wndClass)) 
	{
		MessageBox(NULL, TEXT("This program require Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	//This function will generate an WM_CREATE message.
	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);	//This function will generate a WM_PAINT message.

	/* The message loop for this program.
	if received the WM_QUIT message, the function will return 0.*/
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;

}

//define the Window Procedure WndProc
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC			hdc;
	PAINTSTRUCT	ps;
	RECT		rect;

	switch (message) //get the 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 7 x64 Ultimate Sercice Pack 1!"), -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);
}


3.1.3 通盘考虑

实际上任何windows程序的结构都与Hellowin.cpp类似。不需要记住这个框架的所有细节

该程序包括一个WinMain函数(程序入口) WndProc函数(窗口过程Wind Prock)在hellowin.cpp中并未有任何调用WinProc的代码,但是在WinMain中有一个对WinProc的引用。


Windows函数调用

LoadIcon 加载图标,以供程序使用

LoadCursor 加载光标,以供程序使用

GetStockObject 获取一个图形对象

RegisterClass 为应用程序的窗口注册一个窗口类

MessageBox 显示消息框

CreateWindow 基于窗口类创建一个窗口

ShowWindow 在屏幕中显示窗口

UpdateWindow 指示窗口对其自身进行重绘

GetMessage 从消息队列获取消息

TranslateMessage 翻译一些键盘消息

DispatchMessage 将消息发送给窗口过程(WndProc)

PlaySound  播放声音文件

BeginPaint  表明窗口绘制开始

GetClientRect 获取窗口客户区尺寸

DrawText 显示一个文本字符串

EndPaint 结束窗口绘制

PostQuitMessage 将退出消息插入消息队列

DefWindowProc 执行默认的消息处理

一些常用Wnidow定义的下标


新数据类型

很多事为了兼容不同windows版本定义的一些typedef 或者#define

UINT unsigned int

WPARAM UINT_PTR

 LPARAM LONG_PTR

LRESULT LONG


HELLOWIN使用了4种数据结构

MSG 消息结构

WNDCLASS  窗口类结构

PAINTSTRUCT 绘制结构

RECT 矩形结构

句柄

HINSTANCE 实力句柄-程序本身

HWND 窗口句柄

HDC 设备环境句柄

匈牙利标记法

为了纪念微软程序员Charles Simonyi  变量名以表明该变量数据类型的小写字母开始再加上相应的名字《Windows程序设计》读书笔三 窗口与消息_第1张图片

3.1.4 窗口类注册

窗口总是基于窗口类创建,窗口类决定了处理消息的窗口过程(WndProc)

ASCII 版本


typedef struct tagWNDCLASSA {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
} WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;


使用WNDCLASS 创建一个wndClass的类并初始化个成员,然后调用RegisterClass函数注册

最重要的是 lpfnWndProc (窗口过程的地址)和 lpszClassName(窗口类的名称,通常与主窗口相同)

《Windows程序设计》读书笔三 窗口与消息_第2张图片

wndClass.style = CS_HREDRAW | CS_VREDRAW;

指定了无论何时窗口的水平尺寸(CS_HREDRAW)或垂直尺寸(CS_VREDRAW)被改变,所有基于该窗口类的窗口都要被重画


wndClass.lpfnWndProc  = WndProc;

将窗口类的过程设定为WndProc函数


wndClass.cbClsExtra= 0;
wndClass.cbWndExtra= 0;

在类结构和Windows内部维护的窗口结构中预留一些额外的空间,该案例没使用到这些功能因此设为0


wndClass.hInstance = hInstance;

应用程序的实例句柄

wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

为基于该窗口类的窗口设定一个图标

wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);

为基于该窗口类的窗口设定一个鼠标指针

wndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);

为窗口类的客户区设定背景颜色, hbr表示一个画刷的句柄,该例子设定为白色。

wndClass.lpszMenuName = NULL;

不带菜单栏

wndClass.lpszClassName = szAppName;

窗口类的名称


GetLastError 可以获取RegisterClass返回错误的原因,各种api文档中有说明是否可以使用GetLastError来获取错误信息。

一些程序员喜欢检查每个函数调用的返回值判断是否有错误发生,这是有意义的。

hPrevInstance 表明有没有旧实例在内存中运行。


3.1.5 窗口创建

窗口类只定义了窗口的一般特征,使用CreateWindow来创建窗口时候

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



szAppName 刚才注册的窗口类,通过窗口类名与刚才注册的窗口类建立连接

当新建窗口为顶级窗口,父句柄参数就应该设置为NULL

CreateWindow返回创建窗口的句柄 HWND(handle to Window)

3.1.6 窗口的显示

当CreateWindow调用返回时,窗口已经在Windows内部创建(分配内存),但是还需要调用ShowWindow(hwnd, iCmdShow) 来显示窗口

iCmdShow   SW_SHOWNORMAL 正常显示  SW_SHOWMAXIMIZED 最大化  SW_SHOWMINNOACTIVE 最小化到任务栏

UpdateWindow(hwnd); 使窗口的客户区重绘 像WndProc函数发送一条WM_PAINT消息


3.1.7消息循环

在UpdateWindow之后新建窗口在屏幕中完全可见了,此时程序必须能够接受各种来自用户的键盘输入和鼠标输入等操作。Windows为当前在其运行的每一个Windows程序都维护了一个消息队列,当输入事件发生后,windows会自动将这些事件转换为“消息”。


while (GetMessage(&msg, NULL, 0, 0))
{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}


消息循环

typedef struct tagMSG {
    HWND        hwnd; //消息所指向窗口的句柄
    UINT        message; //消息标识符
    WPARAM      wParam; //32bit 消息参数,取决于具体消息
    LPARAM      lParam; //另一个32bit消息参数,同样取决于具体消息
    DWORD       time; //消息进入消息队列的时间
    POINT       pt; //消息进入消息队列时鼠标指针的位置坐标
#ifdef _MAC
    DWORD       lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;


如果消息队列中捕获的消息非WM_QUIT ,  GetMessage返回值非0,否则返回0

TranslateMessage(&msg) 将消息还给windows进行某些键盘消息的转换,如果应用程序不支持键盘输入可以去掉此函数。

DispatchMessage(&msg)将消息再次返回给Windows,接下来Windows会把这条消息发给合适的窗口来处理。这里是WndProc


3.1.8 窗口过程

注册窗口类,创建窗口,在屏幕中显示窗口,程序进入消息循环,从消息队列中检索消息。

真正有意义的事情发生在窗口过程中。

一个windows程序可以包含多个窗口过程,单一个窗口过程总是与一个通过RegisterClass注册的特定窗口类相关联。

CreateWindow创建基于特定窗口类的窗口,一个窗口类可以创建多个窗口


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

hwnd 接收消息的窗口句柄


message 消息类型

wParam, lParam 扩展消息

如果需要调用窗口函数可以使用SendMessage函数

3.1.9消息处理

使用switch-case结构,处理完返回0, 窗口过程不处理的消息需要传递给DefWindowProc处理并返回值返回给当前窗口过程

用DefWindowProc处理消息非常重要,否则应用程序的其他正常功能都无法进行


3.1.10 声音文件播放

调用CreateWindow函数以后,windows完成必要的操作以后,Windows对WndProc进行调用,将其第一参数设为当前窗口句柄,第二参数设为WM_CREATE,

接着WndProc对WM_CREATE消息进行处理,并将控制权返回给Windows。然后Windows从CreateWindow调用返回到HELLOWin中

通常WM_CREATE中对窗口进行一次性初始化,该例子播放wav文件调用PlaySound函数


3.1.11 WM_PAINT消息

当客户区域部分或全部无效必须更新时,应用程序将得到此通知,也就意味着窗口需要被重绘。

第一条WM_PAINT消息是WinMain中的UpdateWindow发送的。

改变窗口尺寸,或者最小化回复以后都会重绘客户区域。或者窗口覆盖

对WM_PAINT消息处理几乎总是以调用BegnPaint函数开始

hdc = BeginPaint(hwnd, &ps);


GetClientRect(hwnd, &rect);//获得客户区域的RECT


DrawText(hdc, TEXT("Hello, Windows 7 x64 Ultimate Sercice Pack 1!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);


设备环境指物理输出设备以及其设备驱动程序

EndPaint 释放设备环境句柄,使其无效。

DrawText 绘制文本,尽管DrawText看起来类似GDI函数,但是他属于相当高层的模块

 最后一个参数设置了绘制文本的一些排版参数


3.1.12 WM_DESTROY消息

表明Windows正处在依照用户命令的销毁窗口的过程中

标准响应方法是 调用 PostQuitMessage(0); 会将一个WM_QUIT插入消息队列,然后GetMessage 接收到WM_QUIT消息返回0 ,退出消息循环。然后程序返回

return msg.wParam;


3.2 windows编程中的若干难点

虽然进行了注册窗口类,创建窗口,显示窗口,更新窗口,消息循环。但是真正有意义的处理都在窗口过程中

3.2.1 究竟谁调用谁

windows程序中,操作系统可以调用用户的函数WndProc

WndProc在类似的场景下被调用,新建窗口时,窗口被销毁时,窗口尺寸发送变化或者被移动最小化等,用户用鼠标在窗口中执行单击或者双击操作时。用键盘输入文字时,用户从菜单选取某个选项时,客户区需要重绘时等等。

所有对WndProc的调用都是通过消息形式出现。然后WndProc处理相应的消息或者交给DefWindowsProc处理

3.2.1 队列消息和非队列消息

队列消息是指由Windows放入程序的消息队列中的消息然后被程序的消息循环检索并调用窗口函数 Post 线程异步

非队列消息是由对窗口函数直接调用产生的。Send 线程同步

无论是否是队列窗口过程都会处理,窗口过程实际是窗口的“消息中心”

队列消息主要由用户输入产生,主要是按键,鼠标等操作,还有定时器消息,重绘消息,退出消息等。

非队列消息由特定的windows函数引起如CreateWindow(WM_CREATE), ShowWindow(WM_SIZE, WM_SHOWWINDOW), UpdateWindow(WM_PAINT)

每个线程的消息队列仅为那些窗口过程在该线程内执行的窗口进行消息处理。消息队列和窗口过程不是并发的,等窗口过程返回以后DispatchMessage函数才会返回。

窗口过程可以调用为其发送其他消息的函数。窗口过程是可重入的


你可能感兴趣的:(Win32,windows,C语言)