DataMekanix Home Docking Windows Made Easy


home / articles / how to dock bars side by side


 
  Environment:

VC5, VC6; MFC
Win9x, NT4, Win2k, WinMe

History:

First Released: 1998
Date Posted: March 04, 2001
Article Last Updated: March 04, 2001
Code Last Updated: March 04, 2001

How to dock bars side by side

by Cristi Posea

Introduction

Let's say you have 2 toolbars and you dock them at the top of the mainframe, as in this example:

    // ...
    DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_TOP);
    DockControlBar(&m_wndMyBar2, AFX_IDW_DOCKBAR_TOP);
    // ...

This code will dock them each on one row (they will be stacked). What if you want them docked side-by-side? Well, the CFrameWnd::DockControlBar() can take a third parameter, which is LPCRECT lpRect = NULL. This "determines, in screen coordinates, where the control bar will be docked in the nonclient area of the destination frame window". You should know these coordinates, but in some cases they are hard to be determined if not impossible.

There was an entry in the F.A.Q. page on this topic, and I moved it here:

The easy way

Kirk Stowell wrote a simple function about this, here are a couple of links to it:
http://www.codeguru.com/toolbar/demo_toolbar_d.shtml
http://www.codeproject.com/docking/toolbar_docking.asp

That code was written for toolbars, but any control bar will work the same way. Also, the code does not work when the toolbars are docked vertically (on the left or right sides). Let's refine it a little...

First, you can do it even simpler in CMainFrame::OnCreate():

    CRect rc;
    DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_LEFT);
    RecalcLayout(TRUE);
    m_wndMyBar1.GetWindowRect(rc);
    rc.OffsetRect(0, 1);
    DockControlBar(&m_wndMyBar2, AFX_IDW_DOCKBAR_LEFT, rc);

If you want to dock 3 bars on the left, you should add also:

    RecalcLayout(TRUE);
    m_wndMyBar2.GetWindowRect(rc);
    rc.OffsetRect(0, 1);
    DockControlBar(&m_wndMyBar3, AFX_IDW_DOCKBAR_LEFT, rc);

and so on.

Now, a short explanation:
The 3rd parameter passed to DockControlBar() is that lpRect, in screen coordinates. That's why we use GetWindowRect() for retrieving the rectangle of the previously docked bar. But before we could call GetWindowRect(), we must call RecalcLayout(), otherwise the bars have undetermined positions (and a size of (0x0)).

The line rc.OffsetRect(0, 1); tells that the new bar should be docked below the previous one. If you remove this line, the second bar will be docked above the first one. The same is true for horizontally docked bars, excepting that if you want the second bar to be docked to the right of the first one, you will have rc.OffsetRect(1, 0);.

So, the code below has the same effect, but we will dock the second bar first, then the first one above it:

    CRect rc;
    DockControlBar(&m_wndMyBar2, AFX_IDW_DOCKBAR_LEFT);
    RecalcLayout(TRUE);
    m_wndMyBar2.GetWindowRect(rc);
    DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_LEFT, &rc);

All examples until now assumed one thing, althought you didn't noticed it: the bars must be visible. If they are not, the RecalcLayout() call does not guarantee the bars' window sizes and positions. If this is okay with you, you can stop reading - you have enough information to solve your problem.

DockControlBarNextTo()

The following function works in more cases, and it is easier to use. However, it makes use of undocumented MFC code, so be warned that there is no guarantee it will work with future versions of MFC or Visual C++.
DockControlBarNextTo() needs two pointers: pBar is a pointer to your bar, and pTargetBar is a pointer to a previously docked bar. Note that the target bar must be docked when you call this function. You don't need to specify the dockbar ID, nor bar coordinates, because the code does this for you.

void CMainFrame::DockControlBarNextTo(CControlBar* pBar,
                                      CControlBar* pTargetBar)
{
    ASSERT(pBar != NULL);
    ASSERT(pTargetBar != NULL);
    ASSERT(pBar != pTargetBar);

    // the neighbour must be already docked
    CDockBar* pDockBar = pTargetBar->m_pDockBar;
    ASSERT(pDockBar != NULL);
    UINT nDockBarID = pTargetBar->m_pDockBar->GetDlgCtrlID();
    ASSERT(nDockBarID != AFX_IDW_DOCKBAR_FLOAT);

    bool bHorz = (nDockBarID == AFX_IDW_DOCKBAR_TOP ||
        nDockBarID == AFX_IDW_DOCKBAR_BOTTOM);

    // dock normally (inserts a new row)
    DockControlBar(pBar, nDockBarID);

    // delete the new row (the bar pointer and the row end mark)
    pDockBar->m_arrBars.RemoveAt(pDockBar->m_arrBars.GetSize() - 1);
    pDockBar->m_arrBars.RemoveAt(pDockBar->m_arrBars.GetSize() - 1);

    // find the target bar
    for (int i = 0; i < pDockBar->m_arrBars.GetSize(); i++)
    {
        void* p = pDockBar->m_arrBars[i];
        if (p == pTargetBar) // and insert the new bar after it
            pDockBar->m_arrBars.InsertAt(i + 1, pBar);
    }

    // move the new bar into position
    CRect rBar;
    pTargetBar->GetWindowRect(rBar);
    rBar.OffsetRect(bHorz ? 1 : 0, bHorz ? 0 : 1);
    pBar->MoveWindow(rBar);
}

The function works for bars docked on any side of the frame, and any of these bars can be created invisible. This is especially useful when you have to many bars.

Here is an example:

    DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_TOP);
    DockControlBarNextTo(&m_wndMyBar1, &m_wndMyBar2);
    DockControlBarNextTo(&m_wndMyBar2, &m_wndMyBar3);
    DockControlBar(&m_wndMyBar4, AFX_IDW_DOCKBAR_TOP); // new row
    DockControlBarNextTo(&m_wndMyBar4, &m_wndMyBar5);

Enjoy !


Copyright 1998-2010 DataMekanix. All rights reserved.