MFC控制條窗口布局原理

一、框架窗口
框架窗口在其大小被改變的時候會收到WM_SIZE消息,這個消息的處理函數是CFrameWnd::OnSize,此函數接着調用RecalcLayout來重新安置各子窗口,它的主體代碼如下:
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);
}
這裏有兩個小的地方要注意,第一是FWS_SNAPTOBARS風格。一般來說,都是框架窗口主動改變大小,子窗口隨之要修改自己來適應框架窗口的改變,但是這個FWS_SNAPTOBARS風格卻相反,是讓框架窗口改變大小來適應它的子窗口,但我一路跟蹤下來沒有發現有哪個框架窗口有這個風格,都是走else分支的(事實上這個風格是爲CMiniDockFrameWnd準備的,這個框架窗口的大小是根據它內部的控制條來定的);第二是要注意RecalcLayout是不可重入的,MFC防止重入的方法雖然非常的簡單有效,但是它的方法要不能防止多線程的重入——話說回來MFC本身就不是一個線程安全的庫J
好了現在我們進入了整個重佈局動作的主體函數RepositionBars,讓我們仔細分析一下它都幹了些啥見不得人的“勾當”(這個函數在MSDN裏有文檔記載,關於它的幾個參數的含義就不在這裏贅述了):
首先,它創建一個AFX_SIZEPARENTPARAMS結構,並填寫好它的成員變量,最主要的就是兩個:bStretch和rect,前一個是BOOL型變量,表明子窗口是否需要拉伸(拉伸到和客戶區同寬或同高),後一個是當前客戶區大小。
接着,框架窗口按照Z-order,從最上面的子窗口開始,依次向它的所有子窗口發送WM_SIZEPARENT消息(ID爲nIDLeftOver的子窗口除外),以通知它們,按照新的客戶區,重新計算自己的大小和位置,並從AFX_SIZEPARENTPARAMS::rect中將自己所佔的那一塊rectangle扣除,這樣所有的子窗口計算完畢後框架窗口就可以知道剩餘客戶區的大小(PS:到底都發送給了誰?又是按照什麼次序?以MDIDemo爲例,該例子創建了一個CToolBar、一個CThumbnailListCtrlBar,在都是Dock狀態下,跟蹤記錄下的所發送的窗口的ID,依次是0xE801à0xE81Bà0xE81Eà0xE81Cà0xE81D,察看各子窗口ID的定義得知次序是狀態欄à上方的Dock Barà下方的Dock Barà左邊的Dock Barà右邊的Dock Bar,這裏要注意的是,所找到的第一個子窗口的ID是0xE900(0xE900被定義成AFX_IDW_PANE_FIRST,在這個例子裏它是View的窗口ID,與這個ID對應的還有另一個ID叫AFX_IDW_PANE_LAST,SDI的View窗口,MDI的MDIClient窗口,分隔條窗口等的ID都要介於上面兩個ID值之間),等於nIDLeftOver,所以WM_SIZEPARENT消息是不發給它的。那麼爲什麼Tool bar和Thumbnail bar也收不到消息呢?因爲Dock Bar是它的父窗口,消息發給Dock Bar了,Dock bar會計算它內部停靠的全部子窗口的大小,就不需要框架窗口操心了,除此之外,浮動的控制條也是不接受WM_SIZEPARENT消息的,原因很簡單,浮動的控制條不會跟隨主框架窗口的大小改變而改變)。
發送完畢之後(這裏有個細節,框架窗口發送WM_SIZEPARENT消息用的是SendMessage,這意味着只有子窗口處理完該消息後,SendMessage才返回,框架窗口才會接着發送消息給下一個子窗口,全部發送完畢也就意味了所有的子窗口都已經從AFX_SIZEPARENTPARAMS::rect中把自己要的那一塊rectangle給拿掉了),剩下的就是最後可用的客戶區了,根據nFlags的值,執行不同的返回動作。其中reposQuery表示只查詢,不做實際的重佈局動作,把最後剩下的客戶區,拷貝到lpRectParam就返回了,如果不是reposQuery那就要做重佈局了,對reposDefault,我們要把ID爲nIDLeftOver的子窗口的大小和位置調整到被其他子窗口切剩後剩下的可用客戶區內,使這個子窗口正好完全覆蓋最後的可用區域(也就是說所有的子窗口把客戶區全部擠滿了)。而當nFlag等於reposExtra時,在調整 nIDLeftOver子窗口的大小和位置前,用 lpRectParam來對最後剩下的可用區域做修正,具體來說就是把AFX_SIZEPARENTPARAMS::rect向裏縮,縮的距離由lpRectParam指定,這樣就使最後剩下的客戶區不被nIDLeftOver子窗口占滿,而是空出一些地方。修正完畢後最後一次性重佈局所有的子窗口。
至此,框架窗口所做的動作全部完成。

二、控制條子窗口
分析完了框架窗口,接着分析控制條這邊所要做的響應動作。根據前面的跟蹤我們知道除了CStatusBar和CDockBar,從CControlBar繼承下來的控制條諸如CToolBar、CDialogBar等,是收不到WM_SIZEPARENT消息的,它們的父窗口CDockBar代替它們接收這個消息。因此整個重佈局過程的起點是CDockBar對WM_SIZEPARENT消息的處理函數CDockBar::OnSizeParent(對CStatusBar而言,其起點是CControlBar::OnSizeParent,這裏不打算對它作進一步分析,有興趣可以自己完成)。第一步讓我們來分析這個函數所做的動作,這個函數不長,把完整代碼列出:
LRESULT CDockBar::OnSizeParent(WPARAM wParam, LPARAM lParam)
{
AFX_SIZEPARENTPARAMS* lpLayout = (AFX_SIZEPARENTPARAMS*)lParam;

// set m_bLayoutQuery to TRUE if lpLayout->hDWP == NULL
BOOL bLayoutQuery = m_bLayoutQuery;
CRect rectLayout = m_rectLayout;

m_bLayoutQuery = (lpLayout->hDWP == NULL);
m_rectLayout = lpLayout->rect;
LRESULT lResult = CControlBar::OnSizeParent(wParam, lParam);

// restore m_bLayoutQuery
m_bLayoutQuery = bLayoutQuery;
m_rectLayout = rectLayout;

return lResult;
}
如前所述,WM_SIZEPARENT消息傳遞一個AFX_SIZEPARENTPARAMS結構體的指針作爲參數,在這裏我們先取出這個結構體,然後判斷AFX_SIZEPARENTPARAMS::hDWP是否爲空,是的話說明父窗口僅僅是想查詢,並不要真的進行重佈局動作(回到RepositionBars,當nFlags爲reposQuery時,並不調用BeginDeferWindowPos,故而AFX_SIZEPARENTPARAMS::hDWP就一定是NULL),完成必要的變量保護後,進入父類CControlBar的OnSizeParent,在此,根據控制條窗口的風格,決定如何計算控制條的尺寸,具體是這樣的;
         DWORD dwMode = lpLayout->bStretch ? LM_STRETCH : 0; //拉伸否?
         if((m_dwStyle & CBRS_SIZE_DYNAMIC) && m_dwStyle & CBRS_FLOATING) //浮動,形狀可變
         {
                dwMode |= LM_HORZ | LM_MRUWIDTH;//計算水平狀態常用尺寸
         }
         else if(dwStyle & CBRS_ORIENT_HORZ) //水平停靠
         {
                dwMode |= LM_HORZ | LM_HORZDOCK;//計算水平停靠狀態尺寸
         }
         else
         {
                dwMode |= LM_VERTDOCK; //計算垂直停靠狀態尺寸
         }
         CSize size = CalcDynamicLayout(-1, dwMode);
要注意的是最後一行調用,CalcDynamicLayout,這個函數是一個虛函數,先被調用的是CControlBar:: CalcDynamicLayout,這個函數調用了CalcFixedLayout(也是一個虛函數),注意到CDockBar對此函數進行了重載,所以轉了一圈我們又回到了CDockBar中。

發佈了35 篇原創文章 · 獲贊 3 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章