程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> MFC教程(13)-MFC工具條和狀態欄(1)

MFC教程(13)-MFC工具條和狀態欄(1)

編輯:關於VC++

Windows控制窗口

Windows (Windows95或者以上版本) 提供了系列通用控制窗口,其中包括工具條(ToolBar)、狀態欄(StatusBar)、工具條提示窗口(ToolTip)。

Windows在一個DLL加載時注冊個控制窗口的“窗口類”。例如,工具條的“窗口類”是“ToolbarWindow32”,狀態欄的“窗口類”是“msctls_statusbar32”,工具條提示窗口的“窗口類”是“tooltips_class32”。為了保證該DLL被加載,使用控制“窗口類”前,應該首先調用函數InitCommonControl。MFC在窗口注冊函數AfxDeferRegisterClass中實現了這一點。見2.2.1節MFC下窗口的注冊。

創建通用控制窗口,可以使用專門的創建函數,如創建工具條的函數::CreateToolBarEx,創建狀態欄的函數::CreateStatusBarEx。也可以調用窗口創建函數::CreateWindowEx,但是需要指定預定義的“窗口類”,必要的話還要其他步驟,如使用“ToolbarWindow32”“窗口類”創建工具欄後,還需要在工具欄中添加或者插入按鈕。

一般,通用控制可以指定控制窗口風格(Style)。例如,具備風格CCS_TOP,表示該控制窗口放到父窗口客戶區的頂部,具備CCS_BOTTOM,表示該控制窗口在客戶區的底部。具體的控制窗口類可以有特別的適合於自己的風格,例如,TTS_ALWAYSTIP表示只要光標落在工具欄的按鈕上,ToolTip窗口不論激活與否都會顯示出來。

每一控制窗口類都有自己的窗口過程來處理自己的窗口消息,實現特定的功能。控制窗口類的窗口過程由Windows提供。

工具條

工具條的窗口過程處理了必要的消息,提供了標准工具條的功能,例如,工具條對客戶化特征提供內在的支持,用戶可以通過一個客戶化對話框來添加、修改、刪除或者重新安排工具條按鈕。這些特征是否可以被用戶所用或者用到什麼地步是可以由程序控制的。

工具條的窗口過程將自動設置工具條的尺寸大小和位置,如果指定了控制窗口風格CCS_TOP或者CCS_BOTTOM,則窗口過程把工具條放到父窗口客戶區的頂部或者底部。窗口過程任何時候只要收到WM_SIZE或者TB_AUTOSIZE消息就自動地調整工具條的大小和位置。

工具條的按鈕被選中後,會產生一個命令消息,它的窗口過程把該消息送給父窗口的窗口過程處理。

工具條中的按鈕並不以子窗口的形式出現,而是以字符或者位圖按鈕的方式顯示,每個按鈕大小相同,缺省是24*22個像素。每個按鈕都有一個索引,索引編號從0開始。每個按鈕包括如下屬性:

按鈕的字符串索引,位圖索引,風格,狀態,命令ID

按鈕可以有兩種風格TBSTYLE_BUTTON和TBSTYLE_CHECK,前者像一個標准按鈕那樣響應用戶的按擊,後者響應每一次按擊,在按下和跳起兩種狀態之間切換。按鈕響應用戶的動作,給父窗口發送一個包含了該按鈕對應命令ID的命令消息。一般一個按鈕的命令ID對應一個菜單項。

工具條維護兩個列表,分別用來存放工具條按鈕使用的字符串或者位圖,列表中的位圖或者字符串從0開始編號,編號和按鈕的索引相對應。

工具條可以是Dockable(泊位)或者Floatable(漂浮)的。

工具條可以有TBSTYLE_TOOLTIPS風格,如果具有這種風格,則創建和管理一個Tooltip控制,這是一個小的彈出式窗口,用來顯示描述按鈕的文本,平時該窗口隱藏,當鼠標落到按鈕上面並停留約一秒後才彈出,在鼠標附近顯示。

由於Tooltip窗口平時是隱藏的,所以不能接收鼠標消息來決定何時顯示本窗口。這樣,接收鼠標的窗口必須把鼠標消息送給Tooltip窗口,這是通過給Tooptip窗口發送消息TTM_RELAYEVENT來實現的。

狀態欄

狀態欄類似於工具條,有自己的窗口過程,可以泊位、漂浮。不過,習慣上狀態欄都位於屏幕底部。每個狀態條分成若干格(Status bar panes),每格從0開始編號,編號作為格的索引。每一個格,如同工具條的按鈕一樣,並不是一個Windows窗口。

MFC的工具條和狀態欄類

MFC使用CToolBarCtrl、CStatusBarCtrl和CToolTipCtrl窗口類分別對工具條、狀態欄、Tooltip控制窗口進行了封裝。

但是,直接使用這些類還不是很方便。MFC提供了CToolBar、CStatusBar來處理狀態欄和工具條,CToolBar、CStatusBar功能更強大,靈活。這兩個類都派生於CControlBar。

在MFC下,建議這些控制條子窗口ID介於AFX_IDW_TOOLBARFIRST(0xE800)和AFX_IDW_CONTROLBAR_LAST(0Xe8FF)之間。這256個ID中,前32個又有其特殊性,用於MFC的打印預覽中。

CControlBar派生於CWnd類,是控制條窗口類的基類,它派生出CToolBar、CStatusBar、CDockBar、CDialogBar、COleResizeBar類。CControlBar實現了以下功能:

和父窗口(邊框窗口)的頂部或者底部或者其他邊對齊。

可以包含子條目,這些條目或者是基於HWND的子窗口,或者是基於非HWND的條目。負責分配條目數組。

支持CBRS_TOP(缺省,控制條放在頂部),CBRS_BOTTOM(放在底部),CBRS_NOALIGN(父窗口大小變化時不重新放置控制條)等幾種控制風格。

支持派生類的實現。幾個派生類有一定的共性,或者其中兩個有一定的共性,這樣CControlBar實現的函數一部分只適用於某個派生類,一部分適用於兩個或者多個派生類,還有一部分適用於所有的派生類。所謂適用,這裡指派生類直接繼承了CControlBar的實現,或者覆蓋了其實現但是建立在擴展其實現的基礎上。類似地,CControlBar的成員變量也不是為所有派生類所共同適用的。

CStatusBar和CControlBar一方面建立在CControlBar的基礎之上,另一方面以Windows的通用控制狀態欄和工具條為基礎。它們繼承了CControlBar類的特性,但是所封裝的窗口句柄是相應的Windows控制窗口的句柄,如同CFormView繼承了CSrcollView的視類特性,但是其窗口句柄是無模式對話框窗口句柄一樣。

典型地,如果在使用AppWizard生成應用程序時,指定了要求工具條和狀態欄的支持,則在主邊框窗口的OnCreate函數中包含一段如下的代碼,用來創建工具條、狀態欄和設置一些特性。

//創建工具欄
if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar
");
return -1; // fail to create
}
//創建狀態欄
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar
");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
//對工具欄設置Tooltip特征
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
//使得工具欄可以泊位在邊框窗口
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

工具條除了Tooltip,Resizeable,Dockable特性外,還可以是Floatable。應用程序可以使用CFrameWnd::SaveBarState保存邊框窗口的控制條的有關信息到INI文件或者Windows Register庫,使用LoadBarSate從INI文件或者Register庫中讀取有關信息並恢復各個控制條的設置。

下文,將討論工具條等的創建、銷毀,從中分析CControlBar和派生類的關系,討論CControlBar如何實現共性,如何支持派生類的特定要求,派生類又如何實現自己的特定需求等。

控制窗口的創建

創建工具條、狀態條、對話框工具欄的方法是不同的,所以必須給每個派生類CToolBar、CStatusBar、CDialogBar設計和實現自己的窗口創建函數Create。但是,它們是也是有共性的,共性由CControlBar的PreCreateWindow處理。在窗口創建之後,各個派生類都要進行的處理(共性)由CControlBar的OnCreate完成,特別的處理通過派生類的OnNcCreate完成。

PreCreateWindow

首先,討論CControlBar 類的PreCreateWindow的實現。

BOOL CControlBar::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
//修改窗口風格,強制適用clipsliblings,以防重復繪制
cs.style |= WS_CLIPSIBLINGS;
//default border style translation for Win4
//(you can turn off this translation by setting CBRS_BORDER_3D)
if (afxData.bWin4 && (m_dwStyle & CBRS_BORDER_3D) == 0)
{
DWORD dwNewStyle = 0;
switch (m_dwStyle & (CBRS_BORDER_ANY|CBRS_ALIGN_ANY))
{
case CBRS_LEFT: //控制條在邊框窗口的左邊顯示
dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM;
break;
case CBRS_TOP://控制條在邊框窗口的頂部顯示
dwNewStyle = CBRS_BORDER_TOP;
break;
case CBRS_RIGHT://控制條在邊框窗口的右邊顯示
dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM;
break;
case CBRS_BOTTOM://控制條在邊框窗口的底部顯示
dwNewStyle = CBRS_BORDER_BOTTOM;
break;
}
// set new style if it matched one of the predefined border types
if (dwNewStyle != 0)
{
m_dwStyle &= ~(CBRS_BORDER_ANY);
m_dwStyle |= (dwNewStyle | CBRS_BORDER_3D);
}
}
return TRUE;
}

其中,afxData是一個全局變量,MFC用它來記錄系統信息,如版本信息等。這裡afxData.bWin4表示Windows版本是否高於4.0。

CToolBar的PreCreateWindow函數修改了窗口風格,也修改狀態欄、工具欄等的CBRS_風格。CBRS_風格的改變不會影響窗口風格。因為這些CBRS_風格被保存在成員變量m_dwStyle中。

除了上述在程序中用到的影響工具條、狀態欄等顯示位置的CBRS_風格外,還有和泊位相關的CBRS_風格,CBRS_ALIGN_LEFT、CBRS_ALIGN_RIGHT、CBRS_ALIGN_BOTTOM、CBRS_ALIGN_TOP、CBRS_ALIGN_ANY,分別表示工具條可以在停泊在邊框窗口的左邊、右邊、底部、頂部或者所有這些位置;和漂浮相關的CBRS_風格CBRS_FLOAT_MULTI,表示多個工具條可以漂浮在一個微型邊框窗口中;和Tooltips相關的CBRS_風格CBRS_TOOLTIPS和CBRS_FLYBY。

派生類如果沒有特別的要求,可以不覆蓋PreCreateWindow函數。CStatusBar因為有更具體和特殊的風格要求,所以它覆蓋了PreCreateWindow。CStatusBar的覆蓋實現調用了CControlBar的實現。

派生類也可以在覆蓋實現中修改PreCreateWindow參數cs,改變窗口風格;修改m_dwStyle,改變CBRS_風格。

控制條的窗口創建

CControlBar派生類實現了自己的窗口創建函數Create,CControlBar的PreCreateWindow被派生類的Create函數直接或者間接地調用。以CToolBar為例討論窗口創建函數和創建過程。

CToolBar的窗口創建函數Create

Create函數實現如下:

BOOL CToolBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID)
{
ASSERT_VALID(pParentWnd); // must have a parent
ASSERT (!((dwStyle & CBRS_SIZE_FIXED) &&
(dwStyle & CBRS_SIZE_DYNAMIC)));
// 保存dwStyle指定的CBRS_風格
m_dwStyle = dwStyle;
if (nID == AFX_IDW_TOOLBAR)
m_dwStyle |= CBRS_HIDE_INPLACE;
//去掉參數dwStyle包含的CBRS_風格
dwStyle &= ~CBRS_ALL;
//設置窗口風格
dwStyle |=
CCS_NOPARENTALIGN|CCS_NOMOVEY|CCS_NODIVIDER|CCS_NORESIZE;
//初始化通用控制,可以導致InitCommonControl的調用
VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
//創建窗口,將調用PreCreateWindow,OnCreate, OnNcCreate等
CRect rect; rect.SetRectEmpty();
if (!CWnd::Create(TOOLBARCLASSNAME, NULL, dwStyle,
rect, pParentWnd, nID))
return FALSE;
// Note: Parent must resize itself for control bar to be resized
return TRUE;
}

其中:

Create函數的參數1表示工具條的父窗口。參數2指定窗口風格和CBRS_風格,缺省值為 WS_CHILD | WS_VISIBLE | CBRS_TOP,其中WS_CHILD和WS_VISIBLE是窗口風格,CBRS_TOP是CBRS_風格。參數3指定工具條ID,缺省值為AFX_IDW_TOOLBAR(0X0E800或者59392)。如果還有多個工具欄要顯示,在創建它們時則必須給每個工具欄指明ID。

首先,Create函數把參數2(dwStyle)指定的窗口風格和CBRS_風格分離出來,窗口風格保留在dwStyle中,CBRS_風格保存到成員變量m_dwStyle中。CToolBar::PreCreateWindow將進一步修改這些風格。

接著,Create函數調用了函數AfxDeferRegisterClass。它如果沒有注冊TOOLBARCLASSNAME表示的“窗口類”,就注冊該類;否則,返回TRUE,表示已經注冊。TOOLBARCLASSNAME表示的字符串是“ToolbarWindow32”,即“窗口類”名稱。

然後,調用CWnd::Create(7個參數)使用“ToolbarWindow32”“窗口類”創建工具欄。

Create在創建窗口的過程中,用MFC的標准窗口過程取代原來的窗口過程,如同CFormView和CDialog窗口創建時窗口過程被取代一樣,並發送WM_CREATE和WM_NCCREATE消息。

至於添加向工具欄添加按鈕,則由函數LoadToolBar完成。在分析LoadToolBar函數之前,先討論OnCreate、OnNcCreate等函數。

處理WM_CREATE消息

CControlBar提供了消息處理函數OnCreate來處理WM_CREATE消息。

int CControlBar::OnCreate(LPCREATESTRUCT lpcs)
{
//調用基類的實現
if (CWnd::OnCreate(lpcs) == -1)
return -1;
//針對工具欄,是否有Tooltip特性
if (m_dwStyle & CBRS_TOOLTIPS)
EnableToolTips();
//得到父窗口,並添加自身到其控制條列表中
CFrameWnd *pFrameWnd = (CFrameWnd*)GetParent();
if (pFrameWnd->IsFrameWnd())
{
m_pDockSite = pFrameWnd;
m_pDockSite->AddControlBar(this);
}
return 0;
}

如果需要支持Tooltips,則OnCreate調用EnableTooltips。

m_pDockSite是CControlBar的和泊位相關的成員變量,這裡把它初始化為擁有工具欄的父邊框窗口,該邊框窗口把控制條加入其控制條列表m_listControlBars中。

在處理WM_CREATE之前,派生類先處理消息WM_NCCREAE。例如,CToolBar覆蓋了OnNcCreate函數。

處理WM_NCCREATE消息

CToolBar對WM_NCCREATE消息的處理如下:

BOOL CToolBar::OnNcCreate(LPCREATESTRUCT lpCreateStruct)
{
if (!CControlBar::OnNcCreate(lpCreateStruct))
return FALSE;
// if the owner was set before the toolbar was created, set it now
if (m_hWndOwner != NULL)
DefWindowProc(TB_SETPARENT, (WPARAM)m_hWndOwner, 0);
DefWindowProc(TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
return TRUE;
}

CToolBar覆蓋CcontrolBar的該函數用來設置工具條的所屬窗口和描述工具條按鈕結構的大小,這兩個動作都是通過給工具條窗口發送消息來實現的。因為這些消息被送給控制窗口類的窗口過程(Windows提供的)來處理,所以直接調用DefWindowProc,省卻了消息發送的過程。

在控制窗口創建之後,對於工具條來說,下一步就是向工具欄添加按鈕。

向工具欄添加按鈕

通過函數LoadToolBar完成向工具欄添加按鈕的任務,其實現如下:

BOOL CToolBar::LoadToolBar(LPCTSTR lpszResourceName)
{
ASSERT_VALID(this);
ASSERT(lpszResourceName != NULL);
//查找並確認按鈕位圖、字符串等資源的位置
HINSTANCE hInst = AfxFindResourceHandle(lpszResourceName, RT_TOOLBAR);
HRSRC hRsrc = ::FindResource(hInst, lpszResourceName, RT_TOOLBAR);
if (hRsrc == NULL)
return FALSE;
//鎖定資源
HGLOBAL hGlobal = LoadResource(hInst, hRsrc);
if (hGlobal == NULL)
return FALSE;
CToolBarData* pData = (CToolBarData*)LockResource(hGlobal);
if (pData == NULL)
return FALSE;
ASSERT(pData->wVersion == 1);
//復制與各個位圖對應的命令ID到數組pItem
UINT* pItems = new UINT[pData->wItemCount];
for (int i = 0; i < pData->wItemCount; i++)
pItems[i] = pData->items()[i];
//添加按鈕到工具欄,指定各個按鈕對應的ID
BOOL bResult = SetButtons(pItems, pData->wItemCount);
delete[] pItems;
//設置按鈕的位圖
if (bResult)
{
// set new sizes of the buttons
CSize sizeimage(pData->wWidth, pData->wHeight);
CSize sizeButton(pData->wWidth + 7, pData->wHeight + 7);
SetSizes(sizeButton, sizeimage);
// load bitmap now that sizes are known by the toolbar control
bResult = LoadBitmap(lpszResourceName);
}
UnlockResource(hGlobal);
FreeResource(hGlobal);
return bResult;
}

LoadToolBar函數的參數指定了資源。ToolBar資源的類型是RT_TOOLBAR,ToolBar位圖資源的類型是RT_BITMAP。

在RT_TOOLBAR類型的資源讀入內存之後,可以用CToolBarData結構描述。一個這樣的結構包括了ToolBar資源的如下信息:

工具條位圖的版本,寬度,高度,個數,各個位圖對應的命令ID。

然後,LoadToolBar把這些命令ID被復制到數組pItem中;根據位圖寬度、高度形成按鈕尺寸sizeButton和位圖尺寸sizeimage。

接著,調用SetBottons添加按鈕到工具欄,把各個按鈕和命令ID對應起來;調用SetSizes設置按鈕和位圖的尺寸大小;調用LoadBitmap添加或者取代工具條的位圖列表。這些動作都是調用工具欄“窗口類”的窗口過程完成的。例如,SetButtons的實現:

BOOL CToolBar::SetButtons(const UINT* lpIDArray, int nIDCount)
{
ASSERT_VALID(this);
ASSERT(nIDCount >= 1); // must be at least one of them
ASSERT(lpIDArray == NULL ||
AfxIsValidAddress(lpIDArray, sizeof(UINT) * nIDCount, FALSE));
//首先,刪除工具條中現有的按鈕
int nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
while (nCount--)
VERIFY(DefWindowProc(TB_DELETEBUTTON, 0, 0));
if (lpIDArray != NULL)//命令ID數組非空
{
//添加新按鈕
TBBUTTON button; memset(&button, 0, sizeof(TBBUTTON));
int iimage = 0;
for (int i = 0; i < nIDCount; i++)
{
button.fsState = TBSTATE_ENABLED;
if ((button.idCommand = *lpIDArray++) == 0)
{
//按鈕之間分隔
button.fsStyle = TBSTYLE_SEP;
//按鈕之間隔8個像素
button.iBitmap = 8;
}
else
{
//有位圖和命令ID的按鈕
button.fsStyle = TBSTYLE_BUTTON;
button.iBitmap = iimage++;//設置位圖索引
}
//添加按鈕
if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&button))
return FALSE;
}
}
else//命令ID數組空,添加空按鈕
{
TBBUTTON button; memset(&button, 0, sizeof(TBBUTTON));
button.fsState = TBSTATE_ENABLED;
for (int i = 0; i < nIDCount; i++)
{
ASSERT(button.fsStyle == TBSTYLE_BUTTON);
if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&button))
return FALSE;
}
}
//記錄按鈕個數到成員變量m_nCount中
m_nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
//稍後放置按鈕
m_bDelayedButtonLayout = TRUE;
return TRUE;
}

函數的參數1是一個數組,數組的各個元素就是命令ID;參數2是按鈕的個數。首先,SetButtons刪除工具條原來的按鈕;然後,添加新的按鈕,若命令ID數組非空,則把每一個按鈕和命令ID對應並分配位圖索引,否則設置空按鈕並返回FALSE;最後,記錄按鈕個數。

從SetButtons的實現可以看出,對工具條的所有操作都是通過工具條“窗口類”的窗口過程完成的,SetSizes、LoadBitmap也是如此,這裡不作討論。

狀態欄和對話框工具欄的創建

至此,分析了MFC創建工具條窗口的過程。對於狀態欄和對話框工具欄有類似的步驟,但也有不同之處。

CStatusBar的Create使用“msctls_statusbar32”“窗口類”創建狀態欄,窗口ID為AFX_IDW_STATUS_BAR(0XE801),然後通過成員函數SetIndictors給狀態欄分格,類似於給工具條添加按鈕的過程,它實際上是通過狀態欄“窗口類”的窗口過程完成的。

CDialogBar的Create使用CreateDlg創建對話框工具欄,類似於CFormView的過程。在工具欄窗口創建之後,要添加到父窗口的工具欄列表中,這通過CControlBar::OnCreate完成。這樣創建的結果導致窗口過程使用MFC的統一的窗口過程,相應“窗口類”的窗口過程也將在缺省處理中被調用,這一點如同CFormView和CDialog中所描述的。在初始化對話框的時候完成了各個控制按鈕的添加。

CStatusBar和CdialogBar都沒有處理消息WM_NCCREATE。

關於CStautsBar和CDialogBar創建過程的具體實現,這裡不作詳細討論了。

控制條的銷毀

描述了控制條的創建,順便考察其銷毀的設計。

工具條、狀態欄等這些控制窗口都要使用DestroyWindow來銷毀,所有有關操作集中由CControlBar處理。CControlBar覆蓋了虛擬函數DestroyWindow、PostNcDestroy和消息處理函數OnDestroy。

當然,各個派生類的虛擬析構函數被實現。如果成員變量m_bAutoDelete為TRUE,則動態創建的MFC窗口將自動銷毀。

處理控制條的位置

計算控制條位置的過程和算法

工具條等控制條是作為一個子窗口在父邊框窗口內顯示的。為了處理控制條的布置(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.
");
}

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,這裡不作進一步的分析。

工具條、狀態欄和邊框窗口的接口

應用程序在狀態欄中顯示信息

MFC內部通過給邊框窗口發送消息WM_SETMESSAGESTRING、WM_POPMESSAGESTRING的方式在狀態欄中顯示信息。這兩個消息在afxpriv.h裡頭定義。

WM_SETMESSAGESTRING消息表示在狀態欄中顯示和某個ID對應的字符串信息或者指定的字符串信息,消息參數wParam指定了字符串資源ID,消息參數lParam指定了字符串指針,兩個消息參數只有一個有用。一般,一個命令ID對應了一個字符串ID,對應的字符串是命令ID的說明。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved