control bar 的用法 转

来源:互联网 发布:菏泽矩阵直销软件 编辑:程序博客网 时间:2024/06/07 01:34

MFC技术系列(四)--Frame窗口之Control Bar(1)

Visual C++ 2007-12-25 23:36:11 阅读92 评论0   字号: 订阅

 

接上篇,  MFC技术系列(四)--Frame窗口(2)

MFC中主要涉及到下面的文件和类:

barcore.cpp CControlBar

dockcont.cpp CDockContext

bardock.cpp CDockBar

winfrm2.cpp CFrameWnd

bartool.cpp CToolBar

 

MFCFrame窗口除了支持Doc-View框架外,还提供了一个重要特性,就是Dock,即:我们可以从CControlBar派生各种Bar,通过CFrameWnd所提供的Dock方法,可以将该Bar Dock到四个方向,或者让其Floating(通过另一个特殊的Frame窗口来支持,后面将会解释)。在MFC提供的默认SDI或者MDI框架代码中,我们能看到类似如下的代码:

    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

    EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

其中,分别表示起如下作用:

1.     让该tool bar可以dock到任何边

2.       初始化Frame窗口的四个dock bar

3.       tool bardock bar关联起来(做为dock bar的子窗口)

实际上,CFrameWnd提供的Dock功能是由其内部的四个Dock bar所提供的(分别对应四个边)。

1.1           CControlBar

先说说CControlBar类,它的定义位于afxext.h中,实现位于barcore.cpp中。在MFC中说到Bar,一般都是指继承该类而来。既然CControlBar是依赖于CFrameWnd,它的m_pDockSite方法即用来保存其所在的Frame窗口对象的指针,这种关联在CControlBarOnCreate方法中完成,而且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_NCPAINTWM_ERASEBKGND两个消息(在当前场景下,background erase消息是在EraseNonClient方法中发出的)。因此,实际上,CToolBar是在OnNcPaint方法中完成绘制,不过它并没有自己的实现,该方法中仅仅是调用CControlBarEraseNonClient方法。在EraseNonClient方法中,由于是要处理非客户区,所以使用了CWindowDC,而且,剪切掉了客户区域,只留下非客户区域供绘制(使用CDCExcludeClipRect方法,具体请参见后续文章《DC和绘图》),具体绘制的内容仍然是BorderGripper(也是调用上述两个方法)。

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,它使用系统缺省的消息处理函数,处理所有未处理的消息。

说到该消息,不能不提到下面三个方法:

BeginDeferWindowPosDeferWindowPosEndDeferWindowPos。它们常用来处理多个窗口协同的尺寸变化。它实际上是同时记录了多个窗口的位置信息(使用HDWP 结构),在最后一起来改变它们。

3. OnSizeParent

响应父窗口尺寸变化。该消息为MFC私有消息,由父窗口发送到子窗口。在Dock体系中,父窗口就是该Control Bar所在的Frame窗口。

4. OnLButtonDown

鼠标按下,调用CWndOnToolHitTest方法判断是否点击为空白区域(没有点击到任何子窗口)时,准备拖动Bar。此时,将委托给其内部的CDockContext实例的StartDrag方法。那么什么时候结束拖动呢? CDockContext帮我们处理了拖动到结束的所有绘画细节。详细内容请看“CDockContext类”一节。

5. OnLButtonDblClk

鼠标双击,同样也是处于空白区域时,在浮动和停靠状态间执行切换。此时,调用CDockContextToggleDocking方法。

6. OnMouseActivate

如果处于浮动状态,那么将激活顶层top-level窗口,通常就是应用的主窗口。这里使用了CWnd提供的ActivateTopParent方法

 

1.2           CFrameWnd

该类提供了如下方法用来封装ControlBarDock功能:DockControlBarFloatControlBar。其中,

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(

   UINT nIDFirst,

   UINT nIDLast,

   UINT nIDLeftOver,

   UINT nFlag = reposDefault,

   LPRECT lpRectParam = NULL,

   LPCRECT lpRectClient = NULL,

   BOOL bStretch = TRUE

);

其中,

1.       nIDFirstnIDLast指明了子窗口的ID范围。在RecalcLayout中,使用了0 ~ 0xffffnIDLeftOver则表明该子窗口将充满除了control bars外剩余的客户区域,在RecalcLayout中,使用了AFX_IDW_PANE_FIRST,这也是Frame窗口中的ViewID

2.       该方法中,将遍历所有子窗口,如果在first ~ last范围中时,将发送WM_SIZEPARENT消息到每个子窗口。此时,就是Frame窗口的四个DockBar。在它们响应方法OnSizeParent中,将会根据设置的CBRS风格,调用前面提到的CControlBarCalcDynamicLayout动态的layout方法,该方法的默认实现将会调用CalcFixedLayout,而CDockBar重载了该方法,其实现逻辑比较复杂(bardock.cpp)

3.       然后,该方法将会调用ViewCalcWindowRect方法,以便能充满剩余客户区域。该方法为CWnd提供,CView重载了它。

4.       ControlBar处于浮动状态时,CFrameWndFWS_SNAPTOBARS风格表明该Frame窗口将调整为刚好包围ControlBar。此时,调用稍微复杂些。

具体代码片段如下:

    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);

 

 

MFC提供了如下CControlBar的派生类:CDockBar, CToolBar, CStatusBar, CDialogBar, CReBar。这些类的详细情况,将在后续的章节中描述。

MFC技术系列(四)--Frame窗口之Control Bar(2)

Visual C++ 2007-12-25 23:26:05 阅读35 评论0   字号: 订阅


1.3           CDockContext

在前面的CControlBar类中,提到了当拖动荡Bar时,将借助CDockContext类的方法来完成。CDockContext类主要就是用来完成对Bar的行为,包括:拖动,在浮动和停靠间Toggle、改变尺寸。下面分别做一个说明:

1.       Drag(拖动)

标准的行为,即从鼠标左键按下Bar的空白区域(非子窗口区域),然后拖动,绘制焦点矩型,直到鼠标左键抬起,拖动结束。涉及的方法包括:

StartDrag:分三种情况分别计算拖动矩形的初始值(由于有水平和垂直两个方向,所以矩形都是成对的):CBRS_SIZE_DYNAMIC风格、CBRS_SIZE_FIXED风格和其它风格。对于第二种固定尺寸风格来说,只需要一对矩形;而对第一种风格,因为浮动状态还可以改变尺寸,所以还需要计算一个浮动矩形,用m_rectFrameDragHorzm_rectFrameDragVert两个成员保存。该矩形的计算经过三个步骤:第一,先调用BarCalcDynamicLayout计算BarRect(相当于Frame的客户区);第二,计算FrameRect,此时,调用了CMiniFrameWndCalcBorders方法,它调用AdjustWindowRectEx API,该方法用来依据客户区域计算整个窗口Rect;第三,减去窗口边缘的尺寸

Move:响应鼠标移动消息,移动上述矩形。另外,还判断是否能够Dock,依据的信息包括:Ctrl键按下(m_bForceFrame)、Shift键按下(m_bFlip)、Dock位置风格(CBRS_ORIENT_VERTCBRS_ORIENT_HORZ),主要逻辑封装在其CanDock方法中,该方法最终的判断会交给Frame窗口(由m_pDockSite记录)的CanDock方法。

TrackCapture鼠标,从消息队列获取消息,实际处理的消息包括:

a.       WM_LBUTTONUP:如果是Drag状态,那么调用EndDrag,结束拖动;否则为Resize状态,那么调用EndResize结束该行为。

b.       WM_MOUSEMOVEDrag状态,调用Move移动焦点矩形;否则,调用Stretch方法,改变焦点矩形的尺寸,然后绘制它

c.       WM_KEYUP:调用其OnKey方法,主要处理CtrlShift

d.       WM_KEYDOWN:调用其OnKey方法,主要处理CtrlShift键。此时,如果Esc键被按下,那么CancelLoop将被调用,从而结束上述行为(Drag,或Resize

e.       WM_RBUTTONDOWN:调用CancelLoop

可见,在Drag过程中,可以使用Esc,或者鼠标右键结束Drag。(后面的Resize也同样适用)

EndDrag:下面两个步骤

a.       调用CancelLoop

b.       根据Dock的状态,调用Frame窗口的DockControlBar,或者FloatControlBar完成相应的Dock

另外两个方法:

InitLoop:下面三个步骤:

a.       处理消息队列中的WM_PAINT消息

b.       初始化一些状态和中间尺寸

c.       获得桌面窗口(CWndGetDesktopWindow

d.       调用桌面窗口的LockWindowUpdate,任何绘画都不会生效,当然窗口也不可能被移动,直到调用UnlockWindowUpdate

e.       获取桌面窗口的DC(通过CWndGetDCEx

CancelLoop:下面四个步骤:

a.       调用DrawFocusRect,消除Drag矩形

b.       然后释放鼠标的Capture

c.       调用UnlockWindowUpdate,让绘画生效

d.       释放DC

2.       StretchResize

 

这个过程很类似上面的Drag。基本可以对应起来,它们共用Track方法,从开始到结束,基本是StartResize, InitLoop, Track (Stretch), EndLoop, EndResize

3. Toggle Docking

       在浮动和停靠两种状态间切换,由该类的ToggleDocking方法完成。该方法比较简单。

       4. 绘制焦点矩形

       无论是Drag,还是Resize,都有这个步骤。它由该类的DrawFocusRect方法完成,绘制部分调用了CDCDrawDragRect方法。

MFC技术系列(四)--Frame窗口之Control Bar(3)

Visual C++ 2007-12-25 23:35:05 阅读25 评论0   字号: 订阅


1.4          CDockBar

该类所代表的子窗口是CControlBar的父窗口,而Frame窗口则是该类所在的子窗口的父窗口。从表面上看,CControlBar Dock在了Frame窗口中,但实际上,中间还有一层窗口,这就是CDockBar。前面已经提到,CFrameWnd提供了四个CDockBar实例,分别对应四个方向。

该类的定义位于afxpriv.h中(同CDockContext一样),实现位于bardock.cpp中。下面是该类的定义:

class CDockBar : public CControlBar

{

     DECLARE_DYNAMIC(CDockBar)

 

// Construction

public:

     /* explicit */ CDockBar(BOOL bFloating = FALSE);   // TRUE if attached to CMiniDockFrameWnd

     virtual BOOL Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID);

 

// Attributes

     BOOL m_bFloating;

 

     virtual BOOL IsDockBar() const;

     int GetDockedCount() const;

     virtual int GetDockedVisibleCount() const;

 

// Operations

     void DockControlBar(CControlBar* pBar, LPCRECT lpRect = NULL);

     void ReDockControlBar(CControlBar* pBar, LPCRECT lpRect = NULL);

     BOOL RemoveControlBar(CControlBar*, int nPosExclude = -1, int nAddPlaceHolder = 0);

     void RemovePlaceHolder(CControlBar* pBar);

 

// Implementation

public:

     virtual ~CDockBar();

#ifdef _DEBUG

     virtual void AssertValid() const;

     virtual void Dump(CDumpContext& dc) const;

#endif

     virtual CSize CalcFixedLayout(BOOL bStretch, BOOL bHorz);

     virtual void DoPaint(CDC* pDC);

 

     // public implementation helpers

     void GetBarInfo(CControlBarInfo* pInfo);

     void SetBarInfo(CControlBarInfo* pInfo, CFrameWnd* pFrameWnd);

     int FindBar(CControlBar* pBar, int nPosExclude = -1);

     void ShowAll(BOOL bShow);

 

     CPtrArray m_arrBars;    // each element is a CControlBar

protected:

     BOOL m_bLayoutQuery;

     CRect m_rectLayout;

 

     CControlBar* GetDockedControlBar(int nPos) const;

 

     // implementation helpers

     int Insert(CControlBar* pBar, CRect rect, CPoint ptMid);

     virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);

 

     //{{AFX_MSG(CDockBar)

     afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp);

     afx_msg void OnNcPaint();

     afx_msg void OnWindowPosChanging(LPWINDOWPOS lpWndPos);

     afx_msg void OnPaint();

     afx_msg LRESULT OnSizeParent(WPARAM, LPARAM);

     //}}AFX_MSG

     DECLARE_MESSAGE_MAP()

 

     friend class CMiniDockFrameWnd;

};

1. 首先,我们知道CDockBar也是一种CControlBarCDockBar的通过其Create方法创建。既然在Control Bar dock时,需要重新计算位置和尺寸,因此,创建时不需要位置信息。调用CWndCreate方法前,需要先注册class,即:

调用AfxDeferRegisterClass(AFX_WNDCONTROLBAR_REG)。其中,AfxDeferRegisterClass在这里实际是指AfxEndDeferRegisterClass方法,该方法的实定义位于afximpl.h中,实现位于wincore.cpp中。参数AFX_WNDCONTROLBAR_REGMFC所定义的几个窗口类型的一种。下面是其它几类的声明:

#define AFX_WND_REG                             0x00001

#define AFX_WNDCONTROLBAR_REG                   0x00002

#define AFX_WNDMDIFRAME_REG                     0x00004

#define AFX_WNDFRAMEORVIEW_REG                  0x00008

#define AFX_WNDCOMMCTLS_REG                     0x00010 // means all original Win95

这些宏对应的类名称的定义也位于afximpl.h中。下面是MFC采用的命名方式:

// special AFX window class name mangling

 

#ifndef _UNICODE

#define _UNICODE_SUFFIX

#else

#define _UNICODE_SUFFIX _T("u")

#endif

 

#ifndef _DEBUG

#define _DEBUG_SUFFIX

#else

#define _DEBUG_SUFFIX _T("d")

#endif

 

#ifdef _AFXDLL

#define _STATIC_SUFFIX

#else

#define _STATIC_SUFFIX _T("s")

#endif

 

#define AFX_WNDCLASS(s) /

     _T("Afx") _T(s) _T("70") _STATIC_SUFFIX _UNICODE_SUFFIX _DEBUG_SUFFIX

 

#define AFX_WND             AFX_WNDCLASS("Wnd")

#define AFX_WNDCONTROLBAR   AFX_WNDCLASS("ControlBar")

#define AFX_WNDMDIFRAME     AFX_WNDCLASS("MDIFrame")

#define AFX_WNDFRAMEORVIEW  AFX_WNDCLASS("FrameOrView")

#define AFX_WNDOLECONTROL   AFX_WNDCLASS("OleControl")

同时,提供了几个全局变量来保存这些类名称。对于当前类型,类名变量为_afxWndControlBar。它使用COLOR_BTNFACE作为背景色。

 

2. GetDockedCount方法说明一个CDockBar实例能够dock多个control bar(保存在其m_arrBars中)。另外,还提供了GetDockedVisibleCount获取可见的control bar的数目。既然能够dock多个control bar,那么就存在对control bar的管理,包括下面的方法:

DockControlBar, RemoveControlBar, ReDockControlBar, RemovePlaceHolder, FindBar, GetDockControlBar。这里重点解读一下DockControlBar,其基本步骤如下:

a.       判断是否已经dock,否则继续

b.       根据CControlBar实例的风格,设置Dock bar的风格

c.       根据Dock bar风格和停靠状态,设置正确的Control Bar的停靠风格,并打开所有border CBRS_BORDER_ANY

d.       如果是更换dock bar,那么先将control bar隐藏

e.       如果control bar已经具有一个位置矩形,比如:拖动control bar停靠时,那么调用dock barInsert方法,获得合适的rect,然后再移动control bar的位置;否则,直接停靠到最边缘(这通常发生在control bar刚刚创建后)

f.        重新设置control bar的父窗口,然后将control bar从原来的dock bar,或者当前dock bar的原来位置移去

g.       显示control bar

h.       设置让Frame窗口重新lay out其子窗口的标记。注意,此时,layout并未真正发生!那么什么时候真正layout呢,当然是在Frame窗口尺寸改变时。请参看1.2节。

3.

如果需要保存Dock状态和位置,CControlBarInfo类提供了这一功能。通过CDockBarGetBarInfo方法可以得到CControlBarInfo对象。

(未完,待续...)

原创粉丝点击