CStatusBar、 CToolBar、 CDialogBar、 CDockBar这几个类都继承自CControlBar,
而RepositionBars() 可以用来计算CControlBar的布局(位置和大小)。
void RepositionBars(UINT nIDFirst,
UINT nIDLast,
UINT nIDLeftOver,
UINT nFlag = reposDefault,
LPRECT lpRectParam = NULL,
LPCRECT lpRectClient = NULL,
BOOL bStretch = TRUE) ;
具体每个参数的含义可以去查MSDN,这里主要说明这个函数是怎么起作用的。
MSDN给的解释很简单,就是说调用RepositionBars会给窗口内的所有CControlBar发送一条WM_SIZEPARENT 消息,再具体的说明就没了。
不多废话,直接上代码,下面是为了便于理解删减过的代码:
void CWnd::RepositionBars(UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver,
UINT nFlags, LPRECT lpRectParam, LPCRECT lpRectClient, BOOL bStretch)
{
AFX_SIZEPARENTPARAMS layout;
HWND hWndLeftOver = NULL;
//获取客户区矩形
GetClientRect(&layout.rect); // starting rect comes from client rect
//给每个顶级子窗口发送 WM_SIZEPARENT 消息
for (HWND hWndChild = ::GetTopWindow(m_hWnd); hWndChild != NULL;
hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT))
{
UINT_PTR nIDC = _AfxGetDlgCtrlID(hWndChild);
CWnd* pWnd = CWnd::FromHandlePermanent(hWndChild);
if (nIDC == nIDLeftOver)
hWndLeftOver = hWndChild;
else if (nIDC >= nIDFirst && nIDC <= nIDLast && pWnd != NULL)
::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&layout);
}
}
可以看到MSDN指出的发送 WM_SIZEPARENT 消息,而且这个消息是发送给所有顶级子窗口的,那问题来了:假如同时存在工具栏(CToolBar)和状态栏(CStatusBar),那怎么让这2个控件分别计算自身的坐标?
继续看代码,看控件窗口收到 WM_SIZEPARENT 消息是怎么处理的:
LRESULT CControlBar::OnSizeParent(WPARAM, LPARAM lParam)
{
AFX_SIZEPARENTPARAMS* lpLayout = (AFX_SIZEPARENTPARAMS*)lParam;
DWORD dwStyle = RecalcDelayShow(lpLayout);
if ((dwStyle & WS_VISIBLE) && (dwStyle & CBRS_ALIGN_ANY) != 0)
{
// align the control bar
CRect rect;
rect.CopyRect(&lpLayout->rect);
CSize sizeAvail = rect.Size(); // maximum size available
// get maximum requested size
DWORD dwMode = lpLayout->bStretch ? LM_STRETCH : 0;
if ((m_dwStyle & CBRS_SIZE_DYNAMIC) && m_dwStyle & CBRS_FLOATING)
dwMode |= LM_HORZ | LM_MRUWIDTH;
else if (dwStyle & CBRS_ORIENT_HORZ)
dwMode |= LM_HORZ | LM_HORZDOCK;
else
dwMode |= LM_VERTDOCK;
CSize size = CalcDynamicLayout(-1, dwMode);
size.cx = min(size.cx, sizeAvail.cx);
size.cy = min(size.cy, sizeAvail.cy);
if (dwStyle & CBRS_ORIENT_HORZ)
{
lpLayout->sizeTotal.cy += size.cy;
lpLayout->sizeTotal.cx = max(lpLayout->sizeTotal.cx, size.cx);
if (dwStyle & CBRS_ALIGN_TOP)
lpLayout->rect.top += size.cy;
else if (dwStyle & CBRS_ALIGN_BOTTOM)
{
rect.top = rect.bottom - size.cy;
lpLayout->rect.bottom -= size.cy;
}
}
else if (dwStyle & CBRS_ORIENT_VERT)
{
lpLayout->sizeTotal.cx += size.cx;
lpLayout->sizeTotal.cy = max(lpLayout->sizeTotal.cy, size.cy);
if (dwStyle & CBRS_ALIGN_LEFT)
lpLayout->rect.left += size.cx;
else if (dwStyle & CBRS_ALIGN_RIGHT)
{
rect.left = rect.right - size.cx;
lpLayout->rect.right -= size.cx;
}
}
else
{
ASSERT(FALSE); // can never happen
}
rect.right = rect.left + size.cx;
rect.bottom = rect.top + size.cy;
// only resize the window if doing layout and not just rect query
if (lpLayout->hDWP != NULL)
AfxRepositionWindow(lpLayout, m_hWnd, &rect);
}
return 0;
}
可以看到,这个函数主要就是根据 dwStyle 来计算窗口位置和大小,而dwStyle就是状态栏、菜单栏这些控件的样式,在Create时就已经默认定好了。
下面是状态栏的Create函数,第2个参数就是dwStyle:
//CStatusBar::Create
virtual BOOL Create(
CWnd* pParentWnd,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM,
UINT nID = AFX_IDW_STATUS_BAR);
这个CBRS_BOTTOM样式就是调用RepositionBars计算状态栏位置的关键,这个样式表示控件位于窗口的底部,所以计算状态栏位置的时候:
else if (dwStyle & CBRS_ALIGN_BOTTOM)
{
rect.top = rect.bottom - size.cy;
lpLayout->rect.bottom -= size.cy;
}
即:状态栏顶部y坐标 = 窗口底部y坐标 - 状态栏的高度
同理,调用RepositionBars工具栏会移动到窗口客户区最上面,也是因为样式CBRS_TOP:
//CToolBar::Create
virtual BOOL Create(
CWnd* pParentWnd,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP,
UINT nID = AFX_IDW_TOOLBAR);
因此,调用一次RepositionBars,会导致状态栏移动到窗口底部,工具栏移动到窗口顶部。