MFC工具条和状态栏
2008-01-11 19:55
106 查看
http://51dev.com/program/cpp/20070818/7548.html
Windows控制窗口
Windows (Windows95或者以上版本) 提供了系列通用控制窗口,其中包括工具条(ToolBar)、状态栏(StatusBar)、工具条提示窗口(ToolTip)。
Windows在一个DLL加载时注册个控制窗口
“窗口类”。例如,工具条
“窗口类”是“ToolbarWindow32”,状态栏
“窗口类”是“msctls_statusbar32”,工具条提示窗口
“窗口类”是“tooltips_class32”。为了保证该DLL被加载,使用控制“窗口类”前,应该首先调用函数InitCommonControl。MFC在窗口注册函数AfxDeferRegisterClass中实现了这一点。见2.2.1节MFC下窗口
注册。
创建通用控制窗口,可以使用专门
创建函数,如创建工具条
函数::CreateToolBarEx,创建状态栏
函数::CreateStatusBarEx。也可以调用窗口创建函数::CreateWindowEx,但是需要指定预定义
“窗口类”,必要
话还要其他步骤,如使用“ToolbarWindow32”“窗口类”创建工具栏后,还需要在工具栏中添加或者插入按钮。
一般,通用控制可以指定控制窗口风格(Style)。例如,具备风格CCS_TOP,表示该控制窗口放到父窗口客户区
顶部,具备CCS_BOTTOM,表示该控制窗口在客户区
底部。具体
控制窗口类可以有特别
适合于自己
风格,例如,TTS_ALWAYSTIP表示只要光标落在工具栏
按钮上,ToolTip窗口不论激活与否都会显示出来。
每一控制窗口类都有自己
窗口过程来处理自己
窗口消息,实现特定
功能。控制窗口类
窗口过程由Windows提供。
工具条
工具条
窗口过程处理了必要
消息,提供了标准工具条
功能,例如,工具条对客户化特征提供内在
支持,用户可以通过一个客户化对话框来添加、修改、删除或者重新安排工具条按钮。这些特征是否可以被用户所用或者用到什么地步是可以由程序控制
。
工具条
窗口过程将自动设置工具条
尺寸大小和位置,如果指定了控制窗口风格CCS_TOP或者CCS_BOTTOM,则窗口过程把工具条放到父窗口客户区
顶部或者底部。窗口过程任何时候只要收到WM_SIZE或者TB_AUTOSIZE消息就自动地调整工具条
大小和位置。
工具条
按钮被选中后,会产生一个命令消息,它
窗口过程把该消息送给父窗口
窗口过程处理。
工具条中
按钮并不以子窗口
形式出现,而是以字符或者位图按钮
方式显示,每个按钮大小相同,缺省是24*22个像素。每个按钮都有一个索引,索引编号从0开始。每个按钮包括如下属性:
按钮
字符串索引,位图索引,风格,状态,命令ID
按钮可以有两种风格TBSTYLE_BUTTON和TBSTYLE_CHECK,前者像一个标准按钮那样响应用户
按击,后者响应每一次按击,在按下和跳起两种状态之间切换。按钮响应用户
动作,给父窗口发送一个包含了该按钮对应命令ID
命令消息。一般一个按钮
命令ID对应一个菜单项。
工具条维护两个列表,分别用来存放工具条按钮使用
字符串或者位图,列表中
位图或者字符串从0开始编号,编号和按钮
索引相对应。
工具条可以是Dockable(泊位)或者Floatable(漂浮)
。
工具条可以有TBSTYLE_TOOLTIPS风格,如果具有这种风格,则创建和管理一个Tooltip控制,这是一个小
弹出式窗口,用来显示描述按钮
文本,平时该窗口隐藏,当鼠标落到按钮上面并停留约一秒后才弹出,在鼠标附近显示。
由于Tooltip窗口平时是隐藏
,所以不能接收鼠标消息来决定何时显示本窗口。这样,接收鼠标
窗口必须把鼠标消息送给Tooltip窗口,这是通过给Tooptip窗口发送消息TTM_RELAYEVENT来实现
。
状态栏
状态栏类似于工具条,有自己
窗口过程,可以泊位、漂浮。不过,习惯上状态栏都位于屏幕底部。每个状态条分成若干格(Status bar panes),每格从0开始编号,编号作为格
索引。每一个格,如同工具条
按钮一样,并不是一个Windows窗口。
MFC
工具条和状态栏类
MFC使用CToolBarCtrl、CStatusBarCtrl和CToolTipCtrl窗口类分别对工具条、状态栏、Tooltip控制窗口进行了封装。
但是,直接使用这些类还不是很方便。MFC提供了CToolBar、CStatusBar来处理状态栏和工具条,CToolBar、CStatusBar功能更强大,灵活。这两个类都派生于CControlBar。
在MFC下,建议这些控制条子窗口ID介于AFX_IDW_TOOLBARFIRST(0xE800)和AFX_IDW_CONTROLBAR_LAST(0Xe8FF)之间。这256个ID中,前32个又有其特殊性,用于MFC
打印预览中。
CControlBar派生于CWnd类,是控制条窗口类
基类,它派生出CToolBar、CStatusBar、CDockBar、CDialogBar、COleResizeBar类。CControlBar实现了以下功能:
和父窗口(边框窗口)
顶部或者底部或者其他边对齐。
可以包含子条目,这些条目或者是基于HWND
子窗口,或者是基于非HWND
条目。负责分配条目数组。
支持CBRS_TOP(缺省,控制条放在顶部),CBRS_BOTTOM(放在底部),CBRS_NOALIGN(父窗口大小变化时不重新放置控制条)等几种控制风格。
支持派生类
实现。几个派生类有一定
共性,或者其中两个有一定
共性,这样CControlBar实现
函数一部分只适用于某个派生类,一部分适用于两个或者多个派生类,还有一部分适用于所有
派生类。所谓适用,这里指派生类直接继承了CControlBar
实现,或者覆盖了其实现但是建立在扩展其实现
基础上。类似地,CControlBar
成员变量也不是为所有派生类所共同适用
。
CStatusBar和CControlBar一方面建立在CControlBar
基础之上,另一方面以Windows
通用控制状态栏和工具条为基础。它们继承了CControlBar类
特性,但是所封装
窗口句柄是相应
Windows控制窗口
句柄,如同CFormView继承了CSrcollView
视类特性,但是其窗口句柄是无模式对话框窗口句柄一样。
典型地,如果在使用AppWizard生成应用程序时,指定了要求工具条和状态栏
支持,则在主边框窗口
OnCreate函数中包含一段如下
代码,用来创建工具条、状态栏和设置一些特性。
工具条除了Tooltip,Resizeable,Dockable特性外,还可以是Floatable。应用程序可以使用CFrameWnd::SaveBarState保存边框窗口
控制条
有关信息到INI文件或者Windows Register库,使用LoadBarSate从INI文件或者Register库中读取有关信息并恢复各个控制条
设置。
下文,将讨论工具条等
创建、销毁,从中分析CControlBar和派生类
关系,讨论CControlBar如何实现共性,如何支持派生类
特定要求,派生类又如何实现自己
特定需求等。
控制窗口
创建
创建工具条、状态条、对话框工具栏
方法是不同
,所以必须给每个派生类CToolBar、CStatusBar、CDialogBar设计和实现自己
窗口创建函数Create。但是,它们是也是有共性
,共性由CControlBar
PreCreateWindow处理。在窗口创建之后,各个派生类都要进行
处理(共性)由CControlBar
OnCreate完成,特别
处理通过派生类
OnNcCreate完成。
PreCreateWindow
首先,讨论CControlBar 类
PreCreateWindow
实现。
其中,afxData是一个全局变量,MFC用它来记录系统信息,如版本信息等。这里afxData.bWin4表示Windows版本是否高于4.0。
CToolBar
PreCreateWindow函数修改了窗口风格,也修改状态栏、工具栏等
CBRS_风格。CBRS_风格
改变不会影响窗口风格。因为这些CBRS_风格被保存在成员变量m_dwStyle中。
除了上述在程序中用到
影响工具条、状态栏等显示位置
CBRS_风格外,还有和泊位相关
CBRS_风格,CBRS_ALIGN_LEFT、CBRS_ALIGN_RIGHT、CBRS_ALIGN_BOTTOM、CBRS_ALIGN_TOP、CBRS_ALIGN_ANY,分别表示工具条可以在停泊在边框窗口
左边、右边、底部、顶部或者所有这些位置;和漂浮相关
CBRS_风格CBRS_FLOAT_MULTI,表示多个工具条可以漂浮在一个微型边框窗口中;和Tooltips相关
CBRS_风格CBRS_TOOLTIPS和CBRS_FLYBY。
派生类如果没有特别
要求,可以不覆盖PreCreateWindow函数。CStatusBar因为有更具体和特殊
风格要求,所以它覆盖了PreCreateWindow。CStatusBar
覆盖实现调用了CControlBar
实现。
派生类也可以在覆盖实现中修改PreCreateWindow参数cs,改变窗口风格;修改m_dwStyle,改变CBRS_风格。
控制条
窗口创建
CControlBar派生类实现了自己
窗口创建函数Create,CControlBar
PreCreateWindow被派生类
Create函数直接或者间接地调用。以CToolBar为例讨论窗口创建函数和创建过程。
CToolBar
窗口创建函数Create
Create函数实现如下:
其中:
Create函数
参数1表示工具条
父 窗口。参数2指定窗口风格和CBRS_风格,缺省值为 WS_CHILD | WS_VISIBLE | CBRS_TOP,其中WS_CHILD和WS_VISIBLE是窗口风格,CBRS_TOP是CBRS_风格。参数3指定工具条ID,缺省值为 AFX_IDW_TOOLBAR(0X0E800或者59392)。如果还有多个工具栏要显示,在创建它们时则必须给每个工具栏指明ID。
首先,Create函数把参数2(dwStyle)指定
窗口风格和CBRS_风格分离出来,窗口风格保留在dwStyle中,CBRS_风格保存到成员变量m_dwStyle中。CToolBar::PreCreateWindow将进一步修改这些风格。
接着,Create函数调用了函数AfxDeferRegisterClass。它如果没有注册TOOLBARCLASSNAME表示
“窗口类”,就注册该类;否则,返回TRUE,表示已经注册。TOOLBARCLASSNAME表示
字符串是“ToolbarWindow32”,即“窗口类”名称。
然后,调用CWnd::Create(7个参数)使用“ToolbarWindow32”“窗口类”创建工具栏。
Create在创建窗口
过程中,用MFC
标准窗口过程取代原来
窗口过程,如同CFormView和CDialog窗口创建时窗口过程被取代一样,并发送WM_CREATE和WM_NCCREATE消息。
至于添加向工具栏添加按钮,则由函数LoadToolBar完成。在分析LoadToolBar函数之前,先讨论OnCreate、OnNcCreate等函数。
处理WM_CREATE消息
CControlBar提供了消息处理函数OnCreate来处理WM_CREATE消息。
如果需要支持Tooltips,则OnCreate调用EnableTooltips。
m_pDockSite是CControlBar
和泊位相关
成员变量,这里把它初始化为拥有工具栏
父边框窗口,该边框窗口把控制条加入其控制条列表m_listControlBars中。
在处理WM_CREATE之前,派生类先处理消息WM_NCCREAE。例如,CToolBar覆盖了OnNcCreate函数。
处理WM_NCCREATE消息
CToolBar对WM_NCCREATE消息
处理如下:
CToolBar覆盖CcontrolBar
该函数用来设置工具条
所属窗口和描述工具条按钮结构
大小,这两个动作都是通过给工具条窗口发送消息来实现
。因为这些消息被送给控制窗口类
窗口过程(Windows提供
)来处理,所以直接调用DefWindowProc,省却了消息发送
过程。
在控制窗口创建之后,对于工具条来说,下一步就是向工具栏添加按钮。
向工具栏添加按钮
通过函数LoadToolBar完成向工具栏添加按钮
任务,其实现如下:
LoadToolBar函数
参数指定了资源。ToolBar资源
类型是RT_TOOLBAR,ToolBar位图资源
类型是RT_BITMAP。
在RT_TOOLBAR类型
资源读入内存之后,可以用CToolBarData结构描述。一个这样
结构包括了ToolBar资源
如下信息:
工具条位图
版本,宽度,高度,个数,各个位图对应
命令ID。
然后,LoadToolBar把这些命令ID被复制到数组pItem中;根据位图宽度、高度形成按钮尺寸sizeButton和位图尺寸sizeimage。
接着,调用SetBottons添加按钮到工具栏,把各个按钮和命令ID对应起来;调用SetSizes设置按钮和位图
尺寸大小;调用LoadBitmap添加或者取代工具条
位图列表。这些动作都是调用工具栏“窗口类”
窗口过程完成
。例如,SetButtons
实现:
函数
参数1是一个数组,数组
各个元素就是命令ID;参数2是按钮
个数。首先,SetButtons删除工具条原来
按钮;然后,添加新
按钮,若命令ID数组非空,则把每一个按钮和命令ID对应并分配位图索引,否则设置空按钮并返回FALSE;最后,记录按钮个数。
从SetButtons
实现可以看出,对工具条
所有操作都是通过工具条“窗口类”
窗口过程完成
,SetSizes、LoadBitmap也是如此,这里不作讨论。
状态栏和对话框工具栏
创建
至此,分析了MFC创建工具条窗口
过程。对于状态栏和对话框工具栏有类似
步骤,但也有不同之处。
CStatusBar
Create使用“msctls_statusbar32”“窗口类”创建状态栏,窗口ID为AFX_IDW_STATUS_BAR(0XE801),然后通过成员函数SetIndictors给状态栏分格,类似于给工具条添加按钮
过程,它实际上是通过状态栏“窗口类”
窗口过程完成
。
CDialogBar
Create使用CreateDlg创建对话框工具栏,类似于CFormView
过程。在工具栏窗口创建之后,要添加到父窗口
工具栏列表中,这通过CControlBar::OnCreate完成。这样创建
结果导致窗口过程使用MFC
统一
窗口过程,相应“窗口类”
窗口过程也将在缺省处理中被调用,这一点如同CFormView和CDialog中所描述
。在初始化对话框
时候完成了各个控制按钮
添加。
CStatusBar和CdialogBar都没有处理消息WM_NCCREATE。
关于CStautsBar和CDialogBar创建过程
具体实现,这里不作详细讨论了。
控制条
销毁
描述了控制条
创建,顺便考察其销毁
设计。
工具条、状态栏等这些控制窗口都要使用DestroyWindow来销毁,所有有关操作集中由CControlBar处理。CControlBar覆盖了虚拟函数DestroyWindow、PostNcDestroy和消息处理函数OnDestroy。
当然,各个派生类
虚拟析构函数被实现。如果成员变量m_bAutoDelete为TRUE,则动态创建
MFC窗口将自动销毁。
处理控制条
位置
计算控制条位置
过程和算法
工具条等控制条是作为一个子窗口在父边框窗口内显示
。为了处理控制条
布置(Layout),首先需要计算出控制条
尺寸大小,这个工作被委派给工具条等控制窗口自己来完成。为此,CControlBar提供了两个函数来达到这个目
:CalcFixLayout,CalcDynamicLayout。这两个函数都是虚拟函数。各个派生类都覆盖了这两个或者其中一个函数,用来计算自身
尺寸大小。这些计算比较琐碎,在此不作详细讨论。其次,在父窗口位置或者大小变化时,控制条
大小和位置要作相应
调整。
下面,描述MFC确定或者更新工具条、状态栏等位置
步骤:
(1)边框窗口在必要
时候调用虚拟函数RecalcLayout来重新放置它
控制条和客户窗口,例如在创建窗口时、响应消息WM_SIZE时(见5.3.3.5节)边框窗口
初始化)。
(2)CFrameWnd::RecalcLayout调用CWnd
成员函数RepositionBars完成控制条窗口
重新放置。
(3)CWnd::RepositionBars作如下
处理:
RepositionBars首先给各个控制子窗口发送(Send)MFC内部使用
消息WM_SIZEPARENT,把窗口客户区矩形指针传递给它们,给它们一个机会来确认自己
尺寸。
然后,各个控制子窗口用OnSizeParent响应WM_SIZEPARENT消息;ControlBar实现了消息处理函数OnSizeParent,它调用CalcDynamicLayout等函数确定本窗口
大小,并从客户区矩形中减去自己
尺寸。
在所有
控制子窗口处理了OnSizeParent消息之后,RepositonBars利用返回
信息调用函数CalcWindowRect计算客户区窗口(MDI客户窗口、View等)
大小。
最后,调用::EndDeferWindowPos或者::SetWindowPos放置所有
窗口(控制子窗口和客户窗口)。
在窗口被放置
时候,发送消息WM_WINDOWPOSCHANGING和WM_WINDOWPOSCHANGED。MFC
实现中,控制窗口响应了前一个消息,消息处理函数是OnWindowPosChanging。CControlBar、CToolBar和CStatusBar等实现了消息处理函数OnWindowPosChanging。
上述处理过程所涉及
这些函数中,RecalcLayout是CFrameWnd定义
虚拟函数;RepostionBars是CWnd
成员函数;CalcaWindowRect是CWnd
虚拟函数;OnSizeParent是CControlBar定义
消息处理函数;OnWindowPosChanging是CToolbar、CStatusBar、CDockBar等CControlBar派生类定义
消息处理函数。
下面,对其中两个函数RecalcLayout和RepositionBars作一些分析。
CFrameWnd
虚拟函数RecalcLayout
RecalcLayout
实现如下:
该函数主要
目
是调用RepositionBars函数,它分两种情况来调用RepositionBars函数。一种情况是当前边框窗口为浮动控制条
包容窗口(微型边框窗口)时;另一种情况是当前边框窗口为普通边框窗口时。
CWnd
成员函数RepositionBars
RepositionBars
实现如下:
RepositionBars用来改变客户窗口中控制条
尺寸大小或者位置,其中:
参数1和参数2定义了需要重新放置
子窗口ID
范围,一般是0到0xFFFF。
参数3指定了一个子窗口ID,它拥有客户窗口剩下
空间,一般是AFX_IDW_PANE_FIRST,表示视
窗口ID。
参数4指定了操作类型,缺省是CWnd::ReposDefault,表示执行窗口放置操作,参数5不会用到;若取值CWnd::ReposQuery,则表示尝试进行窗口放置(Layout) ,但最后不执行这个操作,只是把参数5初始化成客户区
尺寸大小;若取值CWnd::ReposExtra,则把参数5
值加到参数2表示
子窗口
客户区域,并执行窗口放置操作。
参数6表示传递给函数
可用窗口客户区
尺寸,如果空则使用窗口客户区尺寸。
如果执行layout操作
话,该函数
核心处理就是:
首先,调用::BeginDeferWindowPos初始化一个Windows内部
多窗口位置结构(Multiple-window - position structure)hDWP;
然后,让各个子窗口逐个调用::DeferWindowPos,更新hDWP。在调用::DeferWindowPos之前,要作一个确定子窗口大小
工作。这些工作通过给各个控制子窗口发送消息WM_SIZEPARENT来完成。
控制子窗口通过函数OnSizeParent响应WM_SIZEPARENT消息,先确定自己
尺寸,然后,如果需要进行窗口布置(WM_SIZEPARENT消息参数lParam包含了一个非空
HDWP结构(lpLayout->hDWP)),则OnSizeParent将调用AfxRepositionWindow函数计算本控制窗口
位置,结果保存到hDWP中。
在所有
控制窗口尺寸确定之后,剩下
留给窗口hWndLeftOver(如果存在
话)。确定了hWndLeftOver
大小之后,调用AfxRepositionWindow函数计算其位置,结果保存到hDWP中。
上面提到
函数AfxRepositionWindow间接调用了::DeferWindowPos。
最后,::EndDeferWindowPos,使用hDWP安排所有子窗口
位置和大小。
至于其他函数,如OnSizeparent、OnWindowPosChanging、CalcWindowRect,这里不作进一步
分析。
工具条、状态栏和边框窗口
接口
应用程序在状态栏中显示信息
MFC内部通过给边框窗口发送消息WM_SETMESSAGESTRING、WM_POPMESSAGESTRING
方式在状态栏中显示信息。这两个消息在afxpriv.h里头定义。
WM_SETMESSAGESTRING消息表示在状态栏中显示和某个ID对应
字符串信息或者指定
字符串信息,消息参数wParam指定了字符串资源ID,消息参数lParam指定了字符串指针,两个消息参数只有一个有用。一般,一个命令ID对应了一个字符串ID,对应
字符串是命令ID
说明。
消息WM_POPMESSAGESTRING用来重新设置状态栏。
这两个消息对应
消息处理函数分别是OnSetMessageString和OnPopMessageString,OnSetMessageString和OnPopMessageString分别实现如下:
OnSetMessageString函数直接或者从ID从字符串资源中得到字符串指针。如果是从ID得到字符串指针,则函数GetMessageString被调用。
和命令ID对应
字符串由两部分组成,前一部分用于在状态栏显示,后一部分用于Tooltip显示,分隔符号是“ ”。例如,字符串ID_APP_EXIT(对应“退出”菜单、按钮)是“Exit Application Exit”,当鼠标落在“退出”按钮上时,状态栏显示“Exit Application”,Tooltip显示“Exit”。根据这种格式,GetMessageString分离出第一部分
文本信息。至于第二部分
用途将在讨论Tooltip
章节将用到。
得到了字符串之后,OnSetMessageString调用状态栏
SetWindowText函数。SetWindowText导致消息WM_SETTEXT消息发送给状态栏,状态栏
消息处理函数OnSetText被调用,实际上等于调用了SetPaneText(0, lpsz),即在状态栏
第0格中显示字符串lpsz
信息。对于工具栏来说,SetWindowText可以认为是SetPaneText(0, lpsz)
简化版本。
顺便指出,pMessageBar->GetParentFrame()返回主边框窗口,即使pMessageBar指向漂浮
工具条。关于泊位和漂浮,见后面13.2.5节
描述。
关于OnSetText,其实现如下:
一般,在清除状态栏消息时,发送WM_POPMESSAGESTRING,通过消息参数wParam指定一个字符串资源,其ID 为AFX_IDS_IDLEMESSAGE,对应
字符串是“Ready”。
状态栏显示菜单项
提示信息
状态栏
一个重要作用是显示菜单命令或者工具条按钮
提示信息。本节讨论如何显示菜单命令
提示信息,关于工具条按钮在这方面
讨论见后面13.2.4.4章节。
显示菜单命令
提示信息,就是每当一个菜单项被选中之后,在状态栏显示该菜单
功能、用法等信息。这些信息以字符串资源
形式保存,字符串ID对应于菜单项
命令ID。
所以,必须处理菜单选择消息WM_MENUSELECT。CFrameWnd实现了消息处理函数OnMenuSelect,其实现如下:
OnMenuSelect
作用在于跟踪当前选中
菜单项,把菜单项对应
ID保存在CFrameWnd
成员变量m_nIDTracking中。
如果菜单项没有选中,或者选中
是一个子菜单,则设置nIDTracking为0。
如果选中
是系统菜单,则把系统菜单ID转换成一个对应
命令ID;保存该值到nIDTracking中。
如果选中
菜单是MDI子窗口创建时添加
(用来表示MDI子窗口),则转换菜单ID为AFX_IDS_MDICHILD,所有对应MDI子窗口
菜单项都使用AFX_IDS_MDICHILD,保存该值到nIDTracking中。
其他情况,就是选中菜单项
ID,把它保存到nIDTracking中。
跟踪被选择
菜单项并保存其ID在m_nIDTracking中,OnEnterIdle将用到m_nIDTracking。OnEnterIlde是消息WM_ENTERIDLE
处理函数,CFrameWnd
实现如下。
当一个对话框或者菜单被显示
时候,Windows发送WM_ENTERIDLE消息。消息参数wParam取值为MSGF_DIALOGBOX或者MSGF_MENU。前者表示显示对话框时发送该消息,这时消息参数lParam表示对话框
句柄;后者表示显示菜单时发送该消息,这时消息参数lParam表示菜单
句柄。
经过消息映射,wParam
值传递给OnEnterIdle
参数nWhy,参数lParam
值传给参数who。如果参数1取值为MSGF_MENU,并且OnEnterIdle最近一次在菜单显示被调用时
菜单ID不同于这一次,则调用SetMessageText在状态栏显示对应ID命令
字符串,并且记录当前菜单ID到变量m_nIDTracking中(见消息处理函数OnSetMessageText)。
这样,在菜单选择期间,用户选择
菜单项ID被OnMenuSelect记录,在消息WM_ENTERIDLE处理时在状态栏显示ID命令
提示。
控制条
消息分发处理
工具条(包括对话框工具条)是一个子窗口,它们可以响应各种消息。如果按标准
Windows消息和命令消息
分发途径,一些消息不能送到拥有工具条
边框窗口,因为这些消息都将被工具条(对话框工具条)处理掉。所以,CControlBar覆盖了虚拟函数PreTranslateMessage和WindowProc以便实现特定
消息分发路径。
WindowProc
CControlBar
WindowProc实现了如下
消息分发路径:
用户对控制条
输入消息或者分发给CControlBar及其派生类处理,或者送给拥有控制条
边框窗口处理,或者送给Windows控制“窗口类”
窗口过程处理。
WindowProc
实现如下:
从上述实现可以看出,对于case范围内
一些消息,如WM_COMMAND、WM_NOTIFY等,控制条如果不能处理,则优先分发给其父窗口(边框窗口)处理,然后进入缺省处理,对于其他消息直接调用基类CWnd
实现(缺省处理)。基于这样
机制,可以把用户对工具条按钮或者对话框工具条内控制
操作解释成相应
命令消息,执行对应
命令处理。
对于工具条,当用户选中某个按钮时(鼠标左键弹起,消息是WM_LBUTTONUP),工具条窗口接收到WM_LBUTTONUP消息,该消息不在CControlBar::WindowProc特别处理
消息范围内,于是进行缺省处理,也就是说,把该消息派发给控制条对应
Windows控制
窗口过程处理(即被MFC统一窗口过程所取代
原窗口过程),该窗口过程则把该消息转换成一条命令消息WM_COMMAND,命令ID就是选中按钮对应
ID,然后,发送该命令消息给拥有工具条
边框窗口,导致相应
命令处理函数被调用。
对于对话框工具条,当工具条
某个控制子窗口被选中之后,则产生一条命令通知消息WM_COMMAND,wParam是控制子窗口
ID。CControlBar::WindowProc处理该消息。WindowProc首先调用OnWndMsg把消息发送给对话框工具条或者对话框工具条
基类处理,如果没有被处理
话,则OnWndMsg返回FALSE。接着,WindowPoc把命令消息传递给父窗口(边框窗口)处理。由于工具条
控制窗口
ID对应
是命令ID,所以,这条WM_COMMAND消息传递给边框窗口时,被解释成一个命令消息,相应
命令处理函数被调用。
PreTranslateMessage
CControlBar覆盖PreTranslateMessage函数,主要是为了在光标落在工具条按钮上时显示FLYBY信息,并且让对话框工具条过滤Dialog消息。
函数PreTranslateMessage主要是针对工具栏
,用来处理工具栏
CBRS_FLYBY特征。
对于对话框工具栏,也可以有CBRS_FLYBY特征。但在这种情况下,还需要把一些用户键盘输入解释成对话框消息。为了防止快捷键被解释成对话框消息,在调用函数PreTranslateInput之前,必须调用所有父边框窗口
PreTranslateMessage,给父边框窗口一个机会处理用户
输入消息,判断快捷键是否被按下。
关于Tooltip和本PreTranslateMessage函数处理Tooltip
详细解释见下一节
讨论。
Tooltip
工具条(或对话框工具条)如果指定了CBRS_TOOLTIPS风格(创建时指定或者创建后用SetBarStyle设置),则当鼠标落在某个按钮上(或者对话框
子控制窗口)时,在鼠标附近弹出一个文本框──Tooltip提示窗口。
如果还指定了CBRS_FLYBY风格,则还在状态栏显示和按钮(或子控制窗口)ID对应
字符串信息。当然,鼠标左键在某个按钮(或子控制窗口)按下时,也要在状态栏显示按钮
提示信息,当左键弹起时,则重置状态栏
状态。
如前所述,Tooltip窗口是Windows控制窗口。MFC使用了CToolTipCtrl类封装Tooltip
HWND窗口。在一个线程
生存期间,至多拥有一个Tooltip窗口,该窗口对象
指针保存在线程状态
成员变量m_pToolTip中。线程状态类AFX_THREAD_STATE
析构函数如果检测到m_pToolTip,则销毁MFC窗口对象和相应
Windows窗口对象。
CWnd对Tooltip消息
预处理
为了支持Tooltip显示,CWnd提供了以下函数:EnableTooltip,CancelTooltip,PreTranslateMessage,FilterTooltipMessage,OnToolHitTest。
EnableTooltip设置CBRS_TOOLTIP风格,相反CancelTootip取消这种风格。
PreTranslateMessage调用了FilterTooltipMessage过滤Tooltip消息。
OnToolHitTest是一个由CWnd定义
虚拟函数。CToolBar通过覆盖该函数,来检测对话框工具栏
控制子窗口或者工具栏按钮是否被选中、哪个被选中。
CWnd
PreTranslateMessage在4.5节讨论过,它
实现如下:
至于为什么MFC在模块状态中保存一个处理Tooltip消息
函数地址,通过该函数调用FilterTooltipMessage,是因为Tooltip窗口是模块线程局部有效
。
FilterTooltipMessage检测是否是Tooltip消息。如果是,在必要时创建一个CTooltipCtrl对象和对应
HWND,调用OnToolHitTest确定被选中
按钮或者控制
ID,接着弹出Tooltip窗口。
其他函数和CTooltipCtrl这里不作详细论述了。
处理TTN_NEEDTEXT通知消息
Tooltip窗口在弹出之前,它给工具条(或者对话框工具栏)
父窗口发送通知消息TTN_NEEDTEXT,请求得到要显示
文本。
CFrameWnd类处理了TTN_NEEDTEXT通知消息,消息处理函数是OnToolTipText。
消息映射
定义:
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
这里,使用了扩展消息映射宏把子窗口ID在0和0xFFFF之间
控制条窗口
通知消息TTN_NEEDTEXTA和TTN_NEEDTEXTW映射到函数OnToolTipText。
消息映射
实现:
OnToolTipText是一个扩展映射宏定义
消息处理函数,所以有一个UINT参数并且返回BOOL类型
值。不过,由于第二个参数(NMHDR)
idFrom域包含了有关信息,所以第一个UINT类型
参数没有用上。
OnToolTipText也是一个处理通知消息
例子。其中,通知参数wParam
结构如4.4.4.2节所述,具体如下:
uflags如果等于TTF_IDISHWND,则表示通知消息来自对话框工具条
一个子窗口,而不是包含工具条按钮。
OnToolTipText根据子窗口ID或者工具条按钮对应
ID,得到字符串ID。如前所述,字符串ID由两部分组成,第二部分用于Tooltip显示,分隔符号是“ ”。根据这种格式OnToolTipText分离出Tooltip文本。
得到了Tooltip文本之后,可以有三种方法返回文本信息:把文本信息复制到szText缓冲区;把文本地址复制到lpszText;复制字符串资源
ID到lpszText、复制包含资源
实例句柄到hint。本函数采用了第一种方法。
在得到了返回
Tooltip文本之后,该文本在Tooltip窗口中被显示出来。
其他
OnToolHist等函数
实现不作详细
解释了。下面,讨论CBRS_FLYBY风格
实现。
CBRS_FLYBY风格
实现
CBRS_FLYBY是MFC提供
特征。当鼠标落在工具条按钮(或者对话框工具条
子窗口)上且稳定300ms后,在状态栏显示对应
提示信息。如果选中某个按钮或者子窗口(鼠标左键按下),则在相应命令消息处理之前在状态栏显示有关提示信息,之后(鼠标左键弹起),重新设置状态栏
状态信息。
为了支持这种特征,CControlBar覆盖虚拟函数PreTranslateMessage来处理和CBRS_FLYBY相关
消息,该函数前面已经讨论过,这里解释它如何处理CBRS_FLYBY特征。
如果同时具备
条件1:控制条具有CBRS_FLYBY特征或者当前消息是WM_LBUTTONUP或者WM_LBUTTONDOWN。
条件2:当前消息是鼠标消息(在WM_MOUSEFIRST和WM_MOUSELAST之间或者在WM_NCMOUSEFIRST和WM_NCMOUSELAST之间)。
则进行FLYBY处理。
首先,调用OnToolHitTest测试用户是否选中了工具条
按钮或者子窗口;
如果没有按钮或者子窗口被选中,则重新设置状态栏
状态,取消曾经设置
Check定时器。重置状态栏
状态时调用了SetStatusText(int nHit)函数,它是CControlBar内部使用
函 数,若nHit等于-1,它向父窗口发送WM_POPMESSAGETEXT,消息参数是AFX_IDS_IDLEMESSAGE,结果导致状态栏显示 “Ready”字样;否则发送WM_SETMESSAGETEXT消息,wParm设置为nHit,结果导致在状态栏显示ID为nHit
字符串。
如果有按钮或者子窗口被选中,若左键弹起,则重新设置状态栏信息,取消Wait定时器,并重新设置Check定时器,定时是200ms;若左键按下,则在状态栏显示消息ID对应
提示信息;若是其他鼠标消息,如果当前鼠标所在按钮(子窗口)不同于最近一次,则取消Check定时器,重新设置Wait定时器,定时300毫秒。
CControlBar覆盖了消息处理函数OnTimer,在指定时间之后,检查鼠标位置,如果鼠标还在某个按钮或者子窗口上,则在状态条显示提示信 息。Wait定时器在等待之后准备在状态条显示信息,触发一次后被取消;Check定时器在等待之后,判断是否需要取消状态条当前显示
信息,重新设置状态条,若这样
话,同时也取消Check定时器。
注意,这些鼠标消息被处理之后,并没有终止,它们将继续被发送给控制条
窗口过程处理。
至此,CBRS_FLYBY特征
支持实现描述完毕。
禁止和允许
在MFC下,工具条、状态条还有一个重要
特征,就是自动地根据条件禁止或者允许使用某个按钮、窗格等。在4.4.5节命令消息
处理中,曾详细讨论了其实现原理,现在,详细地分析所涉及函数是如何实现
。有关
消息处理函数和虚拟函数如下。
处理WM_INITIALUPDATE消息
OnInitialUpdate;
处理WM_IDLEUPDATECMDUI消息
OnIdleUpdateCmdUI;
虚拟函数OnUpdateCmdUI。
回顾5.3.3.5节,在边框窗口
创建之后,给所有
子窗口发送初始化消息,控制子窗口用OnInitialUpdate响应它,调用OnIdleUpdateCmdUI完成状态
初始化。
OnIdleUpdateCmdUI还在IDLE处理时进行状态
更新处理,它生成用于处理状态更新消息
命令目标pTarget,然后调用虚拟函数OnUpdateCmdUI(pTarget,…)来更新工具栏或者状态栏
状态。
CControlBar
子类都实现了自己
OnUpdateCmdUI函数,用该函数生成适当
CCmdUI对象state,然后调用CCmdUI
DoUpdate(pTarget,…)给pTarget所指对象发送状态更新消息。为了完成具体
状态更新,从CCmdUI派生出CToolCmdUI和CStatusCCmUI,它们实现了自己
Enable、SetCheck等等。
初始化控制窗口
CControlBar使用OnInitialUpdate消息处理函数初始化控制窗口
状态。
CControlBar实现了OnInitialUpdate函数,通过它来处理WM_INITIALUPDATE消息。各个子类不必覆盖该消息处理函数。
处理Idle消息更新工具条状态
CControlBar使用OnIdleUpdateCmdUI消息处理函数处理IDLE消息。
OnIdleUpdateCmdUI或者在初始化时被OnInitialUpdate调用,或者作为消息处理函数来处理WM_IDLEUPDATECMDUI消息。
CControlBar实现了OnIdleUpdateCmdUI函数,把具体
用户界面更新动作委托给虚拟函数OnUpdateCmdUI完成。
由于各个用户界面
特殊性,所以CControlBar本身没有实现OnUpdateCmdUI,而是留给各个派生类去实现。例如,CToolBar覆盖了OnUpdateCmdUI,其实现如下:
CToolBar
OnUpdateCmdUI函数完成工具条按钮
状态更新。它接受两个参数,参数1表示接收状态更新命令消息
对象,由CControlBar
函数OnIdleUpdateCmdUI传递过来,一般是边框窗口对象;参数2表示如果某条命令消息没有处理函数时,对应
用户接口对象是否被禁止。
OnUpdateCmdUI通过发送状态更新通知消息,逐个更新按钮
状态。更新消息首先让工具条对象处理,如果没有处理
话,送给边框窗口对象处理,导致状态更新命令消息
处理函数被调用,参见4.4.5节。
CStatusBar
OnUpdateCmdUI类似于此。
CDialogBar
OnUpdateCmdUI则调用了虚拟函数UpdateDialogControls来进行状态更新,CWnd提供了该函数
实现,过程类似于CToolBar
函数OnUpdateCmdUI。
菜单项
自动更新
那么,菜单项
自动更新如何实现
呢?OnInitMenuPopup在菜单项状态
自动更新中曾经被提到,其实现如下:
菜单弹出之前,发送WM_INITMENUPOPUP消息,OnInitMenuPopup消息处理函数被调用,逐个更新菜单项目(menu item)
状态。程序员可以处理它们对应
状态更新消息,禁止/允许菜单项目被使用(disable/enable),在菜单项目上打钩或者取消(checked/unchecked),等等。
显示或者隐藏工具栏和状态栏
这里讨论显示或者隐藏工具栏、状态栏
操作,以及工具栏、状态栏被显示/隐藏时,相关
两个菜单项ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR
状态更新。这两个菜单命令及对应
状态更新命令是标准命令消息所包含
。MFC边框窗口实现了菜单命令消息
处理和菜单项状态
更新。
CFrameWnd提供了OnBarCheck来响应与ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR菜单项对应
命令。
消息映射:
ON_COMMAND_EX(ID_VIEW_STATUS_BAR, OnBarCheck)
ON_COMMAND_EX(ID_VIEW_TOOLBAR, OnBarCheck)
这里,使用了扩展命令消息映射宏把ID_VIEW_STATUS_BAR和ID_VIEW_TOOLBAR命令映射给同一个函数OnBarCheck处理。
OnBarCheck函数
实现:
由于是扩展映射宏定义
消息处理函数,所以OnBarCheck函数有一个UINT类型
参数和一个BOOL返回值。
当用户从“View”菜单选择打了钩
“Toolbar”时,消息处理函数OnBarCheck被调用,参数就是菜单项
ID号ID_VIEW_TOOLBAR,它等于工具条
子窗口IDAFX_IDW_TOOLBAR。处理结果,工具条被隐藏;当再次选择该菜单项则工具条被显示。
处理状态条
过程类似于工具条
处理。
ShowControlBar是CFrameWnd
成员函数,参数1表示控制条对象指针,参数2表示显示(TRUE)或者隐藏(FALSE),参数3表示是立即显示(FALSE)或者延迟显示(TRUE)。
如果工具条或者状态条被隐藏,则相应
菜单项ID_VIEW_STATUS_BAR 或者ID_VIEW_TOOLBAR 变成uncheked(菜单项被标记为没有选择),否则,checked(菜单项被标记选择)。CFrameWnd实现了这两个菜单项
状态更新处理,列举其中一个如下:
声明处理ID_VIEW_TOOLBAR
状态更新消息:
ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR, OnUpdateControlBarMenu)
函数
实现:
GetControlBar是CFrameWnd
成员函数,用来返回边框窗口
指定ID
控制条对象(指定ID是控制条
子窗口ID)。
泊位和漂浮
工具条可以泊位在边框窗口
任一边(上、下、左、右),或者漂浮在屏幕上
任何地方。
实现泊位
方法
首先,边框窗口调用CFrameWnd::EnableDocking函数使控制条泊位在边框窗口中有效,指明在边框窗口
哪边接受泊位。如果想在任何边都可以泊位,则使用参数CBRS_ALIGN_ANY。
然后,工具条调用ControlBar::EnableDocking使泊位对工具条有效,如果在调用ControlBar::EnableDocking时指定
泊位目
边和边框窗口能够泊位
边不符合,那么工具条不能泊位,它将漂浮。
最后,边框窗口调用CFrameWnd::DockControlBar泊位工具条。
泊位后形成窗口层次关系
边框窗口、泊位条、工具条
包含关系如下:
边框窗口
泊位条1
工具条1
工具条2
…
泊位条2
…
边框窗口包含1到4个泊位条子窗口,每个泊位条包含若干个控制条子窗口。
泊位
实现
CFrameWnd::EnableDocking指定哪边接受泊位,则为泊位准备一个泊位条。泊位条用CDockBar描述,派生于CControlBar。如果指定任何边都可以泊位,则创建四个CDockBar对象和对应
HWND窗口。然后,调用ControlBar::EnableDocking在对应
泊位条内安置工具条。
MFC设计了CDockBar类和CFrameWnd
一些函数来实现泊位,具体代码实现在此不作详细讨论。
实现漂浮工具条
方法:
边框窗口调用FloatControlBar实现工具条
漂浮。
漂浮
实现:
首先,创建一个微型漂浮边框窗口,该边框窗口有一个泊位条。
然后,在微型边框窗口
泊位条内放置工具条。
MFC设计了微型边框类CMiniFrameWnd,在此基础上派生出微型泊位边框窗口类CMiniDockFrameWnd。CMiniDockFrameWnd增加了一个CDockBar类型成员变量m_wndDockBar,即泊位条。
在CMiniDockFrameWnd对象被创建时,创建泊位条m_wndDockBar。泊位条m_wndDockBar
父窗口如同CMiniDockFrameWnd
父窗口一样,是调用FloatControlBar
边框窗口,而不是微型泊位边框窗口。微型边框窗口和泊位条创建完成之后,调用ControlBar::DockControlBar泊位工具条在CMiniDockFrameWnd窗口。
Windows控制窗口
Windows (Windows95或者以上版本) 提供了系列通用控制窗口,其中包括工具条(ToolBar)、状态栏(StatusBar)、工具条提示窗口(ToolTip)。
Windows在一个DLL加载时注册个控制窗口
“窗口类”。例如,工具条
“窗口类”是“ToolbarWindow32”,状态栏
“窗口类”是“msctls_statusbar32”,工具条提示窗口
“窗口类”是“tooltips_class32”。为了保证该DLL被加载,使用控制“窗口类”前,应该首先调用函数InitCommonControl。MFC在窗口注册函数AfxDeferRegisterClass中实现了这一点。见2.2.1节MFC下窗口
注册。
创建通用控制窗口,可以使用专门
创建函数,如创建工具条
函数::CreateToolBarEx,创建状态栏
函数::CreateStatusBarEx。也可以调用窗口创建函数::CreateWindowEx,但是需要指定预定义
“窗口类”,必要
话还要其他步骤,如使用“ToolbarWindow32”“窗口类”创建工具栏后,还需要在工具栏中添加或者插入按钮。
一般,通用控制可以指定控制窗口风格(Style)。例如,具备风格CCS_TOP,表示该控制窗口放到父窗口客户区
顶部,具备CCS_BOTTOM,表示该控制窗口在客户区
底部。具体
控制窗口类可以有特别
适合于自己
风格,例如,TTS_ALWAYSTIP表示只要光标落在工具栏
按钮上,ToolTip窗口不论激活与否都会显示出来。
每一控制窗口类都有自己
窗口过程来处理自己
窗口消息,实现特定
功能。控制窗口类
窗口过程由Windows提供。
工具条
工具条
窗口过程处理了必要
消息,提供了标准工具条
功能,例如,工具条对客户化特征提供内在
支持,用户可以通过一个客户化对话框来添加、修改、删除或者重新安排工具条按钮。这些特征是否可以被用户所用或者用到什么地步是可以由程序控制
。
工具条
窗口过程将自动设置工具条
尺寸大小和位置,如果指定了控制窗口风格CCS_TOP或者CCS_BOTTOM,则窗口过程把工具条放到父窗口客户区
顶部或者底部。窗口过程任何时候只要收到WM_SIZE或者TB_AUTOSIZE消息就自动地调整工具条
大小和位置。
工具条
按钮被选中后,会产生一个命令消息,它
窗口过程把该消息送给父窗口
窗口过程处理。
工具条中
按钮并不以子窗口
形式出现,而是以字符或者位图按钮
方式显示,每个按钮大小相同,缺省是24*22个像素。每个按钮都有一个索引,索引编号从0开始。每个按钮包括如下属性:
按钮
字符串索引,位图索引,风格,状态,命令ID
按钮可以有两种风格TBSTYLE_BUTTON和TBSTYLE_CHECK,前者像一个标准按钮那样响应用户
按击,后者响应每一次按击,在按下和跳起两种状态之间切换。按钮响应用户
动作,给父窗口发送一个包含了该按钮对应命令ID
命令消息。一般一个按钮
命令ID对应一个菜单项。
工具条维护两个列表,分别用来存放工具条按钮使用
字符串或者位图,列表中
位图或者字符串从0开始编号,编号和按钮
索引相对应。
工具条可以是Dockable(泊位)或者Floatable(漂浮)
。
工具条可以有TBSTYLE_TOOLTIPS风格,如果具有这种风格,则创建和管理一个Tooltip控制,这是一个小
弹出式窗口,用来显示描述按钮
文本,平时该窗口隐藏,当鼠标落到按钮上面并停留约一秒后才弹出,在鼠标附近显示。
由于Tooltip窗口平时是隐藏
,所以不能接收鼠标消息来决定何时显示本窗口。这样,接收鼠标
窗口必须把鼠标消息送给Tooltip窗口,这是通过给Tooptip窗口发送消息TTM_RELAYEVENT来实现
。
状态栏
状态栏类似于工具条,有自己
窗口过程,可以泊位、漂浮。不过,习惯上状态栏都位于屏幕底部。每个状态条分成若干格(Status bar panes),每格从0开始编号,编号作为格
索引。每一个格,如同工具条
按钮一样,并不是一个Windows窗口。
MFC
工具条和状态栏类
MFC使用CToolBarCtrl、CStatusBarCtrl和CToolTipCtrl窗口类分别对工具条、状态栏、Tooltip控制窗口进行了封装。
但是,直接使用这些类还不是很方便。MFC提供了CToolBar、CStatusBar来处理状态栏和工具条,CToolBar、CStatusBar功能更强大,灵活。这两个类都派生于CControlBar。
在MFC下,建议这些控制条子窗口ID介于AFX_IDW_TOOLBARFIRST(0xE800)和AFX_IDW_CONTROLBAR_LAST(0Xe8FF)之间。这256个ID中,前32个又有其特殊性,用于MFC
打印预览中。
CControlBar派生于CWnd类,是控制条窗口类
基类,它派生出CToolBar、CStatusBar、CDockBar、CDialogBar、COleResizeBar类。CControlBar实现了以下功能:
和父窗口(边框窗口)
顶部或者底部或者其他边对齐。
可以包含子条目,这些条目或者是基于HWND
子窗口,或者是基于非HWND
条目。负责分配条目数组。
支持CBRS_TOP(缺省,控制条放在顶部),CBRS_BOTTOM(放在底部),CBRS_NOALIGN(父窗口大小变化时不重新放置控制条)等几种控制风格。
支持派生类
实现。几个派生类有一定
共性,或者其中两个有一定
共性,这样CControlBar实现
函数一部分只适用于某个派生类,一部分适用于两个或者多个派生类,还有一部分适用于所有
派生类。所谓适用,这里指派生类直接继承了CControlBar
实现,或者覆盖了其实现但是建立在扩展其实现
基础上。类似地,CControlBar
成员变量也不是为所有派生类所共同适用
。
CStatusBar和CControlBar一方面建立在CControlBar
基础之上,另一方面以Windows
通用控制状态栏和工具条为基础。它们继承了CControlBar类
特性,但是所封装
窗口句柄是相应
Windows控制窗口
句柄,如同CFormView继承了CSrcollView
视类特性,但是其窗口句柄是无模式对话框窗口句柄一样。
典型地,如果在使用AppWizard生成应用程序时,指定了要求工具条和状态栏
支持,则在主边框窗口
OnCreate函数中包含一段如下
代码,用来创建工具条、状态栏和设置一些特性。
//创建工具栏 if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar "); return -1; // fail to create } //创建状态栏 if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar "); return -1; // fail to create } // TODO: Remove this if you don't want tool tips or a resizeable toolbar //对工具栏设置Tooltip特征 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC); //使得工具栏可以泊位在边框窗口 // TODO: Delete these three lines if you don't want the toolbar to // be dockable m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar);
工具条除了Tooltip,Resizeable,Dockable特性外,还可以是Floatable。应用程序可以使用CFrameWnd::SaveBarState保存边框窗口
控制条
有关信息到INI文件或者Windows Register库,使用LoadBarSate从INI文件或者Register库中读取有关信息并恢复各个控制条
设置。
下文,将讨论工具条等
创建、销毁,从中分析CControlBar和派生类
关系,讨论CControlBar如何实现共性,如何支持派生类
特定要求,派生类又如何实现自己
特定需求等。
控制窗口
创建
创建工具条、状态条、对话框工具栏
方法是不同
,所以必须给每个派生类CToolBar、CStatusBar、CDialogBar设计和实现自己
窗口创建函数Create。但是,它们是也是有共性
,共性由CControlBar
PreCreateWindow处理。在窗口创建之后,各个派生类都要进行
处理(共性)由CControlBar
OnCreate完成,特别
处理通过派生类
OnNcCreate完成。
PreCreateWindow
首先,讨论CControlBar 类
PreCreateWindow
实现。
BOOL CControlBar::PreCreateWindow(CREATESTRUCT& cs) { if (!CWnd::PreCreateWindow(cs)) return FALSE; //修改窗口风格,强制适用clipsliblings,以防重复绘制 cs.style |= WS_CLIPSIBLINGS; //default border style translation for Win4 //(you can turn off this translation by setting CBRS_BORDER_3D) if (afxData.bWin4 && (m_dwStyle & CBRS_BORDER_3D) == 0) { DWORD dwNewStyle = 0; switch (m_dwStyle & (CBRS_BORDER_ANY|CBRS_ALIGN_ANY)) { case CBRS_LEFT: //控制条在边框窗口 左边显示 dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM; break; case CBRS_TOP://控制条在边框窗口 顶部显示 dwNewStyle = CBRS_BORDER_TOP; break; case CBRS_RIGHT://控制条在边框窗口 右边显示 dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM; break; case CBRS_BOTTOM://控制条在边框窗口 底部显示 dwNewStyle = CBRS_BORDER_BOTTOM; break; } // set new style if it matched one of the predefined border types if (dwNewStyle != 0) { m_dwStyle &= ~(CBRS_BORDER_ANY); m_dwStyle |= (dwNewStyle | CBRS_BORDER_3D); } } return TRUE; }
其中,afxData是一个全局变量,MFC用它来记录系统信息,如版本信息等。这里afxData.bWin4表示Windows版本是否高于4.0。
CToolBar
PreCreateWindow函数修改了窗口风格,也修改状态栏、工具栏等
CBRS_风格。CBRS_风格
改变不会影响窗口风格。因为这些CBRS_风格被保存在成员变量m_dwStyle中。
除了上述在程序中用到
影响工具条、状态栏等显示位置
CBRS_风格外,还有和泊位相关
CBRS_风格,CBRS_ALIGN_LEFT、CBRS_ALIGN_RIGHT、CBRS_ALIGN_BOTTOM、CBRS_ALIGN_TOP、CBRS_ALIGN_ANY,分别表示工具条可以在停泊在边框窗口
左边、右边、底部、顶部或者所有这些位置;和漂浮相关
CBRS_风格CBRS_FLOAT_MULTI,表示多个工具条可以漂浮在一个微型边框窗口中;和Tooltips相关
CBRS_风格CBRS_TOOLTIPS和CBRS_FLYBY。
派生类如果没有特别
要求,可以不覆盖PreCreateWindow函数。CStatusBar因为有更具体和特殊
风格要求,所以它覆盖了PreCreateWindow。CStatusBar
覆盖实现调用了CControlBar
实现。
派生类也可以在覆盖实现中修改PreCreateWindow参数cs,改变窗口风格;修改m_dwStyle,改变CBRS_风格。
控制条
窗口创建
CControlBar派生类实现了自己
窗口创建函数Create,CControlBar
PreCreateWindow被派生类
Create函数直接或者间接地调用。以CToolBar为例讨论窗口创建函数和创建过程。
CToolBar
窗口创建函数Create
Create函数实现如下:
BOOL CToolBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID) { ASSERT_VALID(pParentWnd); // must have a parent ASSERT (!((dwStyle & CBRS_SIZE_FIXED) && (dwStyle & CBRS_SIZE_DYNAMIC))); // 保存dwStyle指定 CBRS_风格 m_dwStyle = dwStyle; if (nID == AFX_IDW_TOOLBAR) m_dwStyle |= CBRS_HIDE_INPLACE; //去掉参数dwStyle包含 CBRS_风格 dwStyle &= ~CBRS_ALL; //设置窗口风格 dwStyle |= CCS_NOPARENTALIGN|CCS_NOMOVEY|CCS_NODIVIDER|CCS_NORESIZE; //初始化通用控制,可以导致InitCommonControl 调用 VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG)); //创建窗口,将调用PreCreateWindow,OnCreate, OnNcCreate等 CRect rect; rect.SetRectEmpty(); if (!CWnd::Create(TOOLBARCLASSNAME, NULL, dwStyle, rect, pParentWnd, nID)) return FALSE; // Note: Parent must resize itself for control bar to be resized return TRUE; }
其中:
Create函数
参数1表示工具条
父 窗口。参数2指定窗口风格和CBRS_风格,缺省值为 WS_CHILD | WS_VISIBLE | CBRS_TOP,其中WS_CHILD和WS_VISIBLE是窗口风格,CBRS_TOP是CBRS_风格。参数3指定工具条ID,缺省值为 AFX_IDW_TOOLBAR(0X0E800或者59392)。如果还有多个工具栏要显示,在创建它们时则必须给每个工具栏指明ID。
首先,Create函数把参数2(dwStyle)指定
窗口风格和CBRS_风格分离出来,窗口风格保留在dwStyle中,CBRS_风格保存到成员变量m_dwStyle中。CToolBar::PreCreateWindow将进一步修改这些风格。
接着,Create函数调用了函数AfxDeferRegisterClass。它如果没有注册TOOLBARCLASSNAME表示
“窗口类”,就注册该类;否则,返回TRUE,表示已经注册。TOOLBARCLASSNAME表示
字符串是“ToolbarWindow32”,即“窗口类”名称。
然后,调用CWnd::Create(7个参数)使用“ToolbarWindow32”“窗口类”创建工具栏。
Create在创建窗口
过程中,用MFC
标准窗口过程取代原来
窗口过程,如同CFormView和CDialog窗口创建时窗口过程被取代一样,并发送WM_CREATE和WM_NCCREATE消息。
至于添加向工具栏添加按钮,则由函数LoadToolBar完成。在分析LoadToolBar函数之前,先讨论OnCreate、OnNcCreate等函数。
处理WM_CREATE消息
CControlBar提供了消息处理函数OnCreate来处理WM_CREATE消息。
int CControlBar::OnCreate(LPCREATESTRUCT lpcs) { //调用基类 实现 if (CWnd::OnCreate(lpcs) == -1) return -1; //针对工具栏,是否有Tooltip特性 if (m_dwStyle & CBRS_TOOLTIPS) EnableToolTips(); //得到父窗口,并添加自身到其控制条列表中 CFrameWnd *pFrameWnd = (CFrameWnd*)GetParent(); if (pFrameWnd->IsFrameWnd()) { m_pDockSite = pFrameWnd; m_pDockSite->AddControlBar(this); } return 0; }
如果需要支持Tooltips,则OnCreate调用EnableTooltips。
m_pDockSite是CControlBar
和泊位相关
成员变量,这里把它初始化为拥有工具栏
父边框窗口,该边框窗口把控制条加入其控制条列表m_listControlBars中。
在处理WM_CREATE之前,派生类先处理消息WM_NCCREAE。例如,CToolBar覆盖了OnNcCreate函数。
处理WM_NCCREATE消息
CToolBar对WM_NCCREATE消息
处理如下:
BOOL CToolBar::OnNcCreate(LPCREATESTRUCT lpCreateStruct) { if (!CControlBar::OnNcCreate(lpCreateStruct)) return FALSE; // if the owner was set before the toolbar was created, set it now if (m_hWndOwner != NULL) DefWindowProc(TB_SETPARENT, (WPARAM)m_hWndOwner, 0); DefWindowProc(TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0); return TRUE; }
CToolBar覆盖CcontrolBar
该函数用来设置工具条
所属窗口和描述工具条按钮结构
大小,这两个动作都是通过给工具条窗口发送消息来实现
。因为这些消息被送给控制窗口类
窗口过程(Windows提供
)来处理,所以直接调用DefWindowProc,省却了消息发送
过程。
在控制窗口创建之后,对于工具条来说,下一步就是向工具栏添加按钮。
向工具栏添加按钮
通过函数LoadToolBar完成向工具栏添加按钮
任务,其实现如下:
BOOL CToolBar::LoadToolBar(LPCTSTR lpszResourceName) { ASSERT_VALID(this); ASSERT(lpszResourceName != NULL); //查找并确认按钮位图、字符串等资源 位置 HINSTANCE hInst = AfxFindResourceHandle(lpszResourceName, RT_TOOLBAR); HRSRC hRsrc = ::FindResource(hInst, lpszResourceName, RT_TOOLBAR); if (hRsrc == NULL) return FALSE; //锁定资源 HGLOBAL hGlobal = LoadResource(hInst, hRsrc); if (hGlobal == NULL) return FALSE; CToolBarData* pData = (CToolBarData*)LockResource(hGlobal); if (pData == NULL) return FALSE; ASSERT(pData->wVersion == 1); //复制与各个位图对应 命令ID到数组pItem UINT* pItems = new UINT[pData->wItemCount]; for (int i = 0; i < pData->wItemCount; i ) pItems[i] = pData->items()[i]; //添加按钮到工具栏,指定各个按钮对应 ID BOOL bResult = SetButtons(pItems, pData->wItemCount); delete[] pItems; //设置按钮 位图 if (bResult) { // set new sizes of the buttons CSize sizeimage(pData->wWidth, pData->wHeight); CSize sizeButton(pData->wWidth 7, pData->wHeight 7); SetSizes(sizeButton, sizeimage); // load bitmap now that sizes are known by the toolbar control bResult = LoadBitmap(lpszResourceName); } UnlockResource(hGlobal); FreeResource(hGlobal); return bResult; }
LoadToolBar函数
参数指定了资源。ToolBar资源
类型是RT_TOOLBAR,ToolBar位图资源
类型是RT_BITMAP。
在RT_TOOLBAR类型
资源读入内存之后,可以用CToolBarData结构描述。一个这样
结构包括了ToolBar资源
如下信息:
工具条位图
版本,宽度,高度,个数,各个位图对应
命令ID。
然后,LoadToolBar把这些命令ID被复制到数组pItem中;根据位图宽度、高度形成按钮尺寸sizeButton和位图尺寸sizeimage。
接着,调用SetBottons添加按钮到工具栏,把各个按钮和命令ID对应起来;调用SetSizes设置按钮和位图
尺寸大小;调用LoadBitmap添加或者取代工具条
位图列表。这些动作都是调用工具栏“窗口类”
窗口过程完成
。例如,SetButtons
实现:
BOOL CToolBar::SetButtons(const UINT* lpIDArray, int nIDCount) { ASSERT_VALID(this); ASSERT(nIDCount >= 1); // must be at least one of them ASSERT(lpIDArray == NULL || AfxIsValidAddress(lpIDArray, sizeof(UINT) * nIDCount, FALSE)); //首先,删除工具条中现有 按钮 int nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0); while (nCount--) VERIFY(DefWindowProc(TB_DELETEBUTTON, 0, 0)); if (lpIDArray != NULL)//命令ID数组非空 { //添加新按钮 TBBUTTON button; memset(&button, 0, sizeof(TBBUTTON)); int iimage = 0; for (int i = 0; i < nIDCount; i ) { button.fsState = TBSTATE_ENABLED; if ((button.idCommand = *lpIDArray ) == 0) { //按钮之间分隔 button.fsStyle = TBSTYLE_SEP; //按钮之间隔8个像素 button.iBitmap = 8; } else { //有位图和命令ID 按钮 button.fsStyle = TBSTYLE_BUTTON; button.iBitmap = iimage ;//设置位图索引 } //添加按钮 if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&button)) return FALSE; } } else//命令ID数组空,添加空按钮 { TBBUTTON button; memset(&button, 0, sizeof(TBBUTTON)); button.fsState = TBSTATE_ENABLED; for (int i = 0; i < nIDCount; i ) { ASSERT(button.fsStyle == TBSTYLE_BUTTON); if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&button)) return FALSE; } } //记录按钮个数到成员变量m_nCount中 m_nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0); //稍后放置按钮 m_bDelayedButtonLayout = TRUE; return TRUE; }
函数
参数1是一个数组,数组
各个元素就是命令ID;参数2是按钮
个数。首先,SetButtons删除工具条原来
按钮;然后,添加新
按钮,若命令ID数组非空,则把每一个按钮和命令ID对应并分配位图索引,否则设置空按钮并返回FALSE;最后,记录按钮个数。
从SetButtons
实现可以看出,对工具条
所有操作都是通过工具条“窗口类”
窗口过程完成
,SetSizes、LoadBitmap也是如此,这里不作讨论。
状态栏和对话框工具栏
创建
至此,分析了MFC创建工具条窗口
过程。对于状态栏和对话框工具栏有类似
步骤,但也有不同之处。
CStatusBar
Create使用“msctls_statusbar32”“窗口类”创建状态栏,窗口ID为AFX_IDW_STATUS_BAR(0XE801),然后通过成员函数SetIndictors给状态栏分格,类似于给工具条添加按钮
过程,它实际上是通过状态栏“窗口类”
窗口过程完成
。
CDialogBar
Create使用CreateDlg创建对话框工具栏,类似于CFormView
过程。在工具栏窗口创建之后,要添加到父窗口
工具栏列表中,这通过CControlBar::OnCreate完成。这样创建
结果导致窗口过程使用MFC
统一
窗口过程,相应“窗口类”
窗口过程也将在缺省处理中被调用,这一点如同CFormView和CDialog中所描述
。在初始化对话框
时候完成了各个控制按钮
添加。
CStatusBar和CdialogBar都没有处理消息WM_NCCREATE。
关于CStautsBar和CDialogBar创建过程
具体实现,这里不作详细讨论了。
控制条
销毁
描述了控制条
创建,顺便考察其销毁
设计。
工具条、状态栏等这些控制窗口都要使用DestroyWindow来销毁,所有有关操作集中由CControlBar处理。CControlBar覆盖了虚拟函数DestroyWindow、PostNcDestroy和消息处理函数OnDestroy。
当然,各个派生类
虚拟析构函数被实现。如果成员变量m_bAutoDelete为TRUE,则动态创建
MFC窗口将自动销毁。
处理控制条
位置
计算控制条位置
过程和算法
工具条等控制条是作为一个子窗口在父边框窗口内显示
。为了处理控制条
布置(Layout),首先需要计算出控制条
尺寸大小,这个工作被委派给工具条等控制窗口自己来完成。为此,CControlBar提供了两个函数来达到这个目
:CalcFixLayout,CalcDynamicLayout。这两个函数都是虚拟函数。各个派生类都覆盖了这两个或者其中一个函数,用来计算自身
尺寸大小。这些计算比较琐碎,在此不作详细讨论。其次,在父窗口位置或者大小变化时,控制条
大小和位置要作相应
调整。
下面,描述MFC确定或者更新工具条、状态栏等位置
步骤:
(1)边框窗口在必要
时候调用虚拟函数RecalcLayout来重新放置它
控制条和客户窗口,例如在创建窗口时、响应消息WM_SIZE时(见5.3.3.5节)边框窗口
初始化)。
(2)CFrameWnd::RecalcLayout调用CWnd
成员函数RepositionBars完成控制条窗口
重新放置。
(3)CWnd::RepositionBars作如下
处理:
RepositionBars首先给各个控制子窗口发送(Send)MFC内部使用
消息WM_SIZEPARENT,把窗口客户区矩形指针传递给它们,给它们一个机会来确认自己
尺寸。
然后,各个控制子窗口用OnSizeParent响应WM_SIZEPARENT消息;ControlBar实现了消息处理函数OnSizeParent,它调用CalcDynamicLayout等函数确定本窗口
大小,并从客户区矩形中减去自己
尺寸。
在所有
控制子窗口处理了OnSizeParent消息之后,RepositonBars利用返回
信息调用函数CalcWindowRect计算客户区窗口(MDI客户窗口、View等)
大小。
最后,调用::EndDeferWindowPos或者::SetWindowPos放置所有
窗口(控制子窗口和客户窗口)。
在窗口被放置
时候,发送消息WM_WINDOWPOSCHANGING和WM_WINDOWPOSCHANGED。MFC
实现中,控制窗口响应了前一个消息,消息处理函数是OnWindowPosChanging。CControlBar、CToolBar和CStatusBar等实现了消息处理函数OnWindowPosChanging。
上述处理过程所涉及
这些函数中,RecalcLayout是CFrameWnd定义
虚拟函数;RepostionBars是CWnd
成员函数;CalcaWindowRect是CWnd
虚拟函数;OnSizeParent是CControlBar定义
消息处理函数;OnWindowPosChanging是CToolbar、CStatusBar、CDockBar等CControlBar派生类定义
消息处理函数。
下面,对其中两个函数RecalcLayout和RepositionBars作一些分析。
CFrameWnd
虚拟函数RecalcLayout
RecalcLayout
实现如下:
void CFrameWnd::RecalcLayout(BOOL bNotify) { //RecalcLayout是否正在被调用 if (m_bInRecalcLayout) return; m_bInRecalcLayout = TRUE; // clear idle flags for recalc layout if called elsewhere if (m_nIdleFlags & idleNotify) bNotify = TRUE; m_nIdleFlags &= ~(idleLayout|idleNotify); //与OLE相关 处理 #ifndef _AFX_NO_OLE_SUPPORT // call the layout hook -- OLE support uses this hook if (bNotify && m_pNotifyHook != NULL) m_pNotifyHook->OnRecalcLayout(); #endif //是否包含浮动(floating)控制条 边框窗口(CMiniFrameWnd类) if (GetStyle() & FWS_SNAPTOBARS) { //计算控制条和边框窗口 位置、尺寸并设置它们 位置 CRect rect(0, 0, 32767, 32767); RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, &rect, &rect, FALSE); RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, &m_rectBorder, &rect, TRUE); CalcWindowRect(&rect); SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(), SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); } else //是普通边框窗口,则设置其所有子窗口 位置、尺寸 RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, &m_rectBorder); //本函数处理完毕 m_bInRecalcLayout = FALSE; }
该函数主要
目
是调用RepositionBars函数,它分两种情况来调用RepositionBars函数。一种情况是当前边框窗口为浮动控制条
包容窗口(微型边框窗口)时;另一种情况是当前边框窗口为普通边框窗口时。
CWnd
成员函数RepositionBars
RepositionBars
实现如下:
void CWnd::RepositionBars(UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver, UINT nFlags, LPRECT lpRectParam, LPCRECT lpRectClient, BOOL bStretch) { ASSERT(nFlags == 0 || nFlags == reposQuery || nFlags == reposExtra); AFX_SIZEPARENTPARAMS layout; HWND hWndLeftOver = NULL; layout.bStretch = bStretch; layout.sizeTotal.cx = layout.sizeTotal.cy = 0; if (lpRectClient != NULL) layout.rect = *lpRectClient; //从参数6得到客户区 else //参数lpRectClient空,得到客户区域 GetClientRect(&layout.rect); if (nFlags != reposQuery) //准备放置各个子窗口(layout) layout.hDWP = ::BeginDeferWindowPos(8); // reasonable guess else layout.hDWP = NULL; // not actually doing layout //按一定顺序给各个控制条发送父窗口resize 消息; //各个控制条窗口收到消息后,从客户区中扣除自己使用 区域; //并且必要 话每个控制窗口调用::DeferWindowPos //剩下 区域留给nIDLeftOver子窗口 for (HWND hWndChild = ::GetTopWindow(m_hWnd); hWndChild != NULL; hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT)) { UINT nIDC = _AfxGetDlgCtrlID(hWndChild); CWnd* pWnd = CWnd::FromHandlePermanent(hWndChild); //如果是指定 nIDLeftOver子窗口,则保存其窗口句柄; //否则,是控制条窗口,给它们发送WM_SIZEPARENT消息 if (nIDC == nIDLeftOver) hWndLeftOver = hWndChild; else if (nIDC >= nIDFirst && nIDC <= nIDLast && pWnd != NULL) //如果layout->hDWP非空, OnSizeParent则将执行窗口布置 操作 ::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&layout); } //如果是reposQuery,则得到客户区矩形,返回 if (nFlags == reposQuery) { ASSERT(lpRectParam != NULL); if (bStretch) ::CopyRect(lpRectParam, &layout.rect); else { lpRectParam->left = lpRectParam->top = 0; lpRectParam->right = layout.sizeTotal.cx; lpRectParam->bottom = layout.sizeTotal.cy; } return; } //其他情况下(reposDefault、reposExtra),则需要执行Layout操作 //处理hWndLeftOver(nIDLeftOver子窗口) if (nIDLeftOver != 0 && hWndLeftOver != NULL) { CWnd* pLeftOver = CWnd::FromHandle(hWndLeftOver); // allow extra space as specified by lpRectBorder if (nFlags == reposExtra) { ASSERT(lpRectParam != NULL); layout.rect.left = lpRectParam->left; layout.rect.top = lpRectParam->top; layout.rect.right -= lpRectParam->right; layout.rect.bottom -= lpRectParam->bottom; } //基于layout.rect表示 客户尺寸计算出窗口尺寸 pLeftOver->CalcWindowRect(&layout.rect); //导致函数::DeferWindowPos 调用 AfxRepositionWindow(&layout, hWndLeftOver, &layout.rect); } //给所有 窗口设置尺寸、位置(size and layout) if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP)) TRACE0("Warning: DeferWindowPos failed - low system resources. "); }
RepositionBars用来改变客户窗口中控制条
尺寸大小或者位置,其中:
参数1和参数2定义了需要重新放置
子窗口ID
范围,一般是0到0xFFFF。
参数3指定了一个子窗口ID,它拥有客户窗口剩下
空间,一般是AFX_IDW_PANE_FIRST,表示视
窗口ID。
参数4指定了操作类型,缺省是CWnd::ReposDefault,表示执行窗口放置操作,参数5不会用到;若取值CWnd::ReposQuery,则表示尝试进行窗口放置(Layout) ,但最后不执行这个操作,只是把参数5初始化成客户区
尺寸大小;若取值CWnd::ReposExtra,则把参数5
值加到参数2表示
子窗口
客户区域,并执行窗口放置操作。
参数6表示传递给函数
可用窗口客户区
尺寸,如果空则使用窗口客户区尺寸。
如果执行layout操作
话,该函数
核心处理就是:
首先,调用::BeginDeferWindowPos初始化一个Windows内部
多窗口位置结构(Multiple-window - position structure)hDWP;
然后,让各个子窗口逐个调用::DeferWindowPos,更新hDWP。在调用::DeferWindowPos之前,要作一个确定子窗口大小
工作。这些工作通过给各个控制子窗口发送消息WM_SIZEPARENT来完成。
控制子窗口通过函数OnSizeParent响应WM_SIZEPARENT消息,先确定自己
尺寸,然后,如果需要进行窗口布置(WM_SIZEPARENT消息参数lParam包含了一个非空
HDWP结构(lpLayout->hDWP)),则OnSizeParent将调用AfxRepositionWindow函数计算本控制窗口
位置,结果保存到hDWP中。
在所有
控制窗口尺寸确定之后,剩下
留给窗口hWndLeftOver(如果存在
话)。确定了hWndLeftOver
大小之后,调用AfxRepositionWindow函数计算其位置,结果保存到hDWP中。
上面提到
函数AfxRepositionWindow间接调用了::DeferWindowPos。
最后,::EndDeferWindowPos,使用hDWP安排所有子窗口
位置和大小。
至于其他函数,如OnSizeparent、OnWindowPosChanging、CalcWindowRect,这里不作进一步
分析。
工具条、状态栏和边框窗口
接口
应用程序在状态栏中显示信息
MFC内部通过给边框窗口发送消息WM_SETMESSAGESTRING、WM_POPMESSAGESTRING
方式在状态栏中显示信息。这两个消息在afxpriv.h里头定义。
WM_SETMESSAGESTRING消息表示在状态栏中显示和某个ID对应
字符串信息或者指定
字符串信息,消息参数wParam指定了字符串资源ID,消息参数lParam指定了字符串指针,两个消息参数只有一个有用。一般,一个命令ID对应了一个字符串ID,对应
字符串是命令ID
说明。
消息WM_POPMESSAGESTRING用来重新设置状态栏。
这两个消息对应
消息处理函数分别是OnSetMessageString和OnPopMessageString,OnSetMessageString和OnPopMessageString分别实现如下:
OnSetMessageString LRESULT CFrameWnd::OnSetMessageString(WPARAM wParam, LPARAM lParam) { //最近一次被显示 消息字符串IDS(一个消息对应 字符串) UINT nIDLast = m_nIDLastMessage; m_nFlags &= ~WF_NOPOPMSG; //得到状态栏 CWnd* pMessageBar = GetMessageBar(); if (pMessageBar != NULL) { LPCTSTR lpsz = NULL; CString strMessage; //设置状态栏文本 if (lParam != 0) //指向一个字符串 { ASSERT(wParam == 0); // can't have both an ID and a string lpsz = (LPCTSTR)lParam; // set an explicit string } else if (wParam != 0)//一个字符串资源IDS { //打印预览时映射SC_CLOSE成AFX_IDS_PREVIEW_CLOSE; if (wParam == AFX_IDS_SCCLOSE && m_lpfnCloseProc != NULL) wParam = AFX_IDS_PREVIEW_CLOSE; //得到资源ID所标识 字符串 GetMessageString(wParam, strMessage); lpsz = strMessage; } //在状态栏中显示文本 pMessageBar->SetWindowText(lpsz); // 根据最近一次选择 消息更新状态条所属窗口 有关记录 CFrameWnd* pFrameWnd = pMessageBar->GetParentFrame(); if (pFrameWnd != NULL) { //记录最近一次显示 消息字符串 pFrameWnd->m_nIDLastMessage = (UINT)wParam; //记录最近一次Tracking 命令ID和字符串IDS pFrameWnd->m_nIDTracking = (UINT)wParam; } } m_nIDLastMessage = (UINT)wParam; // new ID (or 0) m_nIDTracking = (UINT)wParam; // so F1 on toolbar buttons work return nIDLast; }
OnSetMessageString函数直接或者从ID从字符串资源中得到字符串指针。如果是从ID得到字符串指针,则函数GetMessageString被调用。
和命令ID对应
字符串由两部分组成,前一部分用于在状态栏显示,后一部分用于Tooltip显示,分隔符号是“ ”。例如,字符串ID_APP_EXIT(对应“退出”菜单、按钮)是“Exit Application Exit”,当鼠标落在“退出”按钮上时,状态栏显示“Exit Application”,Tooltip显示“Exit”。根据这种格式,GetMessageString分离出第一部分
文本信息。至于第二部分
用途将在讨论Tooltip
章节将用到。
得到了字符串之后,OnSetMessageString调用状态栏
SetWindowText函数。SetWindowText导致消息WM_SETTEXT消息发送给状态栏,状态栏
消息处理函数OnSetText被调用,实际上等于调用了SetPaneText(0, lpsz),即在状态栏
第0格中显示字符串lpsz
信息。对于工具栏来说,SetWindowText可以认为是SetPaneText(0, lpsz)
简化版本。
顺便指出,pMessageBar->GetParentFrame()返回主边框窗口,即使pMessageBar指向漂浮
工具条。关于泊位和漂浮,见后面13.2.5节
描述。
关于OnSetText,其实现如下:
LRESULT CStatusBar::OnSetText(WPARAM, LPARAM lParam) { ASSERT_VALID(this); ASSERT(::IsWindow(m_hWnd)); int nIndex = CommandToIndex(0); //返回0 if (nIndex < 0) return -1; return SetPaneText(nIndex, (LPCTSTR)lParam) ? 0 : -1; } OnPopMessageString LRESULT CFrameWnd::OnPopMessageString(WPARAM wParam, LPARAM lParam) { //WF_NOPOPMSG表示边框窗口不处理WM_POPMESSAGESTRING if (m_nFlags & WF_NOPOPMSG) return 0; //调用OnSetMessageString return SendMessage(WM_SETMESSAGESTRING, wParam, lParam); }
一般,在清除状态栏消息时,发送WM_POPMESSAGESTRING,通过消息参数wParam指定一个字符串资源,其ID 为AFX_IDS_IDLEMESSAGE,对应
字符串是“Ready”。
状态栏显示菜单项
提示信息
状态栏
一个重要作用是显示菜单命令或者工具条按钮
提示信息。本节讨论如何显示菜单命令
提示信息,关于工具条按钮在这方面
讨论见后面13.2.4.4章节。
显示菜单命令
提示信息,就是每当一个菜单项被选中之后,在状态栏显示该菜单
功能、用法等信息。这些信息以字符串资源
形式保存,字符串ID对应于菜单项
命令ID。
所以,必须处理菜单选择消息WM_MENUSELECT。CFrameWnd实现了消息处理函数OnMenuSelect,其实现如下:
void CFrameWnd::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU /*hSysMenu*/) { CFrameWnd* pFrameWnd = GetTopLevelFrame(); ASSERT_VALID(pFrameWnd); //跟踪被选中 菜单项 if (nFlags == 0xFFFF) { //取消菜单操作 m_nFlags &= ~WF_NOPOPMSG; if (!pFrameWnd->m_bHelpMode) m_nIDTracking = AFX_IDS_IDLEMESSAGE; else m_nIDTracking = AFX_IDS_HELPMODEMESSAGE; //在状态栏显示 SendMessage(WM_SETMESSAGESTRING, (WPARAM)m_nIDTracking); ASSERT(m_nIDTracking == m_nIDLastMessage); // update right away CWnd* pWnd = GetMessageBar(); if (pWnd != NULL) pWnd->UpdateWindow(); } else { //选中分隔栏、Popup子菜单或者没有选中一个菜单项 if (nItemID == 0 || nFlags & (MF_SEPARATOR|MF_POPUP)) { // nothing should be displayed m_nIDTracking = 0; } else if (nItemID >= 0xF000 && nItemID < 0xF1F0) // max of 31 SC_s { //系统菜单 菜单项被选中 m_nIDTracking = ID_COMMAND_FROM_SC(nItemID); ASSERT(m_nIDTracking >= AFX_IDS_SCFIRST && m_nIDTracking < AFX_IDS_SCFIRST 31); } else if (nItemID >= AFX_IDM_FIRST_MDICHILD) { //如果选中 菜单项表示一个MDI子窗口 m_nIDTracking = AFX_IDS_MDICHILD; } else { //选中了一个菜单项 m_nIDTracking = nItemID; } pFrameWnd->m_nFlags |= WF_NOPOPMSG; } // when running in-place, it is necessary to cause a message to // be pumped through the queue. if (m_nIDTracking != m_nIDLastMessage && GetParent() != NULL) PostMessage(WM_KICKIDLE); }
OnMenuSelect
作用在于跟踪当前选中
菜单项,把菜单项对应
ID保存在CFrameWnd
成员变量m_nIDTracking中。
如果菜单项没有选中,或者选中
是一个子菜单,则设置nIDTracking为0。
如果选中
是系统菜单,则把系统菜单ID转换成一个对应
命令ID;保存该值到nIDTracking中。
如果选中
菜单是MDI子窗口创建时添加
(用来表示MDI子窗口),则转换菜单ID为AFX_IDS_MDICHILD,所有对应MDI子窗口
菜单项都使用AFX_IDS_MDICHILD,保存该值到nIDTracking中。
其他情况,就是选中菜单项
ID,把它保存到nIDTracking中。
跟踪被选择
菜单项并保存其ID在m_nIDTracking中,OnEnterIdle将用到m_nIDTracking。OnEnterIlde是消息WM_ENTERIDLE
处理函数,CFrameWnd
实现如下。
void CFrameWnd::OnEnterIdle(UINT nWhy, CWnd* pWho) { CWnd::OnEnterIdle(nWhy, pWho); //若不是因为菜单选择进入该函数 //或者当前跟踪到 菜单项ID是最近一次处理 ,则返回 if (nWhy != MSGF_MENU || m_nIDTracking == m_nIDLastMessage) return; //将发送消息WM_SETMESSAGETEXT //在状态栏显示m_nIDTracking对应 字符串 SetMessageText(m_nIDTracking); ASSERT(m_nIDTracking == m_nIDLastMessage); }
当一个对话框或者菜单被显示
时候,Windows发送WM_ENTERIDLE消息。消息参数wParam取值为MSGF_DIALOGBOX或者MSGF_MENU。前者表示显示对话框时发送该消息,这时消息参数lParam表示对话框
句柄;后者表示显示菜单时发送该消息,这时消息参数lParam表示菜单
句柄。
经过消息映射,wParam
值传递给OnEnterIdle
参数nWhy,参数lParam
值传给参数who。如果参数1取值为MSGF_MENU,并且OnEnterIdle最近一次在菜单显示被调用时
菜单ID不同于这一次,则调用SetMessageText在状态栏显示对应ID命令
字符串,并且记录当前菜单ID到变量m_nIDTracking中(见消息处理函数OnSetMessageText)。
这样,在菜单选择期间,用户选择
菜单项ID被OnMenuSelect记录,在消息WM_ENTERIDLE处理时在状态栏显示ID命令
提示。
控制条
消息分发处理
工具条(包括对话框工具条)是一个子窗口,它们可以响应各种消息。如果按标准
Windows消息和命令消息
分发途径,一些消息不能送到拥有工具条
边框窗口,因为这些消息都将被工具条(对话框工具条)处理掉。所以,CControlBar覆盖了虚拟函数PreTranslateMessage和WindowProc以便实现特定
消息分发路径。
WindowProc
CControlBar
WindowProc实现了如下
消息分发路径:
用户对控制条
输入消息或者分发给CControlBar及其派生类处理,或者送给拥有控制条
边框窗口处理,或者送给Windows控制“窗口类”
窗口过程处理。
WindowProc
实现如下:
LRESULT CControlBar::WindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { ASSERT_VALID(this); LRESULT lResult; switch (nMsg) { //本函数处理以下消息 case WM_NOTIFY: case WM_COMMAND: case WM_DRAWITEM: case WM_MEASUREITEM: case WM_DELETEITEM: case WM_COMPAREITEM: case WM_VKEYTOITEM: case WM_CHARTOITEM: //首先,工具条处理上述消息,如果没有处理,则接着给所属边框窗口处理 if (OnWndMsg(nMsg, wParam, lParam, &lResult)) return lResult; else return GetOwner()->SendMessage(nMsg, wParam, lParam); } } // 最后,给基类CWnd,按缺省方式处理 lResult = CWnd::WindowProc(nMsg, wParam, lParam); return lResult; }
从上述实现可以看出,对于case范围内
一些消息,如WM_COMMAND、WM_NOTIFY等,控制条如果不能处理,则优先分发给其父窗口(边框窗口)处理,然后进入缺省处理,对于其他消息直接调用基类CWnd
实现(缺省处理)。基于这样
机制,可以把用户对工具条按钮或者对话框工具条内控制
操作解释成相应
命令消息,执行对应
命令处理。
对于工具条,当用户选中某个按钮时(鼠标左键弹起,消息是WM_LBUTTONUP),工具条窗口接收到WM_LBUTTONUP消息,该消息不在CControlBar::WindowProc特别处理
消息范围内,于是进行缺省处理,也就是说,把该消息派发给控制条对应
Windows控制
窗口过程处理(即被MFC统一窗口过程所取代
原窗口过程),该窗口过程则把该消息转换成一条命令消息WM_COMMAND,命令ID就是选中按钮对应
ID,然后,发送该命令消息给拥有工具条
边框窗口,导致相应
命令处理函数被调用。
对于对话框工具条,当工具条
某个控制子窗口被选中之后,则产生一条命令通知消息WM_COMMAND,wParam是控制子窗口
ID。CControlBar::WindowProc处理该消息。WindowProc首先调用OnWndMsg把消息发送给对话框工具条或者对话框工具条
基类处理,如果没有被处理
话,则OnWndMsg返回FALSE。接着,WindowPoc把命令消息传递给父窗口(边框窗口)处理。由于工具条
控制窗口
ID对应
是命令ID,所以,这条WM_COMMAND消息传递给边框窗口时,被解释成一个命令消息,相应
命令处理函数被调用。
PreTranslateMessage
CControlBar覆盖PreTranslateMessage函数,主要是为了在光标落在工具条按钮上时显示FLYBY信息,并且让对话框工具条过滤Dialog消息。
BOOL CControlBar::PreTranslateMessage(MSG* pMsg) { ASSERT_VALID(this); ASSERT(m_hWnd != NULL); //过滤Tooltip消息 if (CWnd::PreTranslateMessage(pMsg)) return TRUE; //是Tooltip消息,已经被处理 UINT message = pMsg->message; //控制条 父窗口,对工具条和对话框工具条,总是创建它 边框窗口 CWnd* pOwner = GetOwner(); //必要 话,在状态条显示工具栏按钮 提示 if (((m_dwStyle & CBRS_FLYBY) || message == WM_LBUTTONDOWN || message == WM_LBUTTONUP) && ((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) || (message >= WM_NCMOUSEFIRST && message <= WM_NCMOUSELAST))) { _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); //确认鼠标在工具栏 哪个按钮上 CPoint point = pMsg->pt; ScreenToClient(&point); TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO)); ti.cbSize = sizeof(TOOLINFO); int nHit = OnToolHitTest(point, &ti); if (ti.lpszText != LPSTR_TEXTCALLBACK) free(ti.lpszText); BOOL bNotButton = message == WM_LBUTTONDOWN && (ti.uFlags & TTF_NOTBUTTON); if (message != WM_LBUTTONDOWN && GetKeyState(VK_LBUTTON) < 0) nHit = pThreadState->m_nLastStatus; //更新状态栏 提示信息 if (nHit < 0 || bNotButton) { if (GetKeyState(VK_LBUTTON) >= 0 || bNotButton) { SetStatusText(-1); KillTimer(ID_TIMER_CHECK); } } else { if (message == WM_LBUTTONUP) { SetStatusText(-1); ResetTimer(ID_TIMER_CHECK, 200); } else { if ((m_nStateFlags & statusSet) || GetKeyState(VK_LBUTTON) < 0) SetStatusText(nHit); else if (nHit != pThreadState->m_nLastStatus) ResetTimer(ID_TIMER_WAIT, 300); } } pThreadState->m_nLastStatus = nHit; } // don't translate dialog messages when in Shift F1 help mode CFrameWnd* pFrameWnd = GetTopLevelFrame(); if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode) return FALSE; //在IsDialogMessage之前调用边框窗口 PreTranslateMessage, //给边框窗口一个处理快捷键 机会 while (pOwner != NULL) { // allow owner & frames to translate before IsDialogMessage does if (pOwner->PreTranslateMessage(pMsg)) return TRUE; // try parent frames until there are no parent frames pOwner = pOwner->GetParentFrame(); } //过滤给对话框 消息和来自子窗口 消息 return PreTranslateInput(pMsg); }
函数PreTranslateMessage主要是针对工具栏
,用来处理工具栏
CBRS_FLYBY特征。
对于对话框工具栏,也可以有CBRS_FLYBY特征。但在这种情况下,还需要把一些用户键盘输入解释成对话框消息。为了防止快捷键被解释成对话框消息,在调用函数PreTranslateInput之前,必须调用所有父边框窗口
PreTranslateMessage,给父边框窗口一个机会处理用户
输入消息,判断快捷键是否被按下。
关于Tooltip和本PreTranslateMessage函数处理Tooltip
详细解释见下一节
讨论。
Tooltip
工具条(或对话框工具条)如果指定了CBRS_TOOLTIPS风格(创建时指定或者创建后用SetBarStyle设置),则当鼠标落在某个按钮上(或者对话框
子控制窗口)时,在鼠标附近弹出一个文本框──Tooltip提示窗口。
如果还指定了CBRS_FLYBY风格,则还在状态栏显示和按钮(或子控制窗口)ID对应
字符串信息。当然,鼠标左键在某个按钮(或子控制窗口)按下时,也要在状态栏显示按钮
提示信息,当左键弹起时,则重置状态栏
状态。
如前所述,Tooltip窗口是Windows控制窗口。MFC使用了CToolTipCtrl类封装Tooltip
HWND窗口。在一个线程
生存期间,至多拥有一个Tooltip窗口,该窗口对象
指针保存在线程状态
成员变量m_pToolTip中。线程状态类AFX_THREAD_STATE
析构函数如果检测到m_pToolTip,则销毁MFC窗口对象和相应
Windows窗口对象。
CWnd对Tooltip消息
预处理
为了支持Tooltip显示,CWnd提供了以下函数:EnableTooltip,CancelTooltip,PreTranslateMessage,FilterTooltipMessage,OnToolHitTest。
EnableTooltip设置CBRS_TOOLTIP风格,相反CancelTootip取消这种风格。
PreTranslateMessage调用了FilterTooltipMessage过滤Tooltip消息。
OnToolHitTest是一个由CWnd定义
虚拟函数。CToolBar通过覆盖该函数,来检测对话框工具栏
控制子窗口或者工具栏按钮是否被选中、哪个被选中。
CWnd
PreTranslateMessage在4.5节讨论过,它
实现如下:
BOOL CWnd::PreTranslateMessage(MSG* pMsg) { //处理Tooltip消息 AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE(); if (pModuleState->m_pfnFilterToolTipMessage != NULL) //导致调用FilterTooltipMessage (*pModuleState->m_pfnFilterToolTipMessage)(pMsg, this); //不是Tooltip消息 return FALSE; }
至于为什么MFC在模块状态中保存一个处理Tooltip消息
函数地址,通过该函数调用FilterTooltipMessage,是因为Tooltip窗口是模块线程局部有效
。
FilterTooltipMessage检测是否是Tooltip消息。如果是,在必要时创建一个CTooltipCtrl对象和对应
HWND,调用OnToolHitTest确定被选中
按钮或者控制
ID,接着弹出Tooltip窗口。
其他函数和CTooltipCtrl这里不作详细论述了。
处理TTN_NEEDTEXT通知消息
Tooltip窗口在弹出之前,它给工具条(或者对话框工具栏)
父窗口发送通知消息TTN_NEEDTEXT,请求得到要显示
文本。
CFrameWnd类处理了TTN_NEEDTEXT通知消息,消息处理函数是OnToolTipText。
消息映射
定义:
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
这里,使用了扩展消息映射宏把子窗口ID在0和0xFFFF之间
控制条窗口
通知消息TTN_NEEDTEXTA和TTN_NEEDTEXTW映射到函数OnToolTipText。
消息映射
实现:
BOOL CFrameWnd::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult) { ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW); //让上一层 边框窗口优先处理该消息 if (GetRoutingFrame() != NULL) return FALSE; //分ANSI and UNICODE两个处理版本 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; TCHAR szFullText[256]; CString strTipText; UINT nID = pNMHDR->idFrom; //如果idFrom是一个子窗口,则得到其ID。 if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) || pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND)) { //idFrom是工具条 句柄 nID = _AfxGetDlgCtrlID((HWND)nID); } if (nID != 0) //若是0,为一分隔栏,不是按钮 { //得到nID对应 字符串 AfxLoadString(nID, szFullText); //从上面得到 字符串中取出Tooltip使用 文本 AfxExtractSubString(strTipText, szFullText, 1, ' '); } //复制分离出 文本 #ifndef _UNICODE if (pNMHDR->code == TTN_NEEDTEXTA) lstrcpyn(pTTTA->szText, strTipText, _countof(pTTTA->szText)); else _mbstowcsz(pTTTW->szText, strTipText, _countof(pTTTW->szText)); #else if (pNMHDR->code == TTN_NEEDTEXTA) _wcstombsz(pTTTA->szText, strTipText, _countof(pTTTA->szText)); else lstrcpyn(pTTTW->szText, strTipText, _countof(pTTTW->szText)); #endif *pResult = 0; //显示Tooltip窗口 ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE); return TRUE; //消息处理完毕 }
OnToolTipText是一个扩展映射宏定义
消息处理函数,所以有一个UINT参数并且返回BOOL类型
值。不过,由于第二个参数(NMHDR)
idFrom域包含了有关信息,所以第一个UINT类型
参数没有用上。
OnToolTipText也是一个处理通知消息
例子。其中,通知参数wParam
结构如4.4.4.2节所述,具体如下:
typedef struct { NMHDR hdr; //WM_NOTIFY消息要求 头 LPTSTR lpszText; //接收工具条按钮对应文本 缓冲区 WCHAR szText[80]; //接收Tooltip显示文本 缓冲区 HINSTANCE hinst; //包含了szText 实例句柄 UINT uflags; //标识了NMHDR idFrom成员 意义 } TOOLTIPTEXT, FAR *LPTOOLTIPTEXT;
uflags如果等于TTF_IDISHWND,则表示通知消息来自对话框工具条
一个子窗口,而不是包含工具条按钮。
OnToolTipText根据子窗口ID或者工具条按钮对应
ID,得到字符串ID。如前所述,字符串ID由两部分组成,第二部分用于Tooltip显示,分隔符号是“ ”。根据这种格式OnToolTipText分离出Tooltip文本。
得到了Tooltip文本之后,可以有三种方法返回文本信息:把文本信息复制到szText缓冲区;把文本地址复制到lpszText;复制字符串资源
ID到lpszText、复制包含资源
实例句柄到hint。本函数采用了第一种方法。
在得到了返回
Tooltip文本之后,该文本在Tooltip窗口中被显示出来。
其他
OnToolHist等函数
实现不作详细
解释了。下面,讨论CBRS_FLYBY风格
实现。
CBRS_FLYBY风格
实现
CBRS_FLYBY是MFC提供
特征。当鼠标落在工具条按钮(或者对话框工具条
子窗口)上且稳定300ms后,在状态栏显示对应
提示信息。如果选中某个按钮或者子窗口(鼠标左键按下),则在相应命令消息处理之前在状态栏显示有关提示信息,之后(鼠标左键弹起),重新设置状态栏
状态信息。
为了支持这种特征,CControlBar覆盖虚拟函数PreTranslateMessage来处理和CBRS_FLYBY相关
消息,该函数前面已经讨论过,这里解释它如何处理CBRS_FLYBY特征。
如果同时具备
条件1:控制条具有CBRS_FLYBY特征或者当前消息是WM_LBUTTONUP或者WM_LBUTTONDOWN。
条件2:当前消息是鼠标消息(在WM_MOUSEFIRST和WM_MOUSELAST之间或者在WM_NCMOUSEFIRST和WM_NCMOUSELAST之间)。
则进行FLYBY处理。
首先,调用OnToolHitTest测试用户是否选中了工具条
按钮或者子窗口;
如果没有按钮或者子窗口被选中,则重新设置状态栏
状态,取消曾经设置
Check定时器。重置状态栏
状态时调用了SetStatusText(int nHit)函数,它是CControlBar内部使用
函 数,若nHit等于-1,它向父窗口发送WM_POPMESSAGETEXT,消息参数是AFX_IDS_IDLEMESSAGE,结果导致状态栏显示 “Ready”字样;否则发送WM_SETMESSAGETEXT消息,wParm设置为nHit,结果导致在状态栏显示ID为nHit
字符串。
如果有按钮或者子窗口被选中,若左键弹起,则重新设置状态栏信息,取消Wait定时器,并重新设置Check定时器,定时是200ms;若左键按下,则在状态栏显示消息ID对应
提示信息;若是其他鼠标消息,如果当前鼠标所在按钮(子窗口)不同于最近一次,则取消Check定时器,重新设置Wait定时器,定时300毫秒。
CControlBar覆盖了消息处理函数OnTimer,在指定时间之后,检查鼠标位置,如果鼠标还在某个按钮或者子窗口上,则在状态条显示提示信 息。Wait定时器在等待之后准备在状态条显示信息,触发一次后被取消;Check定时器在等待之后,判断是否需要取消状态条当前显示
信息,重新设置状态条,若这样
话,同时也取消Check定时器。
注意,这些鼠标消息被处理之后,并没有终止,它们将继续被发送给控制条
窗口过程处理。
至此,CBRS_FLYBY特征
支持实现描述完毕。
禁止和允许
在MFC下,工具条、状态条还有一个重要
特征,就是自动地根据条件禁止或者允许使用某个按钮、窗格等。在4.4.5节命令消息
处理中,曾详细讨论了其实现原理,现在,详细地分析所涉及函数是如何实现
。有关
消息处理函数和虚拟函数如下。
处理WM_INITIALUPDATE消息
OnInitialUpdate;
处理WM_IDLEUPDATECMDUI消息
OnIdleUpdateCmdUI;
虚拟函数OnUpdateCmdUI。
回顾5.3.3.5节,在边框窗口
创建之后,给所有
子窗口发送初始化消息,控制子窗口用OnInitialUpdate响应它,调用OnIdleUpdateCmdUI完成状态
初始化。
OnIdleUpdateCmdUI还在IDLE处理时进行状态
更新处理,它生成用于处理状态更新消息
命令目标pTarget,然后调用虚拟函数OnUpdateCmdUI(pTarget,…)来更新工具栏或者状态栏
状态。
CControlBar
子类都实现了自己
OnUpdateCmdUI函数,用该函数生成适当
CCmdUI对象state,然后调用CCmdUI
DoUpdate(pTarget,…)给pTarget所指对象发送状态更新消息。为了完成具体
状态更新,从CCmdUI派生出CToolCmdUI和CStatusCCmUI,它们实现了自己
Enable、SetCheck等等。
初始化控制窗口
CControlBar使用OnInitialUpdate消息处理函数初始化控制窗口
状态。
void CControlBar::OnInitialUpdate() { //在窗口显示之前,更新状态 OnIdleUpdateCmdUI(TRUE, 0L); }
CControlBar实现了OnInitialUpdate函数,通过它来处理WM_INITIALUPDATE消息。各个子类不必覆盖该消息处理函数。
处理Idle消息更新工具条状态
CControlBar使用OnIdleUpdateCmdUI消息处理函数处理IDLE消息。
LRESULT CControlBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM) { // handle delay hide/show BOOL bVis = GetStyle() & WS_VISIBLE; UINT swpFlags = 0; if ((m_nStateFlags & delayHide) && bVis) swpFlags = SWP_HIDEWINDOW; else if ((m_nStateFlags & delayShow) && !bVis) swpFlags = SWP_SHOWWINDOW; m_nStateFlags &= ~(delayShow|delayHide); if (swpFlags != 0) { SetWindowPos(NULL, 0, 0, 0, 0, swpFlags| SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE); } // the style must be visible and if it is docked // the dockbar style must also be visible if ((GetStyle() & WS_VISIBLE) && (m_pDockBar == NULL || (m_pDockBar->GetStyle() & WS_VISIBLE))) { //得到父边框窗口,状态更新消息将发送给它 CFrameWnd* pTarget = (CFrameWnd*)GetOwner(); if (pTarget == NULL || !pTarget->IsFrameWnd()) pTarget = GetParentFrame(); if (pTarget != NULL) OnUpdateCmdUI(pTarget, (BOOL)wParam); } return 0L; }
OnIdleUpdateCmdUI或者在初始化时被OnInitialUpdate调用,或者作为消息处理函数来处理WM_IDLEUPDATECMDUI消息。
CControlBar实现了OnIdleUpdateCmdUI函数,把具体
用户界面更新动作委托给虚拟函数OnUpdateCmdUI完成。
由于各个用户界面
特殊性,所以CControlBar本身没有实现OnUpdateCmdUI,而是留给各个派生类去实现。例如,CToolBar覆盖了OnUpdateCmdUI,其实现如下:
void CToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler) { //定义一个CCmdUI对象,CToolCmdUI派生于CCmdUI CToolCmdUI state; //给CCmdUI 各个成员赋值 state.m_pOther = this; //得到总 按钮数目 state.m_nIndexMax = (UINT)DefWindowProc(TB_BUTTONCOUNT, 0, 0); //逐个按钮进行状态更新 for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex ) { //获取按钮状态信息 TBBUTTON button; _GetButton(state.m_nIndex, &button); //得到按钮 ID state.m_nID = button.idCommand; // ignore separators if (!(button.fsStyle & TBSTYLE_SEP)) { //优先让CToolBar对象处理状态更新消息 if (CWnd::OnCmdMsg(state.m_nID, CN_UPDATE_COMMAND_UI, &state, NULL)) continue;//处理了更新消息,更新下一个按钮 //CToolBar没有处理,将发送给pTarget处理状态更新消息 //第二个参数bDisableIfNoHndler往下传 state.DoUpdate(pTarget, bDisableIfNoHndler); } } //更新加到控制条中 对话框控制 状态 UpdateDialogControls(pTarget, bDisableIfNoHndler); }
CToolBar
OnUpdateCmdUI函数完成工具条按钮
状态更新。它接受两个参数,参数1表示接收状态更新命令消息
对象,由CControlBar
函数OnIdleUpdateCmdUI传递过来,一般是边框窗口对象;参数2表示如果某条命令消息没有处理函数时,对应
用户接口对象是否被禁止。
OnUpdateCmdUI通过发送状态更新通知消息,逐个更新按钮
状态。更新消息首先让工具条对象处理,如果没有处理
话,送给边框窗口对象处理,导致状态更新命令消息
处理函数被调用,参见4.4.5节。
CStatusBar
OnUpdateCmdUI类似于此。
CDialogBar
OnUpdateCmdUI则调用了虚拟函数UpdateDialogControls来进行状态更新,CWnd提供了该函数
实现,过程类似于CToolBar
函数OnUpdateCmdUI。
菜单项
自动更新
那么,菜单项
自动更新如何实现
呢?OnInitMenuPopup在菜单项状态
自动更新中曾经被提到,其实现如下:
void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT, BOOL bSysMenu) { AfxCancelModes(m_hWnd); if (bSysMenu) return; // don't support system menu ASSERT(pMenu != NULL); // check the enabled state of various menu items CCmdUI state; state.m_pMenu = pMenu; ASSERT(state.m_pOther == NULL); ASSERT(state.m_pParentMenu == NULL); //判断菜单是否在顶层菜单(top level menu)中弹出,如果这样 //则设置m_pParentMenu指向顶层菜单,否则m_pParentMenu //为空,表示它是一个二级弹出菜单 HMENU hParentMenu; //是否是浮动式 弹出菜单(floating pop up menu) if (AfxGetThreadState()->m_hTrackingMenu == pMenu->m_hMenu) state.m_pParentMenu = pMenu; // parent == child for tracking popup else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)// { CWnd* pParent = GetTopLevelParent(); // child windows don't have menus -- need to go to the top! //得到顶层窗口 菜单 if (pParent != NULL && (hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL) { int nIndexMax = ::GetMenuItemCount(hParentMenu); //确定顶层窗口 菜单是否包含本菜单项 for (int nIndex = 0; nIndex < nIndexMax; nIndex ) { if (::GetSubMenu(hParentMenu, nIndex) == pMenu->m_hMenu) { //顶层窗口菜单是本菜单 父菜单 state.m_pParentMenu = CMenu::FromHandle(hParentMenu); break; } } } } //本菜单 菜单项(menu item)数量 state.m_nIndexMax = pMenu->GetMenuItemCount(); //对所有菜单项逐个进行状态更新 for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex ) { state.m_nID = pMenu->GetMenuItemID(state.m_nIndex); if (state.m_nID == 0) continue; // menu separator or invalid cmd - ignore it ASSERT(state.m_pOther == NULL); ASSERT(state.m_pMenu != NULL); if (state.m_nID == (UINT)-1) { // 可能是一个popup菜单,得到其第一个子菜单项目 state.m_pSubMenu = pMenu->GetSubMenu(state.m_nIndex); if (state.m_pSubMenu == NULL || (state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 || state.m_nID == (UINT)-1) { continue; // 找不到popup菜单 子菜单项 } //popup菜单不会被自动 禁止 state.DoUpdate(this, FALSE); } else { //正常 菜单项,若边框窗口 m_bAutoMenuEnable设置为 //TURE且菜单项非系统菜单,则自动enable/disable该菜单项 state.m_pSubMenu = NULL; state.DoUpdate(this, m_bAutoMenuEnable && state.m_nID < 0xF000); } //经过菜单状态 更新处理,可能增加或删除了一些菜单项 UINT nCount = pMenu->GetMenuItemCount(); if (nCount < state.m_nIndexMax) { state.m_nIndex -= (state.m_nIndexMax - nCount); while (state.m_nIndex < nCount && pMenu->GetMenuItemID(state.m_nIndex) == state.m_nID) { state.m_nIndex ; } } state.m_nIndexMax = nCount; } }
菜单弹出之前,发送WM_INITMENUPOPUP消息,OnInitMenuPopup消息处理函数被调用,逐个更新菜单项目(menu item)
状态。程序员可以处理它们对应
状态更新消息,禁止/允许菜单项目被使用(disable/enable),在菜单项目上打钩或者取消(checked/unchecked),等等。
显示或者隐藏工具栏和状态栏
这里讨论显示或者隐藏工具栏、状态栏
操作,以及工具栏、状态栏被显示/隐藏时,相关
两个菜单项ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR
状态更新。这两个菜单命令及对应
状态更新命令是标准命令消息所包含
。MFC边框窗口实现了菜单命令消息
处理和菜单项状态
更新。
CFrameWnd提供了OnBarCheck来响应与ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR菜单项对应
命令。
消息映射:
ON_COMMAND_EX(ID_VIEW_STATUS_BAR, OnBarCheck)
ON_COMMAND_EX(ID_VIEW_TOOLBAR, OnBarCheck)
这里,使用了扩展命令消息映射宏把ID_VIEW_STATUS_BAR和ID_VIEW_TOOLBAR命令映射给同一个函数OnBarCheck处理。
OnBarCheck函数
实现:
BOOL CFrameWnd::OnBarCheck(UINT nID) { ASSERT(ID_VIEW_STATUS_BAR == AFX_IDW_STATUS_BAR); ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR); //得到工具条或者状态条 CControlBar* pBar = GetControlBar(nID); if (pBar != NULL) { //若控制条可见,则隐藏它;否则,显示它 ShowControlBar(pBar, (pBar->GetStyle() & WS_VISIBLE) == 0, FALSE); //处理完毕 return TRUE; } //可以让下一个命令目标继续处理 return FALSE; }
由于是扩展映射宏定义
消息处理函数,所以OnBarCheck函数有一个UINT类型
参数和一个BOOL返回值。
当用户从“View”菜单选择打了钩
“Toolbar”时,消息处理函数OnBarCheck被调用,参数就是菜单项
ID号ID_VIEW_TOOLBAR,它等于工具条
子窗口IDAFX_IDW_TOOLBAR。处理结果,工具条被隐藏;当再次选择该菜单项则工具条被显示。
处理状态条
过程类似于工具条
处理。
ShowControlBar是CFrameWnd
成员函数,参数1表示控制条对象指针,参数2表示显示(TRUE)或者隐藏(FALSE),参数3表示是立即显示(FALSE)或者延迟显示(TRUE)。
如果工具条或者状态条被隐藏,则相应
菜单项ID_VIEW_STATUS_BAR 或者ID_VIEW_TOOLBAR 变成uncheked(菜单项被标记为没有选择),否则,checked(菜单项被标记选择)。CFrameWnd实现了这两个菜单项
状态更新处理,列举其中一个如下:
声明处理ID_VIEW_TOOLBAR
状态更新消息:
ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR, OnUpdateControlBarMenu)
函数
实现:
void CFrameWnd::OnUpdateControlBarMenu(CCmdUI* pCmdUI) { ASSERT(ID_VIEW_STATUS_BAR == AFX_IDW_STATUS_BAR); ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR); CControlBar* pBar = GetControlBar(pCmdUI->m_nID); //存在工具栏 if (pBar != NULL) { //工具条窗口被显示则checked,被隐藏则uncheked pCmdUI->SetCheck((pBar->GetStyle() & WS_VISIBLE) != 0); return; } pCmdUI->ContinueRouting(); }
GetControlBar是CFrameWnd
成员函数,用来返回边框窗口
指定ID
控制条对象(指定ID是控制条
子窗口ID)。
泊位和漂浮
工具条可以泊位在边框窗口
任一边(上、下、左、右),或者漂浮在屏幕上
任何地方。
实现泊位
方法
首先,边框窗口调用CFrameWnd::EnableDocking函数使控制条泊位在边框窗口中有效,指明在边框窗口
哪边接受泊位。如果想在任何边都可以泊位,则使用参数CBRS_ALIGN_ANY。
然后,工具条调用ControlBar::EnableDocking使泊位对工具条有效,如果在调用ControlBar::EnableDocking时指定
泊位目
边和边框窗口能够泊位
边不符合,那么工具条不能泊位,它将漂浮。
最后,边框窗口调用CFrameWnd::DockControlBar泊位工具条。
泊位后形成窗口层次关系
边框窗口、泊位条、工具条
包含关系如下:
边框窗口
泊位条1
工具条1
工具条2
…
泊位条2
…
边框窗口包含1到4个泊位条子窗口,每个泊位条包含若干个控制条子窗口。
泊位
实现
CFrameWnd::EnableDocking指定哪边接受泊位,则为泊位准备一个泊位条。泊位条用CDockBar描述,派生于CControlBar。如果指定任何边都可以泊位,则创建四个CDockBar对象和对应
HWND窗口。然后,调用ControlBar::EnableDocking在对应
泊位条内安置工具条。
MFC设计了CDockBar类和CFrameWnd
一些函数来实现泊位,具体代码实现在此不作详细讨论。
实现漂浮工具条
方法:
边框窗口调用FloatControlBar实现工具条
漂浮。
漂浮
实现:
首先,创建一个微型漂浮边框窗口,该边框窗口有一个泊位条。
然后,在微型边框窗口
泊位条内放置工具条。
MFC设计了微型边框类CMiniFrameWnd,在此基础上派生出微型泊位边框窗口类CMiniDockFrameWnd。CMiniDockFrameWnd增加了一个CDockBar类型成员变量m_wndDockBar,即泊位条。
在CMiniDockFrameWnd对象被创建时,创建泊位条m_wndDockBar。泊位条m_wndDockBar
父窗口如同CMiniDockFrameWnd
父窗口一样,是调用FloatControlBar
边框窗口,而不是微型泊位边框窗口。微型边框窗口和泊位条创建完成之后,调用ControlBar::DockControlBar泊位工具条在CMiniDockFrameWnd窗口。
相关文章推荐
- MFC工具条和状态栏,内部实现原理详细分析
- MFC教程(13)-- MFC工具条和状态栏(1)
- MFC 教程【13_MFC工具条和状态栏 】
- MFC的工具条和状态栏
- MFC工具条和状态栏
- MFC入门(四) 工具条和状态栏
- MFC的工具条和状态栏
- MFC工具条和状态栏
- 精华教程MFC入门系列(四)添加工具条和状态栏
- 第十三讲 MFC工具条和状态栏
- MFC工具条和状态栏
- MFC教程-MFC工具条和状态栏
- MFC教程(13)-- MFC工具条和状态栏(1)
- 13_MFC工具条和状态栏
- MFC状态栏的编程
- 给基于对话框的MFC程序添加状态栏并实时显示时间
- MFC状态栏创建和设计
- VS2013/MFC编程入门之三十五(状态栏的使用详解)
- VC6.0 MFC 隐藏任务栏图标和状态栏图标
- MFC学习笔记之七————工具栏编程与状态栏编程