窗口的概念很容易理解,就是我们使用软件时看到的界面。Windows 的核心就是窗口,它是Windows一统PC操作系统市场的杀手锏,如下图所示:
我们使用的软件都有自己的窗口,比如 QQ、计算器、记事本等。这些窗口可以包含输入框、下拉菜单、单选按钮、多选按钮、文本区域等各种各样的控件(Controls),有的甚至还有动画!
窗口、控件、图像、音频视频等都称为资源(Resource),在程序中都可以使用、创建、添加、修改等。
在Windows编程中,不同窗口、控件、图像等都对应一个唯一的数字(初学者可以理解为 ID),称为句柄(Handle)。通过句柄,程序可以获取对应资源的各种信息,也可以使用、修改、删除该资源。
你可以将句柄理解为学号,你不需要记住学生的姓名、住址、成绩等各种信息,当你需要了解这名学生时,只要去教务处,将学号(句柄)告诉那里的工作人员(Windows),他就能够帮你找到这个学生。
句柄屏蔽了很多细节,程序员不需要了解背后的机制。例如用 CreateFile() 函数创建文件后会返回一个文件句柄,然后通过这个句柄就可以读写、删除该文件,而不需要了解Windows是如何将句柄与文件关联起来的,也不需要了解句柄到底保存了哪些信息,Windows 是闭源的,这些背后的细节只有微软知道。
在一般的编程中,我们都是通过 API 函数来调用系统功能,让操作系统来帮我们完成很多工作,例如调用 CreateFile() 函数,操作系统会帮我们创建一个文件,而不需要我们参与任何工作,非常方便。
反过来,操作系统也会“偷懒”,会调用我们程序中的函数,让我们自己处理某些事情。例如用户敲击键盘,操作系统会首先收到通知,但它并不会处理,而是调用程序中的函数,告诉程序用户敲击了键盘,你自己处理好了;如果程序不处理,操作系统才会进行默认的操作。
当然,这不能理解为操作系统“偷懒”,而是给我们一个机会,让我们自行处理某些事情,从而使程序更加灵活和健壮,也让程序员有了更多发挥的空间。
用户敲击键盘、点击鼠标、拖动窗口、选择菜单、输入文字等所有的操作都称为事件(Event)。这与我们平时理解的“事件”是类似的,都表示发生了某些情况,好的或者坏的。
当有事件发生时,Windows 会生成一条消息(Message),告诉程序发生了什么事情。这与我们平时理解的“消息”是类似,都表示一种传递信息的载体。
那么,Windows 是如何通过消息将发生的事件通知给应用程序的呢?
每当事件发生时,Windows 会生成一条消息,并放到一个由系统维护的队列中。然后,程序会自己从这个队列中获取消息并分析,调用事件处理函数(处理事件的代码也就在这个函数中),对用户的操作进行响应。
队列是一种先进先出的数据结构,不明白的请自行Google或百度。
注意:Windows 向队列中分派消息和应用程序从队列中获取消息并不是同步的,Windows 不管队列中有没有消息,不管应用程序有没有处理完毕,只要有事件发生,就会将消息丢进队列,什么时候处理完毕是应用程序的事。
可见,消息是连接 Windows 和应用程序的纽带,Windows 通过消息告诉应用程序发生了什么,应用程序通过消息知道该做什么。
消息其实是一个结构体,名字为 MSG,定义为:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
Windows 向队列中投递消息,其实就是将一个类型为 MSG 的结构体变量丢进队列。
MSG 结构体中各成员变量的含义如下:
1) hwnd表示消息所属的窗口。用户一般是在程序的窗口下进行操作,所以一个消息一般都是与某个窗口相关联的。例如在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。
2) message表示消息类型,是一个数值。在Windows中,消息是由一个数值来表示的,不同类型的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX 对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。
3) 第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。
4) 最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。