Better docking of Toolbars





4.00/5 (4 votes)
Workaround for toolbar docking issue in VC6 applications
Introduction
I have been working on developing an application in VC6 for quite sometime. In this application, I have more than 20 toolbars. But at any point of time, all the toolbars would not be shown to the user as some of them were hidden always, while some were made available to users depending on the mode of operation. All toolbars that were not applicable for the current mode of operation were put in hidden mode. This was working fine when I had only few toolbars. But as the number of toolbars increased, I found that the toolbars would not retain their states, or when I tried to move them from one row to another, they would jump and sit in a different row and move the other toolbars around.
Background
In order to resolve this issue, I searched CodeProject and other sites to see if someone else had faced this problem. While searching, I came across quite a few good links. Some of them are mentioned below:
- Docking Toolbars Side-By-Side [toolbar_docking.aspx] by Kirk Stowell. This link was useful when I was starting the application... but after starting, if I tried to move the application toolbars, I was back to the problem of positions not being retained when moving them around. Also, this would make toolbars to be always placed in a pre-determined order and not in the order that user had placed them in, before closing the application.
- How to dock bars side by side [http://www.datamekanix.com/articles/side-by-side/] by Cristi Posea. This one, similar to the first link mentioned would do the docking of toolbars in a predefined format.
- By doing further search, I found that there has been an issue logged in Microsoft's bug logging site, that described the problem that I have been facing. [http://connect.microsoft.com/VisualStudio/feedback/details/100915/cdockbar-insert-bug]. As mentioned at the end of this link, the fix was made available in Visual Studio 2005 SP1. This was not of much help to me as I am still using VC6.
Since I wanted to have a solution for the "toolbar jumping and not retaining their position" issue in VC6 that had the fix done by Microsoft in Visual Studio 2005 SP1, I decided to write a custom toolbar class and then use that class to implement a custom DockBar
and DockContext
class in order to make the framework allow me to handle the toolbar docking. That way, I thought I could implement the fix done by Microsoft for VC8 in VC6 itself. But after doing all the steps mentioned in the article, I still found that the issue of toolbars jumping was still present. I therefore made changes such that I had fix done by Microsoft [as found in the source code of CDockBar
in VC8] and also added a change of my own so that the toolbars stopped jumping around.
With this idea in mind, I searched my favorite CodeProject site for articles on custom toolbars and came across a couple of good articles. They are listed below:
- "ToolBar with Customization and Controls" [toolbarex.aspx] by Deepak Khajuria. Using the concept from this article, I made code changes to fix the toolbar docking issue.
- "
CSizingControlBar
- a resizable control bar" [sizecbar.aspx] by Cristi Posea. This helped me to customize theCCustomDockContext
class.
Using the Code
I followed the steps mentioned below:
- In Mainfrm.h, replace the toolbar object type from standard
CToolBar
object withCCustomToolbar
[This will allow you to add some custom functionalities to toolbars if required and also required in order to override the MFC'sCDockBar
andCDockContext
classes.] - In MainFrm.cpp, in
CMainFrame::OnCreate()
function, after creating the toolbar object, you need to call: EnableCustomToolbarDocking(CBRS_ALIGN_TOP);
This call ensure thatCCustomDockContext
class object is set as the toolbar's dock context object.EnableCustomFrameDocking(this, CBRS_ALIGN_ANY);
This call will replace theCDockBar
class object with theCCustomDockBar
class object.
The code snippet is shown below:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//Create elements of the application, like status bar here...
//create the toolbar...
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
m_wndToolBar.LoadBitmap(IDB_BITMAP1);
//Set the font, window text and other attributes for your toolbar...
//Set the docking for the toolbar. I have used only CBRS_ALIGN_TOP here...
m_wndToolBar.EnableCustomToolbarDocking(CBRS_ALIGN_TOP);
//Setup the custom frame docking for the application.
//I have used the function code from the toolbarex article...
EnableCustomFrameDocking(this, CBRS_ALIGN_ANY);
//This custom dock function will allow us to use
//our CCustomDockBar class implementation...
DockCustomControlBar(&m_wndToolBar);
// Please note: here only one toolbar creation is shown,
//but you will have to do it for all toolbars that are created for the application.
return 0;
}
In your custom toolbar class, implement the EnableCustomToolbarDocking()
function. In this function, the CCustomDockContext
object is set as the toolbar's dock context. The implementation is given below:
void CCustomToolsControlBar::EnableCustomToolbarDocking(DWORD dwDockStyle)
{
// must be CBRS_ALIGN_XXX or CBRS_FLOAT_MULTI only
ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY | CBRS_FLOAT_MULTI)) == 0);
// cannot have the CBRS_FLOAT_MULTI style
ASSERT((dwDockStyle & CBRS_FLOAT_MULTI) == 0);
// the bar must have CBRS_SIZE_DYNAMIC style
ASSERT((m_dwStyle & CBRS_SIZE_DYNAMIC) != 0);
m_dwDockStyle = dwDockStyle;
if(m_pDockContext == NULL)
m_pDockContext = new CCustomDockContext(this);
// permanently wire the bar's owner to its current parent
if(m_hWndOwner == NULL)
m_hWndOwner = ::GetParent(m_hWnd);
}
The EnableCustomFrameDocking();
is a global function where we replace the existing CDockBar
objects that is set for the framework with our CCustomDockBar
objects. The implementation for this function is given below:
const DWORD customDockBarMappingInfo[4][2] =
{
{ AFX_IDW_DOCKBAR_TOP, CBRS_TOP },
{ AFX_IDW_DOCKBAR_BOTTOM, CBRS_BOTTOM },
{ AFX_IDW_DOCKBAR_LEFT, CBRS_LEFT },
{ AFX_IDW_DOCKBAR_RIGHT, CBRS_RIGHT },
};
void EnableCustomFrameDocking(CFrameWnd* pFrame, DWORD dwDockStyle)
{
ASSERT_VALID(pFrame);
// must be CBRS_ALIGN_XXX or CBRS_FLOAT_MULTI only
ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY | CBRS_FLOAT_MULTI)) == 0);
pFrame->EnableDocking(dwDockStyle);
for (int i = 0; i < 4; i++)
{
if (customDockBarMappingInfo[i][1] & dwDockStyle & CBRS_ALIGN_ANY)
{
CDockBar* pDock = (CDockBar*)pFrame->GetControlBar
(customDockBarMappingInfo[i][0]);
// make sure the dock bar is of correct type
if( pDock == NULL || ! pDock->IsKindOf(RUNTIME_CLASS(CCustomDockBar)) )
{
BOOL bNeedDelete = ! pDock->m_bAutoDelete;
pDock->m_pDockSite->RemoveControlBar(pDock);
pDock->m_pDockSite = 0; // avoid problems in destroying the dockbar
pDock->DestroyWindow();
if(bNeedDelete)
delete pDock;
pDock = NULL;
}
if(pDock == NULL)
{
pDock = new CCustomDockBar;
ASSERT_VALID(pDock);
if ((!pDock) || (!pDock->Create(pFrame,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
WS_CHILD | WS_VISIBLE | customDockBarMappingInfo[i][1],
customDockBarMappingInfo[i][0])))
{
AfxThrowResourceException();
}
}
}
}
}
Now that we have rewired the CDockBar
and CDockContext
class objects with our CCustomDockBar
and CCustomDockContext
class objects, we need to ensure that the custom functionality is called to do the docking stuff rather than the MFC one. To do this, define the following functions in your MainFrm.h file:
void DockCustomControlBar(CControlBar* pBar, UINT nDockBarID = 0, LPCRECT lpRect = NULL);
void DockCustomControlBar(CControlBar* pBar, CCustomDockBar* pDockBar, LPCRECT lpRect);
The implementation for these functions are given below:
void CMainFrame::DockCustomControlBar
(CControlBar* pBar, UINT nDockBarID, LPCRECT lpRect)
{
CCustomDockBar* pDockBar =
(nDockBarID == 0) ? NULL : (CCustomDockBar*)GetControlBar(nDockBarID);
DockCustomControlBar(pBar, pDockBar, lpRect);
}
void CMainFrame::DockCustomControlBar
(CControlBar* pBar, CCustomDockBar* pDockBar, LPCRECT lpRect)
{
ASSERT(pBar != NULL);
// make sure CControlBar::EnableDocking has been called
ASSERT(pBar->m_pDockContext != NULL);
if (pDockBar == NULL)
{
for (int i = 0; i < 4; i++)
{
if ((dwDockBarMap[i][1] & CBRS_ALIGN_ANY) ==
(pBar->m_dwStyle & CBRS_ALIGN_ANY))
{
pDockBar = (CCustomDockBar*)GetControlBar(dwDockBarMap[i][0]);
ASSERT(pDockBar != NULL);
// assert fails when initial CBRS_ of bar does not
// match available docking sites, as set by EnableDocking()
break;
}
}
}
ASSERT(pDockBar != NULL);
ASSERT(m_listControlBars.Find(pBar) != NULL);
ASSERT(pBar->m_pDockSite == this);
// if this assertion occurred it is because the parent of pBar was not initially
// this CFrameWnd when pBar's OnCreate was called
// i.e. this control bar should have been created
// with a different parent initially
pDockBar->DockControlBar(pBar, lpRect);
}
Points of Interest
This whole exercise of replacing the dock context and dock bar is to allow us to have control over the code used in CDockBar::Insert()
to insert toolbar at our desired location/location of interest, both when moving the toolbars around and also when reopening the application. This is mainly useful when we have lots of toolbars and only few are shown while others are hidden, either due to their non-applicability for the current operation mode or are closed by the user as per his requirement of client area availability.
I have shown below the code of CDockBar::Insert()
function, as it is available in:
- MFC\SRC\BarDock.cpp file in VC6:
int CDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid) { ASSERT_VALID(this); ASSERT(pBarIns != NULL); int nPos = 0; int nPosInsAfter = 0; int nWidth = 0; int nTotalWidth = 0; BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ; for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++) { CControlBar* pBar = GetDockedControlBar(nPos); if (pBar != NULL && pBar->IsVisible()) { CRect rectBar; pBar->GetWindowRect(&rectBar); ScreenToClient(&rectBar); nWidth = max(nWidth, bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1); if (bHorz ? rect.left > rectBar.left : rect.top > rectBar.top) nPosInsAfter = nPos; } else // end of row because pBar == NULL { nTotalWidth += nWidth - afxData.cyBorder2; nWidth = 0; if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth) { if (nPos == 0) // first section m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; } nPosInsAfter = nPos; } } // create a new row m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; }
- Microsoft Visual Studio 8\VC\atlmfc\src\mfc\BarDock.cpp in VC8:
int CDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid) { ENSURE_VALID(this); ENSURE_VALID(pBarIns); int nPos = 0; int nPosInsAfter = 0; int nWidth = 0; int nTotalWidth = 0; BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ; for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++) { CControlBar* pBar = GetDockedControlBar(nPos); if (pBar != NULL && pBar->IsVisible()) { CRect rectBar; pBar->GetWindowRect(&rectBar); ScreenToClient(&rectBar); nWidth = max(nWidth, bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1); if (bHorz ? rect.left > rectBar.left : rect.top > rectBar.top) nPosInsAfter = nPos; } else { if (pBar == NULL) // end of row { nTotalWidth += nWidth - afxData.cyBorder2; nWidth = 0; if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth) { if (nPos == 0) // first section m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; } nPosInsAfter = nPos; } // invisible toolbars are ignored } } // create a new row m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; }
CCustomDockBar::Insert()
function:int CCustomDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid) { ASSERT_VALID(this); ASSERT(pBarIns != NULL); int nPos = 0; int nPosInsAfter = 0; int nWidth = 0; int nTotalWidth = 0; BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ; for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++) { CControlBar* pBar = GetDockedControlBar(nPos); if (pBar != NULL && pBar->IsVisible()) { CRect rectBar; pBar->GetWindowRect(&rectBar); ScreenToClient(&rectBar); nWidth = max(nWidth, bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1); // are we dealing with toolbars placed at the top or bottom? if(bHorz) { if (rect.left > rectBar.left) { nPosInsAfter = nPos; if(nPos + 1 < m_arrBars.GetSize()) { CControlBar* pNextBar = GetDockedControlBar(nPos + 1); if(pNextBar == NULL) { int toolbarRectMidValue = rect.top + (rect.bottom - rect.top) / 2; int currentToolbarFirstQuarter = rectBar.top + (rectBar.bottom - rectBar.top) / 4; int currentToolbarThirdQuarter = rectBar.top + ((rectBar.bottom - rectBar.top) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } } else { int toolbarRectMidValue = rect.top + (rect.bottom - rect.top) / 2; int currentToolbarFirstQuarter = rectBar.top + (rectBar.bottom - rectBar.top) / 4; int currentToolbarThirdQuarter = rectBar.top + ((rectBar.bottom - rectBar.top) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } else { if (rect.top > rectBar.top) { nPosInsAfter = nPos; if(nPos + 1 < m_arrBars.GetSize()) { CControlBar* pNextBar = GetDockedControlBar(nPos + 1); if(pNextBar == NULL) { int toolbarRectMidValue = rect.left + (rect.right - rect.left) / 2; int currentToolbarFirstQuarter = rectBar.left + (rectBar.right - rectBar.left) / 4; int currentToolbarThirdQuarter = rectBar.left + ((rectBar.right - rectBar.left) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } } else { int toolbarRectMidValue = rect.left + (rect.right - rect.left) / 2; int currentToolbarFirstQuarter = rectBar.left + (rectBar.right - rectBar.left) / 4; int currentToolbarThirdQuarter = rectBar.left + ((rectBar.right - rectBar.left) / 4) * 3; if(toolbarRectMidValue >= currentToolbarFirstQuarter && toolbarRectMidValue <= currentToolbarThirdQuarter) { m_arrBars.InsertAt(nPosInsAfter + 1, pBarIns); return (nPosInsAfter + 1); } } } } else { if (pBar == NULL) // end of row { nTotalWidth += nWidth - 2; nWidth = 0; if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth) { if (nPos == 0) // first section m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; } nPosInsAfter = nPos; } // invisible toolbars are ignored } } // create a new row m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL); m_arrBars.InsertAt(nPosInsAfter+1, pBarIns); return nPosInsAfter+1; }
Hope this helps!