📄 mfc.txt
字号:
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)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -