⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mfc.txt

📁 MFC工具条和状态栏 Windows控制窗口
💻 TXT
📖 第 1 页 / 共 4 页
字号:

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 + -