您的位置:首页 > 其它

RepositionBars的用法和参数的意义

2010-05-08 21:51 288 查看
MFC窗口位置管理详细分析及实例在一般用MFC编写的程序的窗口客户区中,可能有好几个子窗口(具有WM_CHILD风格的窗口)。上边是工具栏,中间是视图窗口,下边是状态栏。三个窗口在框架的客户区里和平共处,互不重叠。主框架窗口的尺寸改变了,别的子窗口都能及时调整自己的尺寸以便保持相互位置关系不变,例如状态条窗口总能保持在主框架客户区底部,并且其宽度总能和主框架客户区宽度一致。工具栏窗口总能停靠在主框架的某一边不变,其宽度或高度总能和主框架客户区的宽度或高度一致,视图窗口总能填满主框架客户区的剩余空间。

假如我们自己从CWnd类派生一个窗口类并生成一个窗口,在它的客户区里要生成若干个子窗口,我们想使这些子窗口排列得规规矩矩,互不重叠,当父窗口的尺寸变了时各个子窗口能适时调整自己的尺寸和位置,使各个子窗口之间的位置大小比例关系不变。当移动其中一个或几个子窗口时,别的子窗口能及时为这个移动了的子窗口让位。当然我们可以利用api函数里管理窗口的函数来编写自己的管理子窗口的方法。可是如果在父窗口的客户区里有了工具栏,状态条等等子窗口时,你自己加进来的子窗口还能和这些mfc提供的子窗口融洽相处吗?你如何保证你的子窗口不会覆盖了能够四处停靠的工具栏?当工具栏和状态条消失后你的子窗口如何才能知道,以便及时调整自己的大小从而覆盖工具栏和状态条腾出的空间?基于文档视图构架的窗口的客户区内还有个视图,你自己硬加上的子窗口能不和视图窗口争地盘吗?

所以必须了解mfc的窗口管理它的客户区的方法。其实,mfc的窗口管理它的客户区的方法是非常简单的:父窗口调用一个函数,子窗口响应一个消息,就这么多。

CWnd::RepositionBars函数和WM_SIZEPARENT消息

先简述一下mfc的窗口为子窗口分配客户区空间的过程:这一过程是父窗口与子窗口共同协调完成的。父窗口先提供它的客户区内的一块区域,叫做起始可用区域。然后调用一个函数,在这个函数里,父窗口把这片区域通过一个消息提交给它的第一个子窗口,该子窗口决定自己要占用多大一块,然后在可用区域里把它将占据的部分划出去,这样可用区域就被切去了一块。父窗口再把这块剩下的可用区域通过同样的消息提交给第二个子窗口,第二个子窗口再根据自己的需要切掉一块。如此这般,每个子窗口都切去自己所需的一块。最后剩下的可用区域就给最后的子窗口使用。可以看出,除了最后一个子窗口外,其它子窗口都得在消息响应函数里有自己的算法来决定自己将在可用区域里占据多大一块,最后一个子窗口由于别无选择,所以不需要这样的算法。

当然,初始的可用区域是一个矩形,每次被切割后剩下的可用区域还是一个矩形,不可能是别的形状的。

举例说来,在一个典型单文档程序中,父窗口就是从CFrameWnd派生的主框架窗口,最后一个子窗口就是视图窗口,如果用了 CSplitterWnd生成分隔条的话,最后一个子窗口就是拥有分隔条的那个窗口。其它子窗口就是工具栏窗口和状态条窗口,以及可能有的别的控件窗口。

在典型多文档界面程序中,父窗口就是主框架窗口,最后一个子窗口就是覆盖在主窗口客户区,背景为黑灰色,拥有包含文档的子框架窗口的那个窗口,这是个预定义了窗口类的窗口,它的窗口类名是“MDIClient”。如果用了CSplitterWnd生成分隔条的话,最后一个子窗口就是拥有分隔条的那个窗口。其它窗口就是工具栏窗口,状态条窗口以及可能有的别的控件窗口。

这个函数和消息是:函数CWnd::RepositionBars()以及消息WM_SIZEPARENT。这个消息是mfc自定义的,不是windows自有的。

先简单说明一下这个函数和消息。

1。函数CWnd::RepositionBars()

这个函数不是虚函数,所以就无法在派生类里通过覆盖来编制自己的版本了,只能搞懂它的功能,以便能灵活使用。

简单而言,这个函数的功能是将可用的客户区区域信息放到消息WM_SIZEPARENT的消息参数里,然后枚举本窗口的所有子窗口,给每个子窗口 (除掉一个特定的子窗口,相当于上文提到的最后一个子窗口)都发送这个消息,每个响应这个消息的子窗口都会把可用客户区切去一块。最后把那个特定的子窗口的尺寸和位置调整到刚好放在最后剩下的可用区域里。

2。消息WM_SIZEPARENT

每个欲参与分配客户区的子窗口都要响应这个消息,除非这个子窗口是那个特定的子窗口。

响应这个消息的子窗口至少要做两件事:1,将可用的父窗口客户区切去自己所占据的一块。2,根据消息参数的指示,将自己的大小和位置调整到刚好容纳到自己所占据的区域里或不做调整。

下面详细介绍一下函数CWnd::RepositionBars()和消息WM_SIZEPARENT。

1。函数CWnd::RepositionBars() void RepositionBars( UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver, UINT nFlag = CWnd::reposDefault, LPRECT lpRectParam = NULL, LPCRECT lpRectClient = NULL, BOOL bStretch = TRUE );

参数比较多,但还是比较好懂的。

(1)nIDFirst和nIDLast

参与分配父窗口客户区的子窗口的id范围。

每个WM_CHILD风格的窗口都有个id,这是在窗口创建过程中指定的。函数CWnd::Create()的第六个参数就是这个id。api函数 CreateWindow和 CreateWindowEx里的那个HMENU类型的参数,当窗口的风格里有WM_CHILD时,它不是指的菜单句柄,而是该窗口的id。

nIDFirst和nIDLast参数指明了:如果一个子窗口的id值大于等于nIDFirst并且小于等于nIDLast,在这个函数中才会给这个子窗口发送 WM_SIZEPARENT消息,这个子窗口才能参与父窗口客户区的分配。

(2)nIDLeftOver

前面说过,有一个特定的子窗口,它不响应WM_SIZEPARENT消息。只有当其它的子窗口都分配完了,它才来捡取父窗口客户区里剩下的那块。 nIDLeftOver正是这个子窗口的id。它也必须大于等于nIDFirst并且小于等于nIDLast。

(3)lpRectClient

这是一个指向RECT结构数据的指针。这个RECT结构里存放的正是父窗口客户区的初始可用区域。随着在该函数里依次给各个子窗口发送 WM_SIZEPARENT消息,每个响应这个消息的子窗口都会切去自己所占据的部分。最后剩下的部分,就是id为nIDLeftOver的子窗口将要占据的区域了。这个参数可以为NULL,这时初始的可用区域就是整个父窗口客户区。

(4)nFlag和lpRectParam

这两个参数放在一起讲比较好。nFlag是该函数的功能标志,它可以有三个值:reposDefault,reposQuery 和reposExtra。

当nFlag等于reposDefault时,RepositionBars函数的功能是这样的:依次给id介于nIDFirst和nIDLast 之间并且不等于nIDLeftOver的子窗口发送WM_SIZEPARENT消息,每个响应这个消息的子窗口从lpRectClient所指的结构里切去自己所占据的部分,并且将自己的大小和位置调整到自己所占据的区域的大小,最后RepositionBars函数还将id为nIDLeftOver的子窗口的大小和位置调整到被其他子窗口切剩的可用区域内,使这个子窗口正好完全覆盖最后的可用区域。这种情况下lpRectParam不用,可以为 NULL。

当nFlag等于reposQuery 时,RepositionBars函数的功能是这样的:依次给id介于nIDFirst和nIDLast之间并且不等于nIDLeftOver的子窗口发送WM_SIZEPARENT消息,每个响应这个消息的子窗口从lpRectClient所指的结构里切去自己所占据的部分,但是他们并不调整自己的大小和位置,最后RepositionBars函数并不调整将id为nIDLeftOver的子窗口的大小和位置,而是根据bStretch的值来做动作:如果bStretch为TRUE,那么 RepositionBars函数把最后剩下的可用区域拷贝到lpRectParam指向的RECT结构里;如果bStretch为FALSE,那么 RepositionBars函数把所有其他子窗口占用掉的可用区域的高和宽(要所有的子窗口都紧排在一起,形成一个大的矩形,这个值才有意义)拷贝到 lpRectParam指向的RECT结构的bottom 和right成员里,其top和left成员被置零。使用这个nFlag值来调用RepositionBars的目的不是要重排子窗口,而是要看看,假如重排子窗口的话,这些子窗口将占去多大一块,最后剩下的可用区域在什么位置等等信息。

当nFlag等于reposExtra时,该函数的功能和nFlag等于reposDefault时差不多,有点小小的区别。此时需要用到 lpRectParam。前面说过,当 nFlag等于reposDefault时,RepositionBars函数将在最后把id为nIDLeftOver的子窗口的大小和位置调整到被其他子窗口切剩的可用区域内,使这个子窗口正好完全覆盖最后的可用区域。而当nFlag等于reposExtra时,RepositionBars在调整id 为nIDLeftOver的子窗口的大小和位置前,还要用 lpRectParam来对最后剩下的可用区域做修正。假设lpRect指向的是最后的可用区域,那么这个修正是这样进行的:

lpRect->top+=lpRectParam->top;
lprect->left+=lpRectParam->left;
lpRect->right-=lpRectParam->right;
lpRect->bottom-=lpRectParam->bottom;

通过这样的修正,可以使最后剩下的可用区域不被id为nIDLeftOver的子窗口占满,而是空出一些地方来留作他用。
(5)bStretch

这个参数上面已经提到一点它的作用。它主要是提供给各个响应WM_SIZEPARENT消息的子窗口用的,子窗口例如工具栏,状态条等在决定自己将从父窗口客户区的可用空间里划走多少时,这个参数也是个判断的依据。详细可以参阅工具栏和状态条响应WM_SIZEPARENT的函数 OnSizeParent()。

处理控制条的位置

计算控制条位置的过程和算法
工具条等控制条是作为一个子窗口在父边框窗口内显示的。为了处理控制条的布置(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./n");

}

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,这里不作进一步的分析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: