从一个函数入手, 理解窗口过程
m_list.InsertColumn(0, _T(“工号”), 0, 80);
MFC中有很多诸如列表控件类CListCtrl这样的类来管理不同控件, CListCtrl类中又有很多诸如InsertColumn的函数来实现具体的管理行为
自然MFC的这些类中的这些函数都指望Win32中能有对应的API可以调用, 显然不现实, Win32承受不起
事实上它们都是统一调用的Win32中的SendMessage
比如InsertColumn内部实现就是:
::SendMessage(m_hWnd, LVM_INSERTCOLUMN, nCol, (LPARAM)pColumn); }
可以看到, 它调用了WINAPI的SendMessage, 交给了SendMessge一个LVM_INSERTCOLUMN消息,以及一个列信息结构体, 让它帮忙完成插入操作
SendMessage是什么?
SendMessage是Win32的一个API, 非常重要, 宏观上它的功能是向窗口发送消息
SendMessage函数的格式:
LRESULT SendMessage(
[in] HWND hWnd, //HWND类型。接收消息的窗口的句柄。
[in] UINT Msg, //UINT类型。要发送的消息。系统提供的消息列表,参见系统定义的消息。
[in] WPARAM wParam, //WPARAM类型。附加的特定于消息的信息。
[in] LPARAM lParam //LPARAM类型。附加的特定于消息的信息。
);
当以上在调用InsertColumn函数时, hWnd是列表视图控件的句柄,即m_list对象附着的句柄; Msg是LVM_INSERTCOLUMN消息; wParam是新列的索引; lParam是一个指向LVCOLUMN结构体的指针,这个结构包含了新列的各项必要属性. 接下来就把任务交给SendMessage了
SendMessage把消息和数据发给了谁?
SendMessage会立即调用目标窗口的窗口过程,并等待窗口过程处理完消息后才返回(同步阻塞函数)
什么是窗口类?
Windows中每种窗口都对应着一种窗口类, 对于列表控件来说, 列表视图窗口类是SysListView32, 当创建一个CListCtrl对象时, MFC会在后台调用WINAPI来创建一个SysListView32窗口(即SysListView32类对象)
既然CListCtrl能代表列表窗口,为啥还需要SysListView32, 用同一个名字不好吗?
这是属于先后关系搞反了, 虽然CListCtrl和SysListView32都代表了列表视图控件, 但是要注意:
-
SysLiWistView32是在Windows API中使用的窗口类名, 而CListCtrl是在MFC中使用的.
-
Windows API是一个低级的编程接口,它提供了直接访问操作系统服务的能力, 而MFC是一个基于Windows API的高级编程框架,它提供了一组C++类,这些类封装了Windows API的功能,使得你可以更容易地创建Windows应用程序, 在MFC中,你不需要直接使用窗口类名,使用MFC封装后的控件类就可以, 也不需要直接处理窗口消息。
-
也就是说MFC的CListCtrl是对SysListView32类更高层次的封装, CListCtrl内部调用的其实还是原生的WINAPI的SysListView32中的东西, 封装后可以方便用户使用, Qt中也有类似的QListView封装了SysListView32, 也就是说没了这些不同编程环境下的高级封装类可以, 但是没了SysListView32不行
-
SysListView32 提供了一种基于消息的方式来操作列表控件, 这是根本
-
CListCtrl提供了一种面向对象的方式来操作列表控件, 这只是一种对SysListView32 的一种封装而已
什么是窗口过程?
-
窗口过程的本质是一个函数,是一个不依赖于具体对象, 被操作系统调用的回调函数, 英文是Window Procedure, 一般不翻译为窗口程序, 而翻译为窗口过程
-
每个窗口都关联着对应的窗口过程, 同一类的窗口关联的是同一个窗口过程
-
窗口过程负责接收并处理该窗口的所有消息, 这个函数往往会比较丰富, 因为窗口过程通常使用一个switch语句来根据消息的类型调用不同的代码块。每个代码块处理一种特定类型的消息, 一个窗口往往会有比较多种消息. 实际上,窗口过程的代码通常会被组织得很好,每种消息的处理代码都封装在一个单独的函数中, 所以从代码上看窗口过程函数也不会太大, 只是会调用比较多的对应于不同消息的消息处理函数
窗口过程函数在哪里?
窗口过程函数是在窗口类中定义的, 同一窗口类创建的所有窗口共同使用一个窗口过程来响应消息, 对于CListCtrl控件,它的窗口过程是在系统提供的列表视图窗口类SysListView32中定义的.既然属于同一类的所有窗口都使用相同的默认窗口过程, 那么窗口过程就需要是静态的或者全局的, 因为非静态成员函数需要一个对象上下文,而窗口过程作为一个回调函数,是由操作系统调用的,操作系统并不知道这个对象上下文(回调函数一般都是跟对象无关)
SendMessage怎么知道要将消息等信息发送给哪个窗口过程?
根据第一个参数即窗口句柄得知的. 一个窗口(控件当然也是窗口)通常由一个窗口句柄(HWND)来表示。这个句柄是一个指向窗口内存结构的指针, 这个结构体包含了窗口的所有信息, 不仅有窗口的位置、大小、样式等, 还记录了窗口过程的地址, 窗口正是通过这样与对应的窗口过程关联的
具体到InsertColumn怎么执行?
当m_list.InsertColumn(0, _T(“工号”), 0, 80);时,hWnd是列表视图控件的句柄(即对象m_list中附着的列表控件句柄),Msg是LVM_INSERTCOLUMN消息,wParam是新列的索引(即第0列),lParam是一个指向LVCOLUMN结构体的指针,这个结构包含了创建新列的需要的属性(即使你调用InsertColumn的时候传入的列信息参数是好几个, InsertColumn内部也会给你整合成一个信息结构体交给SendMessage跟消息一起发过去, 这是InsertColumn内部代码逻辑,可以断点进去看)
首先, InsertColumn函数内部将列次, 列标题等要插入的新列相关信息整合成一个列信息结构体LVCOLUMN 以及 消息LVM_INSERTCOLUMN 以及 窗口句柄(列表控件句柄)等必要信息一起交给SendMessage
然后SendMessage从窗口句柄中提取出窗口过程的地址,操作系统根据地址调用窗口过程(回调函数), 窗口过程根据消息类型调用不同的消息处理函数, 窗口过程这个回调函数的代码是不对外开放的
SendMessage和PostMessage的区别
SendMessage和PostMessage都是Win32 API中用于向窗口发送消息的函数,但它们的工作方式有所不同:
- SendMessage函数会将指定的消息发送到窗口的窗口过程,并等待窗口过程处理完消息后才返回, 当你调用SendMessage函数时,你的程序会被阻塞,直到消息被处理完毕. 同步阻塞.
- PostMessage函数会将指定的消息发送到窗口的消息队列,并立即返回, 当你调用PostMessage函数时,你的程序不会被阻塞,你也不知道消息何时会被处理. 异步非阻塞.
更高层次的总结和概括
- SendMessage函数是Windows消息机制的核心
- Windows是一个消息驱动系统, 应用程序之间, 应用程序与Windows系统之间的通信主要是通过发送和接收消息来完成的, 每当用户与程序交互(例如点击鼠标, 按下键盘等), 或者系统发生某些事件(例如窗口创建, 窗口关闭等)时, 都会产生相应的消息
- 人造景观