<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
    數據加載中……

    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()。?

    2。消息WM_SIZEPARENT?

    這是個mfc自定義的消息。在msdn里的TN024這篇技術文章里有關于這個消息的說明。?

    該消息的兩個參數中wParam不用,lParam是指向一個AFX_SIZEPARENTPARAMS結構變量的指針,這個結構變量是在RepositionBars函數里定義的:?

    AFX_SIZEPARENTPARAMS layout;?

    AFX_SIZEPARENTPARAMS結構定義如下:?

    struct AFX_SIZEPARENTPARAMS
    {
    HDWP hDWP;?
    RECT rect;?
    SIZE sizeTotal;?
    BOOL bStretch;?
    };
    這個結構變量的成員是在RepositionBars函數里填寫的:它的bStretch成員就是RepositionBars的參數bStretch,它的sizeTotal成員的兩個成員cx和cy都被設置為零,它的rect成員就是從RepositionBars的參數lpRectClient里拷貝過來的,就是父窗口客戶區的初始可用區域嘛。每個響應這個消息的子窗口都必須修改rect成員的值,以便切去自己所占據的部分。?
    成員hDWP是什么?這得知道三個api函數:BebinDeferWindowPos(),DeferWindowPos()和EndDeferWindowPos()。這三個api函數是用來成批設置窗口的位置和尺寸的。BebinDeferWindowPos()先通知windows分配一個將用來存貯窗口的位置和尺寸信息的結構,它不是返回這個結構的指針,而是返回代表這個結構的句柄,句柄的類型是HDWP。然后每個需要重新設置位置和尺寸的窗口都要調用DeferWindowPos()函數(該函數需要那個HDWP 類型的句柄為參數),以便往那個結構里填寫各自的窗口位置和大小信息。最后,在某個合適的時候調用EndDeferWindowPos(),windows就會根據那個結構里的信息把有關的窗口的位置和大小一次性設置好。比起針對每個窗口分別用SetWindowPos()等函數逐個設置來說,這種方法速度快。?

    好了,在RepositionBars函數里正是調用了BebinDeferWindowPos(),獲得一個HDWP類型的句柄,這個句柄就被填寫到了上面那個結構變量 layout的成員hDWP里。然后RepositionBars函數給每個符合條件的子窗口發送WM_SIZEPARENT消息。在每個響應WM_SIZEPARENT消息的子窗口里,要調用DeferWindowPos()來設置位置和尺寸信息。當所有的子窗口都響應完畢WM_SIZEPARENT消息后,RepositionBars函數再調用 EndDeferWindowPos()函數,這一來,除了那個id為nIDLeftOver的子窗口外,所有的子窗口都一次性排好了位置了。?

    至于該結構的sizeTotal成員的意義,它累計每個子窗口所占據掉的可用區域的長寬尺寸和。每個子窗口在響應WM_SIZEPARENT消息時一般都要把自己所占據的區域的高和寬分別累加到sizeTotal結構的cy和cx成員里。這有什么意義呢?當每個子窗口所占據的區域都是挨在一起的時候,這個 sizeTotal結構就有意義了,主框架窗口可以使nFlag等于reposQuery,使bStretch等于FALSE來調用RepositionBars函數,RepositionBars函數會把 sizeTotal結構的兩個成員值拷貝到lpRectParam參數里返回給主框架類(前面也提到過),這樣主框架類就知道它的客戶區內的子窗口占去了客戶區內多大的一塊空間。如果你的主框架窗口沒有利用這個信息,那么響應WM_SIZEPARENT消息的子窗口就可以不理睬sizeTotal成員。?

    ID的分配?

    可以看到,每個子窗口都有個id,同一個父窗口的子窗口的id不能重復。mfc的一些現成的控件子窗口都有預定義的id:?

    id名 id值 意義

    AFX_IDW_TOOLBAR 0xE800 // 主窗口的工具欄的id
    AFX_IDW_STATUS_BAR 0xE801 // 狀態欄的id
    AFX_IDW_PREVIEW_BAR 0xE802 // PrintPreview Dialog Bar
    AFX_IDW_RESIZE_BAR 0xE803 // OLE in-place resize bar
    AFX_IDW_REBAR 0xE804 // COMCTL32 "rebar" Bar
    AFX_IDW_DIALOGBAR 0xE805 // CDialogBar

    還有象單文檔程序的視圖窗口,多文檔程序的那個MDIClient窗口,分隔條窗口,他們的id值介于下面兩個id值之間:?
    AFX_IDW_PANE_FIRST 0xE900 //
    AFX_IDW_PANE_LAST 0xE9FF

    你要給你自己的子窗口分配id的話,別和上面的重復了。一般如果用IDE的菜單view/resource symbols項來加入自己的id的話,是不會重復的。有關id,還可以看看msdn里的TN020文章,那是專講id的。?

    實例分析?

    1。CFrameWnd類是如何調用RepositionBars函數的?

    前面介紹了RepositionBars的各個參數和意義,現在看看CFrameWnd類是如何調用這個函數的,從中可以學習RepositionBars函數的使用方法。?

    CFrameWnd類及其派生類生成的窗口的客戶區內可以有工具欄,狀態條和視圖窗口等子窗口。當父窗口的尺寸發生變化時,這些子窗口的各自的位置和大小比例關系保持不變,這就需要父窗口一旦在它自己的尺寸發生變化時就調用RepositionBars函數。CFrameWnd類是集中在函數 RecalcLayout里調用RepositionBars函數的。該類保證了在窗口尺寸發生變化時函數RecalcLayout都被調用,從而RepositionBars函數也能被及時調用,確保了各個子窗口都能及時調整自己的位置和大小。?

    RecalcLayout是個虛函數。該函數的功能就是在主框架的客戶區內提供一個初始的可用區域,并把這個區域放在一個CRect類型的變量里。該函數大致是這樣的:?

    void CFrameWnd::RecalcLayout(BOOL bNotify)
    {
    if (m_bInRecalcLayout)
    return;//這大概是在防止該函數重入
    m_bInRecalcLayout = TRUE;
    ....
    ....
    ....
    ....
    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;
    } 可以看出,mfc認為這個函數是不能重入的。在編制自己的RecalcLayout()函數時也得用同樣的方法來防止重入。
    后面的if語句檢查框架窗口是否具有風格FWS_SNAPTOBARS,這個風格用在什么時候呢?我是這樣認為的:通常都是在主框架窗口的尺寸改變
    時,子窗口在響應WM_SIZEPARENT消息時調整自己的尺寸以便跟上框架窗口的尺寸變化。有這樣的情況:父窗口的客戶區內的子窗口的數目是動態變
    化的,而且這些子窗口互相不能重疊,他們的尺寸由于某種原因不好改變。那么當子窗口的數目發生增減時,如不調整父窗口自己的尺寸,就會導
    致客戶區留下空白或新增加的子窗口沒有多余空間安排。FWS_SNAPTOBARS風格就是用在這種情況下,使父窗口能調整自己的大小以便容納子窗口。
    看這個分支里的語句,似乎是這樣的。
    一般都不會有FWS_SNAPTOBARS風格的,所以一般是執行else分支。在這個分支里簡單地調用RepositionBars去重排所有的子窗口,它的參數
    lpRectClient 使用默認的NULL值,意思就是初始可用區域是父窗口的整個客戶區。
    可以在自己的派生類里編寫自己的RecalcLayout函數,以便用自己的方法調用RepositionBars函數。要注意的是在CFrameWnd類的窗口剛被創建
    時RecalcLayout函數也被調用,此時可能某些用戶自己加的子窗口還未被創建出來,所以在這個函數內如果要引用某個用戶自己加的子窗口的句柄
    的話必須先用::IsWindow()函數判斷一下該窗口句柄是否可用。否則的話就會出現非法操作了。


    實戰演練
    由于精力有限,只提供一個實戰例子:將視圖,工具欄和狀態欄趕到右邊
    我們要生成這樣的界面:視圖窗口,工具欄和狀態條統統在右邊,左邊是個自己加的窗口。
    第一步:啟動AppWizard生成一個單文檔程序,全部使用默認設置。
    第二步:在CMainFrame類里增加一個成員 CWnd m_mywnd。
    第三步:在CMainFrame::OnCreate()函數里增加這幾行:
    m_mywnd.CreateEx
    (
    WS_EX_CLIENTEDGE,
    AfxRegisterWndClass
    (
    CS_HREDRAW|CS_VREDRAW,
    ::LoadCursor(NULL,IDC_ARROW),
    ::CreateSolidBrush(RGB(190,190,190))
    ),
    "",
    WS_VISIBLE|WS_CHILD,
    CRect(0,0,0,0),
    this,
    IDC_MYPANE //用IDE的菜單view/resource symbols項加入的id。
    );

    第四步:啟動ClassView,在CMainFrame里加上虛函數RecalcLayout(),函數體這樣寫:
    void CMainFrame::RecalcLayout(BOOL bNotify)
    {
    if (m_bInRecalcLayout)
    return;
    m_bInRecalcLayout = TRUE;

    //rect1是新加的窗口將占據的區域
    //rect2就是提供給工具欄,狀態條和視圖窗口的初始可用區域。
    CRect rect1,rect2;
    GetClientRect(&rect1);
    rect1.right=rect1.right/3;
    GetClientRect(&rect2);
    rect2.left=rect2.right/3;

    if(::IsWindow(m_mywnd.m_hWnd)) //這句是不能少的
    m_mywnd.MoveWindow(&rect1);
    RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, CRect(0,0,0,0),&rect2);
    m_bInRecalcLayout = FALSE;
    }

    第五步:用IDE的菜單view/resource symbols項加入一個id:IDC_MYPANE。
    第六步:編譯并運行程序。

    好了,在主框架窗口的左邊多了一個灰色的窗口,它占主窗口客戶區的三分之一。工具欄,狀態條和視圖都被趕到右邊三分之二的地方去了。

    from: http://dev.poptool.net/soft/vc/36541.html

    posted on 2006-11-11 16:55 weidagang2046 閱讀(380) 評論(0)  編輯  收藏 所屬分類: Windows

    主站蜘蛛池模板: 亚洲av日韩av综合| 人成电影网在线观看免费| 日韩免费无码视频一区二区三区| 三上悠亚电影全集免费| 亚洲一级毛片免费观看| 精品熟女少妇av免费久久| 国产成人3p视频免费观看| 国产亚洲情侣一区二区无| 亚洲免费日韩无码系列| 毛色毛片免费观看| 亚洲一区二区女搞男| 国产一区二区三区免费| 国产成人aaa在线视频免费观看| 亚洲综合无码一区二区| 国产精品永久免费10000| 亚洲啪AV永久无码精品放毛片| 日韩免费高清播放器| 亚洲精品视频在线观看免费| 免费a级毛片18以上观看精品| 久久久久久亚洲精品成人| 可以免费看的卡一卡二| 国产精品亚洲专区无码WEB| 亚洲AⅤ视频一区二区三区 | 男女一边摸一边做爽的免费视频| 丰满人妻一区二区三区免费视频| 无码国产精品一区二区免费I6| 亚洲国产成人片在线观看无码| 羞羞漫画页面免费入口欢迎你| 国产福利在线免费| 瑟瑟网站免费网站入口 | 国产精品1024永久免费视频 | 国产久爱免费精品视频| 亚洲AV本道一区二区三区四区| 中文字幕版免费电影网站| 在线观看亚洲人成网站| 日韩免费视频观看| 免费观看成人久久网免费观看| 国产aⅴ无码专区亚洲av| 最近免费中文字幕大全视频 | 亚洲黄色免费网址| 四虎影视永久免费观看|