Click here to Skip to main content
Click here to Skip to main content

Tagged as

Better docking of Toolbars

, 29 Mar 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

  1. 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.
  2. 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.
  3. 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:

  1. "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.
  2. "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:

  1. 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.] 
  2. In MainFrm.cpp, in CMainFrame::OnCreate() function, after creating the toolbar object, you need to call:
    1. EnableCustomToolbarDocking(CBRS_ALIGN_TOP); This call ensure that CCustomDockContext class object is set as the toolbar's dock context object.
    2. 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;

//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:

  1. 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;
    }
  2. 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;
    }
  3. 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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nataraj1978
Team Leader GE Intelligent Platforms
India India
No Biography provided

Comments and Discussions

 
GeneralDouble click on floating toolbar Pinmembercodevisio22-Apr-11 9:17 
GeneralA sample project would be nice... PinmemberG A McHale27-Mar-10 22:39 
GeneralRe: A sample project would be nice... PinmemberNataraj197828-Mar-10 3:36 
GeneralRe: A sample project would be nice... PinmemberAlison P29-Mar-10 2:23 
GeneralRe: A sample project would be nice... PinmemberNataraj197829-Mar-10 3:25 
GeneralRe: A sample project would be nice... PinmemberAlison P29-Mar-10 5:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150129.1 | Last Updated 29 Mar 2010
Article Copyright 2010 by Nataraj1978
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid