RepositionBars的用法和參數的意義

來源:互聯網
上載者:User

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將調用AfxRepositionWindowFunction Compute本控制視窗的位置,結果儲存到hDWP中。

在所有的控制視窗尺寸確定之後,剩下的留給視窗hWndLeftOver(如果存在的話)。確定了hWndLeftOver的大小之後,調用AfxRepositionWindowFunction Compute其位置,結果儲存到hDWP中。

上面提到的函數AfxRepositionWindow間接調用了::DeferWindowPos。

最後,::EndDeferWindowPos,使用hDWP安排所有子視窗的位置和大小。

 

至於其他函數,如OnSizeparent、OnWindowPosChanging、CalcWindowRect,這裡不作進一步的分析 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.