barcore.cpp (CControlBar)
dockcont.cpp (CDockContext)
bardock.cpp (CDockBar)
winfrm2.cpp (CFrameWnd)
bartool.cpp (CToolBar)
MFC的Frame窗口除了支持Doc-View框架外,还提供了一个重要特性,就是Dock,即:我们可以从CControlBar派生各种Bar,通过CFrameWnd所提供的Dock方法,可以将该Bar Dock到四个方向,或者让其Floating(通过另一个特殊的Frame窗口来支持,后面将会解释)。在MFC提供的默认SDI或者MDI框架代码中,我们能看到类似如下的代码:
其中,分别表示起如下作用:
1.
2.
3.
实际上,CFrameWnd提供的Dock功能是由其内部的四个Dock bar所提供的(分别对应四个边)。
1.1
先说说CControlBar类,它的定义位于afxext.h中,实现位于barcore.cpp中。在MFC中说到Bar,一般都是指继承该类而来。既然CControlBar是依赖于CFrameWnd,它的m_pDockSite方法即用来保存其所在的Frame窗口对象的指针,这种关联在CControlBar的OnCreate方法中完成,而且Frame窗口也借由它的AddControlBar方法完成对ControlBar的关联(可见这里是双向关联)。当然,在CContolBar销毁(OnDestroy)时,将调用Frame窗口的RemoveControlBar,解除关联。
它的主要功能反映在下面的方法中:
EnableDocking:该方法并不在barcore.cpp中,而是在bardock.cpp中。
下面是两个针对Control Bar尺寸风格进行具体计算的方法,虚方法。这两个方法和对应的风格主要是在CToolBar中应用得较多。该方法的调用入口请参见下面的CFrameWnd的描述:
CalcFixedLayout:对应CBRS_SIZE_FIXED。该风格表现为:无论是停靠,还是浮动状态,其形状都不可改变。
CalcDynamicLayout:对应CBRS_SIZE_DYNAMIC。该风格表现为:如果是浮动状态,那么当用户拖动工具栏的边缘时,工具栏在需要的地方“换行”以改变形状。如果是停靠状态,则不可以改变形状。
消息响应方法:
1.OnPaint
此时,主要由DoPaint虚方法完成绘制。默认包含两个步骤,即:绘制Bar的边缘DrawBorders和绘制Bar的手柄DrawGripper。值得注意的是,CToolBar具有自己的OnPaint方法,它只是处理了延迟layout的情形,然后调用缺省的消息处理(DefWindowProc)。我们知道系统在处理WM_PAINT消息时,将会发送WM_NCPAINT和WM_ERASEBKGND两个消息(在当前场景下,background erase消息是在EraseNonClient方法中发出的)。因此,实际上,CToolBar是在OnNcPaint方法中完成绘制,不过它并没有自己的实现,该方法中仅仅是调用CControlBar的EraseNonClient方法。在EraseNonClient方法中,由于是要处理非客户区,所以使用了CWindowDC,而且,剪切掉了客户区域,只留下非客户区域供绘制(使用CDC的ExcludeClipRect方法,具体请参见后续文章《DC和绘图》),具体绘制的内容仍然是Border和Gripper(也是调用上述两个方法)。
2. OnWindowPosChanging
对消息WM_WINDOWPOSCHANGING的响应,此时,根据CBRS_BORDER系列风格,将right,或bottom边缘处理为无效区域。
该消息的产生应当区别停靠和浮动状态:
a. 停靠状态:此时,CControlBar为子窗口,当移动Bar时,其position发生了变化,故会产生该消息;
b. 浮动状态:此时,CControlBar同样为子窗口。但你所移动的为其外面的Frame窗口,所以如果仅仅是移动,将不会产生该消息。只有改边Frame窗口尺寸时,Bar尺寸和position也被更改,才会产生该消息。
这里默认并没有调用基类的方法,而是直接调用了DefWindowProc API。原因是:基类将会调用具体窗口的消息处理函数,即:CWnd中的m_pfnSuper成员,但是某些控件并不能正确处理WM_WINDOWPOSCHANGING消息,比如:带有TBSTYLE_FLAT 风格的CToolBar。所以,直接采用了上述API,它使用系统缺省的消息处理函数,处理所有未处理的消息。
说到该消息,不能不提到下面三个方法:
BeginDeferWindowPos、DeferWindowPos、EndDeferWindowPos。它们常用来处理多个窗口协同的尺寸变化。它实际上是同时记录了多个窗口的位置信息(使用HDWP
3. OnSizeParent
响应父窗口尺寸变化。该消息为MFC私有消息,由父窗口发送到子窗口。在Dock体系中,父窗口就是该Control Bar所在的Frame窗口。
4. OnLButtonDown
鼠标按下,调用CWnd的OnToolHitTest方法判断是否点击为空白区域(没有点击到任何子窗口)时,准备拖动Bar。此时,将委托给其内部的CDockContext实例的StartDrag方法。那么什么时候结束拖动呢? CDockContext帮我们处理了拖动到结束的所有绘画细节。详细内容请看“CDockContext类”一节。
5. OnLButtonDblClk
鼠标双击,同样也是处于空白区域时,在浮动和停靠状态间执行切换。此时,调用CDockContext的ToggleDocking方法。
6. OnMouseActivate
如果处于浮动状态,那么将激活顶层top-level窗口,通常就是应用的主窗口。这里使用了CWnd提供的ActivateTopParent方法。
1.2 CFrameWnd类
该类提供了如下方法用来封装ControlBar的Dock功能:DockControlBar和FloatControlBar。其中,
1. DockControlBar:初始化Frame窗口的四个dock bar
2. FloatControlBar要实现浮动,那么包围在ControlBar之外的一定是一个不同于当前Frame窗口的另一个窗口。MFC提供了CMiniDockFrameWnd类服务于此。因此,在该方法内部,实际上创建一个该类的对象,作为该Bar新的Frame。(前面提到CControlBar中使用m_pDockSite存放Frame窗口对象)
Frame窗口在哪里做Layout?
当Frame窗口尺寸改变时,其RecalcLayout将会被调用。在该方法中,FrameWnd的所有子窗口都将被layout,主要工作的方法就是RepositionBars,其声明如下:
void RepositionBars(
);
其中,
1.
2.
3.
4.
具体代码片段如下: