標籤:使用 os 資料 io re c
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()。