Some of the interesting features in Microsoft Office products are those
dealing with toolbars. Office toolbars behave similarly to rebars (coolbars)
in that the user receives direct animated feedback as the bar is dragged and
rearranged. Unlike rebars however, Office toolbars also behave like the
standard MFC toolbars in that the user may "tear" a toolbar off of the
application frame and float it in a separate window.
Visual Studio 6.0 introduced the
CReBar class, and corresponding support
in the MFC App Wizard. The primary drawback to using this new feature is that
you lose the docking features of the traditional MFC toolbars. This article
demonstrates a simple way to endow MFC applications using CReBar with
tear-off toolbar capabilities.
Disclaimer: The solution presented here is intended to implement the basic
functionality described above. Some readers may desire more complex behavior,
such as side docking or bottom docking. I invite you to write such capabilities
and submit them to CodeProject yourself. Unfortunately I don't have the time
to write additional features (and my product does not require them.) I did
play with side-docking for a few moments and decided against getting into a
bloody battle with bad-rebar behavior and ugly MFC docking code.
To get started, download the TearoffRebar.zip file accompanying this article and
run the demo. If you like it and want to add its functionality to your
application, read on.
The tear-off rebar functionality presented here works by subclassing
and retrofitting your
CFrameWnd to use a subclassed
the class that MFC uses to host floating toolbars. The new classes live in the
files TearOffReBar.h and TearOffReBar.cpp. The following instructions assume you
are working with an app-wizard generated project. Your results may vary if you
aren't (you may have to think on your own a bit…)
Step 1. Add the files TearOffReBar.h and TearOffReBar.cpp to your project.
#include TearOffReBar.h in your MainFrm.h file, and add the line
DECLARE_REBAR_DOCKING() inside your
By the way, if you're thinking I've gone a little overboard using title-case with
my file and class names you may be right. However, I started out thinking I really
just wanted a widget to replace
CReBar and I ended up naming my classes to match.
If the world revolved around me, I would have called it
Step 3. Change member variable
m_wndReBar to be of type
CTearOffRebar, instead of
CReBar. If you application doesn't use
CReBar, then you should go make it do so
before going any further. If you're deeply entrenched with traditional MFC
toolbars, you may end up bastardizing some code.
Step 4. In your
method of CMainFrame, make a call to
If you have any calls to plain old
EnableDocking(), remove them or comment them out.
That's about it. Assuming your app doesn't do anything wild or crazy, you should
be able to dock and undock your toolbars from your rebar.
How it all works:
The rebar common control is kind enough to source the notification messages
RBN_ENDDRAG. If we hande these and
WM_MOUSEMOVE, we can
write code to watch for and intervene when the user starts dragging toolbars around:
BOOL CTearOffReBar::OnBeginDrag(NMHDR* pNotifyStruct, LRESULT* result)
NMREBAR* pnmrb = reinterpret_cast<NMREBAR*>(pNotifyStruct);
memset(&rbi, 0, sizeof(rbi));
rbi.cbSize = sizeof(rbi);
RBBIM_CHILD; SendMessage(RB_GETBANDINFO,pnmrb->uBand, (LPARAM)&rbi);
m_hwndBand = rbi.hwndChild;
thing to see here is that we're storing away the hwnd of the bar (band) being
dragged. We do this because we wouldn't be able to get it later otherwise. Remember
that a rebar is a just window that hosts child windows (bands) that are your
actual toolbar windows.
Here's the method where all the fun happens. Try to keep in mind that I
was going for simplicity.
void CTearOffReBar::OnMouseMove(UINT nFlags, CPoint pt)
if (this != GetCapture() || NULL == m_hwndBand)
CRect rcl; GetClientRect(&rcl);
UINT uBand = BandFromHWND(m_hwndBand);
CFrameWnd* pFrame = reinterpret_cast<CFRAMEWND*>(AfxGetMainWnd());
CControlBar* pBar = reinterpret_cast<CCONTROLBAR*>
DWORD dwStyle = pBar->m_dwStyle;
memset(&rbi, 0, sizeof(rbi));
rbi.cbSize = sizeof(rbi);
rbi.fMask = RBBIM_BACKGROUND | RBBIM_CHILDSIZE | RBBIM_COLORS |
RBBIM_HEADERSIZE | RBBIM_IDEALSIZE | RBBIM_ID |
RBBIM_IMAGE | RBBIM_LPARAM | RBBIM_SIZE | RBBIM_STYLE |
RBBIM_TEXT | RBBIM_CHILD;
rbi.cch = sizeof(aszBuff);
rbi.lpText = aszBuff;
SendMessage(RB_GETBANDINFO, uBand, (LPARAM)&rbi);
SendMessage(RB_SHOWBAND, uBand, FALSE);
CMiniReBarDockFrameWnd* pMiniFrame =
m_hwndBand = NULL;
The code is pretty well commented, but I'll give you the play-by-play here too.
What's happening is that the rebar is receiving mouse move messages. We can
check to see if these moves are the result of the user dragging a band by
checking a) whether the rebar has mouse-capture, and b) whether we've stowed
away the hwnd of the band being dragged. If either of these two checks fail,
we're not interested in continuing.
we determine whether we should undock the bar. This check simply determines whether
the current mouse point is outside of the rebar window's rectangle by at
least 10 pixels.
If we've decided to undock the bar, we need to determine what "band" it lives in.
To do this we call
BandFromHWND() , which iterates through the rebar's bands
and searches for the band with the desired hwnd. You might wonder why we
didn't just save away the band-ID in
OnBeginDrag() . The answer is that the band-ID will
change if the band is moved within the rebar, which is the likely case if the user
is dragging the band. Once we have the band-ID, we retrieve and store the band's
attributes in a
REBARBANDINFO structure. We'll be using these stored attributes
later when the band is re-docked in the rebar.
Now we're free to delete the band from the rebar. This is accomplished by sending
the rebar a
RB_DELETEBAND message. The preceding
RB_SHOWBAND message aids in
avoiding some weird redraw issues I encountered when first writing the code.
band gone, we're free to re-parent the toolbar window into a new top-level mini-frame
window. Just about the last thing I wanted to do was write a bunch
of code to do this, so I decided to leverage some code already built into
CFrameWnd::FloatControlBar(). This function takes a control bar and
re-parents it into a newly created
CMiniDockFrameWnd doesn't cut the mustard for us later on, so there is some
other magic to force CFrameWnd to use my new class CMiniReBarDockFrameWnd.
Yes, that's a mouthful.
Once we've called
FloatControlBar(), we need only to perform a few housekeeping
details. The first is to store with the control bar the
REBARBANDINFO of the
old band that was previously home to the bar. This is done with a call to
CMiniReBarDockFrameWnd::SetRebarBandInfo(). The second detail is to seamlessly
allow the user to continue dragging the new frame window. This is easily accomplished
by faking out the new frame window by sending it a
which is the message generated by a user manually clicking in the frame's caption
I said a moment ago that there was some magic involved to have the
CFrameWnd make us a
CMiniReBarDockFrameWnd instance instead of a
The magic is revealed if you check out the
DECLARE_REBAR_DOCKING() macro and the
corresponding call to
EnableRebarDocking(). This code does what the
method does: it tells the
CFrameWnd what class to use for hosting floating control
OK, so you can see how we "tear off" a toolbar from the rebar and float it in
its own frame window. The reverse procedure is similar, and is implemented in
void CMiniReBarDockFrameWnd::OnMoving(UINT fwSide, LPRECT pRect)
CTearOffReBar* pRebar = CalcDockingReBar(&dwInsertPos, &bHorizontal);
if (NULL == pRebar) return;
CControlBar*pBar = (CControlBar*)GetWindow(GW_CHILD)->GetWindow(GW_CHILD);
pBar->m_dwStyle = m_dwStyle; pBar->
SendMessage(RB_INSERTBAND, dwInsertPos, (LPARAM)&m_rbi);
CPoint pt(rclReBar.left-5, rclReBar.top+5);
m_wndDockBar.RemoveControlBar(pBar);pBar->m_pDockBar = NULL;
::MapWindowPoints(NULL, pRebar->GetSafeHwnd(), &pt,1);
pRebar->PostMessage(WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y));
This function is also pretty straightforward. It is called as the user is
moving (dragging) the
CMiniReBarDockFrameWnd window. It calls the helper
determine whether or not to dock with the rebar (if you decide to have multiple
rebars in your frame window, you should extend this function to identify the
correct one for docking). Assuming a rebar is located,
the function inserts a new rebar-band and re parents the control bar. The logic in
this function also figures out whether the band should be inserted or
append in the rebar's internal list (so that the band is inserted
at the top or bottom of the rebar). Once this is done,
OnMoving() uses the
same trick we saw earlier to seamlessly continue the dragging operation
within the rebar itself.
That's it, folks! One last point: If you're
looking to implement more Office 2000 toolbar features, my personal
recommendation is to check out a library called BCGControlBar.
This library has the most complete set of free toolbar-enhancements I've run across.
Although BCGControlBar does not support tear-off rebars, my intention is
to submit my technique for inclusion in that library.