1 f////////////////////////////////////////////////////////////////
2 // Copyright 1998 Paul DiLascia
3 // If this code works, it was written by Paul DiLascia.
4 // If not, I don't know who wrote it.
6 // CFixMFCToolBar fixes sizing bugs in MFC CToolBar, so it works with
7 // modern toolbars in versions of comctl32.dll > 4.70.
9 // Most of this code is copied from MFC, with slight modifications marked "PD"
13 #include "UIModulVer.h"
18 static char THIS_FILE[] = __FILE__;
21 IMPLEMENT_DYNAMIC(CFixMFCToolBar, CToolBar)
23 BEGIN_MESSAGE_MAP(CFixMFCToolBar, CToolBar)
24 ON_MESSAGE(TB_SETBITMAPSIZE, OnSetBitmapSize)
25 ON_MESSAGE(TB_SETBUTTONSIZE, OnSetButtonSize)
26 ON_MESSAGE(WM_SETTINGCHANGE, OnSettingChange)
27 ON_MESSAGE(WM_SETFONT, OnSettingChange)
31 // This function gets the version number of comctl32.dll.
33 static int GetVerComCtl32()
37 VERIFY(ver.DllGetVersion(_T("comctl32.dll"), dvi));
38 return dvi.dwMajorVersion*100 + dvi.dwMinorVersion;
41 int CFixMFCToolBar::iVerComCtl32 = GetVerComCtl32();
43 CFixMFCToolBar::CFixMFCToolBar()
45 m_bShowDropdownArrowWhenVertical = FALSE;
48 CFixMFCToolBar::~CFixMFCToolBar()
53 // These functions duplicate functionalityin VC 6.0
54 // Need to set transparent/flat style before setting
55 // button/image size or font to allow zero-height border.
57 LRESULT CFixMFCToolBar::OnSetBitmapSize(WPARAM, LPARAM lp)
59 return OnSizeHelper(m_sizeImage, lp);
61 LRESULT CFixMFCToolBar::OnSetButtonSize(WPARAM, LPARAM lp)
63 return OnSizeHelper(m_sizeButton, lp);
65 LRESULT CFixMFCToolBar::OnSettingChange(WPARAM, LPARAM lp)
67 return OnSizeHelper(CSize(0,0), lp);
69 LRESULT CFixMFCToolBar::OnSizeHelper(CSize& sz, LPARAM lp)
71 ASSERT(iVerComCtl32 > 0);
72 BOOL bModStyle =FALSE;
74 if (iVerComCtl32 >= 471) {
76 bModStyle = ModifyStyle(0, TBSTYLE_TRANSPARENT|TBSTYLE_FLAT);
79 LRESULT lRet = Default();
84 SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
91 // This is the all-important function that gets the true size of a button,
92 // instead of using m_sizeButton. And it's virtual, so you can override if
93 // my algorithm doesn't work, as will surely be the case in some circumstances.
95 CSize CFixMFCToolBar::GetButtonSize(TBBUTTON* pData, int iButton)
97 ASSERT(iVerComCtl32 > 0);
99 // Get the actual size of the button, not what's in m_sizeButton.
100 // Make sure to do SendMessage instead of calling MFC's GetItemRect,
101 // which has all sorts of bad side-effects! (Go ahead, take a look at it.)
104 SendMessage(TB_GETITEMRECT, iButton, (LPARAM)&rc);
105 CSize sz = rc.Size();
108 // Now must do special case for various versions of comctl32.dll,
110 DWORD dwStyle = pData[iButton].fsStyle;
111 if ((pData[iButton].fsState & TBSTATE_WRAP)) {
112 if (dwStyle & TBSTYLE_SEP) {
113 // this is the last separator in the row (eg vertically docked)
114 // fudge the height, and ignore the width. TB_GETITEMRECT will return
115 // size = (8 x 22) even for a separator in vertical toolbar
117 if (iVerComCtl32 <= 470)
118 sz.cy -= 3; // empircally good fudge factor
119 else if (iVerComCtl32 != 471)
121 sz.cx = 0; // separator takes no width if it's the last one
123 } else if (dwStyle & TBSTYLE_DROPDOWN &&
124 !m_bShowDropdownArrowWhenVertical) {
125 // ignore width of dropdown
134 // Part 2 of correction for MFC is to recalculate everything when the bar
135 // goes from docked to undocked because the AdjustSize calculation happens
136 // when the bar is in the old state, and thus wrong. After the bar is
137 // docked/undocked, I'll recalculate with the new style and commit the change.
139 void CFixMFCToolBar::OnBarStyleChange(DWORD dwOldStyle, DWORD dwNewStyle)
141 CToolBar::OnBarStyleChange(dwOldStyle, dwNewStyle);
143 if (dwOldStyle != dwNewStyle) {
145 if ((dwNewStyle & CBRS_SIZE_DYNAMIC) && (dwNewStyle & CBRS_FLOATING))
146 dwMode = LM_HORZ | LM_MRUWIDTH;
147 else if (dwNewStyle & CBRS_ORIENT_HORZ)
148 dwMode = LM_HORZ | LM_HORZDOCK;
150 dwMode = LM_VERTDOCK;
152 CalcDynamicLayout(-1, dwMode | LM_COMMIT);
156 ////////////////////////////////////////////////////////////////
157 // Stuff below is copied from MFC; only mod is to call GetButtonSize.
165 CSize CFixMFCToolBar::CalcSize(TBBUTTON* pData, int nCount)
167 ASSERT(pData != NULL && nCount > 0);
170 CSize sizeResult(0,0);
171 int cyTallestOnRow = 0;
173 for (int i = 0; i < nCount; i++)
175 if (pData[i].fsState & TBSTATE_HIDDEN)
179 // Load actual size of button into a local variable
180 // called m_sizeButton. C++ will use this instead of
181 // CToolBar::m_sizeButton.
183 CSize m_sizeButton = GetButtonSize(pData, i);
186 // I also changed the logic below to be more correct.
187 cyTallestOnRow = max(cyTallestOnRow, m_sizeButton.cy);
188 sizeResult.cx = max(cur.x + m_sizeButton.cx, sizeResult.cx);
189 sizeResult.cy = max(cur.y + m_sizeButton.cy, sizeResult.cy);
191 cur.x += m_sizeButton.cx - CX_OVERLAP;
193 if (pData[i].fsState & TBSTATE_WRAP)
196 cur.y += cyTallestOnRow;
198 if (pData[i].fsStyle & TBSTYLE_SEP)
199 cur.y += m_sizeButton.cy;
205 int CFixMFCToolBar::WrapToolBar(TBBUTTON* pData, int nCount, int nWidth)
207 ASSERT(pData != NULL && nCount > 0);
211 for (int i = 0; i < nCount; i++)
213 pData[i].fsState &= ~TBSTATE_WRAP;
215 if (pData[i].fsState & TBSTATE_HIDDEN)
221 // Load actual size of button into a local variable
222 // called m_sizeButton. C++ will use this instead of
223 // CToolBar::m_sizeButton.
225 CSize m_sizeButton = GetButtonSize(pData, i);
227 dx = m_sizeButton.cx;
228 dxNext = dx - CX_OVERLAP;
233 for (int j = i; j >= 0 && !(pData[j].fsState & TBSTATE_WRAP); j--)
235 // Find last separator that isn't hidden
236 // a separator that has a command ID is not
237 // a separator, but a custom control.
238 if ((pData[j].fsStyle & TBSTYLE_SEP) &&
239 (pData[j].idCommand == 0) &&
240 !(pData[j].fsState & TBSTATE_HIDDEN))
242 bFound = TRUE; i = j; x = 0;
243 pData[j].fsState |= TBSTATE_WRAP;
250 for (int j = i - 1; j >= 0 && !(pData[j].fsState & TBSTATE_WRAP); j--)
252 // Never wrap anything that is hidden,
253 // or any custom controls
254 if ((pData[j].fsState & TBSTATE_HIDDEN) ||
255 ((pData[j].fsStyle & TBSTYLE_SEP) &&
256 (pData[j].idCommand != 0)))
259 bFound = TRUE; i = j; x = 0;
260 pData[j].fsState |= TBSTATE_WRAP;
274 //////////////////////////////////////////////////////////////////////////
276 // Functions below are NOT actually modified. They're only here because they
277 // calls the modified functions above, which are NOT virtual.
278 //////////////////////////////////////////////////////////////////////////
280 void CFixMFCToolBar::SizeToolBar(TBBUTTON* pData, int nCount, int nLength, BOOL bVert)
282 ASSERT(pData != NULL && nCount > 0);
286 int nMin, nMax, nTarget, nCurrent, nMid;
288 // Wrap ToolBar as specified
290 nTarget = WrapToolBar(pData, nCount, nMax);
292 // Wrap ToolBar vertically
294 nCurrent = WrapToolBar(pData, nCount, nMin);
296 if (nCurrent != nTarget)
300 nMid = (nMin + nMax) / 2;
301 nCurrent = WrapToolBar(pData, nCount, nMid);
303 if (nCurrent == nTarget)
309 WrapToolBar(pData, nCount, nMax);
316 CSize size = CalcSize(pData, nCount);
317 WrapToolBar(pData, nCount, size.cx);
321 CSize sizeMax, sizeMin, sizeMid;
323 // Wrap ToolBar vertically
324 WrapToolBar(pData, nCount, 0);
325 sizeMin = CalcSize(pData, nCount);
327 // Wrap ToolBar horizontally
328 WrapToolBar(pData, nCount, 32767);
329 sizeMax = CalcSize(pData, nCount);
331 while (sizeMin.cx < sizeMax.cx)
333 sizeMid.cx = (sizeMin.cx + sizeMax.cx) / 2;
334 WrapToolBar(pData, nCount, sizeMid.cx);
335 sizeMid = CalcSize(pData, nCount);
337 if (nLength < sizeMid.cy)
339 if (sizeMin == sizeMid)
341 WrapToolBar(pData, nCount, sizeMax.cx);
346 else if (nLength > sizeMid.cy)
354 struct _AFX_CONTROLPOS
360 CSize CFixMFCToolBar::CalcLayout(DWORD dwMode, int nLength)
363 ASSERT(::IsWindow(m_hWnd));
364 if (dwMode & LM_HORZDOCK)
365 ASSERT(dwMode & LM_HORZ);
369 CSize sizeResult(0,0);
373 nCount = SendMessage(TB_BUTTONCOUNT, 0, 0);
377 pData = new TBBUTTON[nCount];
378 for (i = 0; i < nCount; i++)
379 GetButton(i, &pData[i]); // **PD** renamed from _GetButton
385 if (!(m_dwStyle & CBRS_SIZE_FIXED))
387 BOOL bDynamic = m_dwStyle & CBRS_SIZE_DYNAMIC;
389 if (bDynamic && (dwMode & LM_MRUWIDTH))
390 SizeToolBar(pData, nCount, m_nMRUWidth);
391 else if (bDynamic && (dwMode & LM_HORZDOCK))
392 SizeToolBar(pData, nCount, 32767);
393 else if (bDynamic && (dwMode & LM_VERTDOCK))
394 SizeToolBar(pData, nCount, 0);
395 else if (bDynamic && (nLength != -1))
397 CRect rect; rect.SetRectEmpty();
398 CalcInsideRect(rect, (dwMode & LM_HORZ));
399 BOOL bVert = (dwMode & LM_LENGTHY);
400 int nLen = nLength + (bVert ? rect.Height() : rect.Width());
402 SizeToolBar(pData, nCount, nLen, bVert);
404 else if (bDynamic && (m_dwStyle & CBRS_FLOATING))
405 SizeToolBar(pData, nCount, m_nMRUWidth);
407 SizeToolBar(pData, nCount, (dwMode & LM_HORZ) ? 32767 : 0);
410 sizeResult = CalcSize(pData, nCount);
412 if (dwMode & LM_COMMIT)
414 _AFX_CONTROLPOS* pControl = NULL;
415 int nControlCount = 0;
416 BOOL bIsDelayed = m_bDelayedButtonLayout;
417 m_bDelayedButtonLayout = FALSE;
419 for(int i = 0; i < nCount; i++)
420 if ((pData[i].fsStyle & TBSTYLE_SEP) && (pData[i].idCommand != 0))
423 if (nControlCount > 0)
425 pControl = new _AFX_CONTROLPOS[nControlCount];
428 for(int i = 0; i < nCount; i++)
430 if ((pData[i].fsStyle & TBSTYLE_SEP) && (pData[i].idCommand != 0))
432 pControl[nControlCount].nIndex = i;
433 pControl[nControlCount].nID = pData[i].idCommand;
436 GetItemRect(i, &rect);
437 ClientToScreen(&rect);
438 pControl[nControlCount].rectOldPos = rect;
445 if ((m_dwStyle & CBRS_FLOATING) && (m_dwStyle & CBRS_SIZE_DYNAMIC))
446 m_nMRUWidth = sizeResult.cx;
447 for (i = 0; i < nCount; i++)
448 SetButton(i, &pData[i]); // **PD** renamed from _SetButton
450 if (nControlCount > 0)
452 for (int i = 0; i < nControlCount; i++)
454 CWnd* pWnd = GetDlgItem(pControl[i].nID);
458 pWnd->GetWindowRect(&rect);
459 CPoint pt = rect.TopLeft() - pControl[i].rectOldPos.TopLeft();
460 GetItemRect(pControl[i].nIndex, &rect);
461 pt = rect.TopLeft() + pt;
462 pWnd->SetWindowPos(NULL, pt.x, pt.y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
467 m_bDelayedButtonLayout = bIsDelayed;
472 //BLOCK: Adjust Margins
474 CRect rect; rect.SetRectEmpty();
475 CalcInsideRect(rect, (dwMode & LM_HORZ));
476 sizeResult.cy -= rect.Height();
477 sizeResult.cx -= rect.Width();
479 CSize size = CControlBar::CalcFixedLayout((dwMode & LM_STRETCH), (dwMode & LM_HORZ));
480 sizeResult.cx = max(sizeResult.cx, size.cx);
481 sizeResult.cy = max(sizeResult.cy, size.cy);
486 CSize CFixMFCToolBar::CalcFixedLayout(BOOL bStretch, BOOL bHorz)
488 DWORD dwMode = bStretch ? LM_STRETCH : 0;
489 dwMode |= bHorz ? LM_HORZ : 0;
491 return CalcLayout(dwMode);
494 CSize CFixMFCToolBar::CalcDynamicLayout(int nLength, DWORD dwMode)
496 if ((nLength == -1) && !(dwMode & LM_MRUWIDTH) && !(dwMode & LM_COMMIT) &&
497 ((dwMode & LM_HORZDOCK) || (dwMode & LM_VERTDOCK)))
499 return CalcFixedLayout(dwMode & LM_STRETCH, dwMode & LM_HORZDOCK);
501 return CalcLayout(dwMode, nLength);
504 /////////////////////////////////////////////////////////////////////////////
505 // CToolBar attribute access
507 // **PD** I renamed this from _GetButton.
509 void CFixMFCToolBar::GetButton(int nIndex, TBBUTTON* pButton) const
511 CToolBar* pBar = (CToolBar*)this;
512 VERIFY(pBar->SendMessage(TB_GETBUTTON, nIndex, (LPARAM)pButton));
513 // TBSTATE_ENABLED == TBBS_DISABLED so invert it
514 pButton->fsState ^= TBSTATE_ENABLED;
517 // **PD** I renamed this from _SetButton.
519 void CFixMFCToolBar::SetButton(int nIndex, TBBUTTON* pButton)
521 // get original button state
523 VERIFY(SendMessage(TB_GETBUTTON, nIndex, (LPARAM)&button));
525 // prepare for old/new button comparsion
526 button.bReserved[0] = 0;
527 button.bReserved[1] = 0;
528 // TBSTATE_ENABLED == TBBS_DISABLED so invert it
529 pButton->fsState ^= TBSTATE_ENABLED;
530 pButton->bReserved[0] = 0;
531 pButton->bReserved[1] = 0;
533 // nothing to do if they are the same
534 if (memcmp(pButton, &button, sizeof(TBBUTTON)) != 0)
536 // don't redraw everything while setting the button
537 DWORD dwStyle = GetStyle();
538 ModifyStyle(WS_VISIBLE, 0);
539 VERIFY(SendMessage(TB_DELETEBUTTON, nIndex, 0));
540 VERIFY(SendMessage(TB_INSERTBUTTON, nIndex, (LPARAM)pButton));
541 ModifyStyle(0, dwStyle & WS_VISIBLE);
543 // invalidate appropriate parts
544 if (((pButton->fsStyle ^ button.fsStyle) & TBSTYLE_SEP) ||
545 ((pButton->fsStyle & TBSTYLE_SEP) && pButton->iBitmap != button.iBitmap))
547 // changing a separator
552 // invalidate just the button
554 if (SendMessage(TB_GETITEMRECT, nIndex, (LPARAM)&rect))
555 InvalidateRect(rect, FALSE); // don't erase background