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 the CCustomDockContext
class.
Using the Code
I followed the steps mentioned below:
- In Mainfrm.h, replace the toolbar object type from standard
CToolBar
object with CCustomToolbar
[This will allow you to add some custom functionalities to toolbars if required and also required in order to override the MFC's CDockBar
and CDockContext
classes.]
- In MainFrm.cpp, in
CMainFrame::OnCreate()
function, after creating the toolbar object, you need to call:
EnableCustomToolbarDocking(CBRS_ALIGN_TOP);
This call ensure that CCustomDockContext
class object is set as the toolbar's dock context object.
EnableCustomFrameDocking(this, CBRS_ALIGN_ANY);
This call will replace the CDockBar
class object with the CCustomDockBar
class object.
The code snippet is shown below:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
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; }
m_wndToolBar.LoadBitmap(IDB_BITMAP1);
m_wndToolBar.EnableCustomToolbarDocking(CBRS_ALIGN_TOP);
EnableCustomFrameDocking(this, CBRS_ALIGN_ANY);
DockCustomControlBar(&m_wndToolBar);
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)
{
ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY | CBRS_FLOAT_MULTI)) == 0);
ASSERT((dwDockStyle & CBRS_FLOAT_MULTI) == 0);
ASSERT((m_dwStyle & CBRS_SIZE_DYNAMIC) != 0);
m_dwDockStyle = dwDockStyle;
if(m_pDockContext == NULL)
m_pDockContext = new CCustomDockContext(this);
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);
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]);
if( pDock == NULL || ! pDock->IsKindOf(RUNTIME_CLASS(CCustomDockBar)) )
{
BOOL bNeedDelete = ! pDock->m_bAutoDelete;
pDock->m_pDockSite->RemoveControlBar(pDock);
pDock->m_pDockSite = 0; 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);
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);
break;
}
}
}
ASSERT(pDockBar != NULL);
ASSERT(m_listControlBars.Find(pBar) != NULL);
ASSERT(pBar->m_pDockSite == this);
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 {
nTotalWidth += nWidth - afxData.cyBorder2;
nWidth = 0;
if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth)
{
if (nPos == 0) m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL);
m_arrBars.InsertAt(nPosInsAfter+1, pBarIns);
return nPosInsAfter+1;
}
nPosInsAfter = nPos;
}
}
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) {
nTotalWidth += nWidth - afxData.cyBorder2;
nWidth = 0;
if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth)
{
if (nPos == 0) m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL);
m_arrBars.InsertAt(nPosInsAfter+1, pBarIns);
return nPosInsAfter+1;
}
nPosInsAfter = nPos;
}
}
}
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);
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) {
nTotalWidth += nWidth - 2;
nWidth = 0;
if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth)
{
if (nPos == 0) m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL);
m_arrBars.InsertAt(nPosInsAfter+1, pBarIns);
return nPosInsAfter+1;
}
nPosInsAfter = nPos;
}
}
}
m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL);
m_arrBars.InsertAt(nPosInsAfter+1, pBarIns);
return nPosInsAfter+1;
}
Hope this helps!