Click here to Skip to main content
15,881,715 members
Articles / Desktop Programming / MFC
Article

Tear-off Rebars

Rate me:
Please Sign up or sign in to vote.
4.46/5 (8 votes)
26 Jan 2000 173.9K   2.1K   51   14
This article Implements the functionality similar to the Office 2000 toolbars

Sample Image - TearOffRebars.jpg

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 CReBar, and retrofitting your CFrameWnd to use a subclassed CMiniDockFrameWnd, 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.

Step 2. #include TearOffReBar.h in your MainFrm.h file, and add the line DECLARE_REBAR_DOCKING() inside your CMainFrame class.

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 CRebar.

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 OnCreate() method of CMainFrame, make a call to EnableRebarDocking(). 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_BEGINDRAG and 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);

    // cache info about the band that is being dragged:
    REBARBANDINFO rbi;
    memset(&rbi, 0, sizeof(rbi));
    rbi.cbSize = sizeof(rbi);
    rbi.fMask  = 
    RBBIM_CHILD; SendMessage(RB_GETBANDINFO,pnmrb->uBand, (LPARAM)&rbi);
    m_hwndBand = rbi.hwndChild;
                                             
    return TRUE;
}

The main 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) 
{
    // default:
    CReBar::OnMouseMove(nFlags, pt);

    // ensure dragging:
    if (this != GetCapture() || NULL == m_hwndBand)    
        return;

    // are we going to tear off?
    CRect rcl; GetClientRect(&rcl);
    rcl.InflateRect(10, 10);
    if (rcl.PtInRect(pt))
        return;

    // we're done tracking...
    ReleaseCapture();

    // we'll need these:
    UINT        uBand  = BandFromHWND(m_hwndBand);
    CFrameWnd*  pFrame = reinterpret_cast<CFRAMEWND*>(AfxGetMainWnd());
    ASSERT_KINDOF(CFrameWnd, pFrame);
    CControlBar* pBar  = reinterpret_cast<CCONTROLBAR*>
                                                (CWnd::FromHandle(m_hwndBand));
    ASSERT_KINDOF(CControlBar, pBar);
    DWORD dwStyle = pBar->m_dwStyle;

    // retrieve and cache band information:
    REBARBANDINFO rbi;
    char aszBuff[_MAX_PATH];
    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);

    // hide then delete the band:
    SendMessage(RB_SHOWBAND, uBand, FALSE); 
    SendMessage(RB_DELETEBAND, uBand); 
    pBar->ShowWindow(SW_SHOW); 
    pBar->SetOwner(pFrame); 

    // determine where to float thebar at: 
    MapWindowPoints(NULL, &pt,1);
    SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));

    // float the bar:
    pBar->EnableDocking(CBRS_ALIGN_ANY); 
    CPoint ptCreate(pt);
    ptCreate.Offset(-10,-10);

    pFrame->FloatControlBar(pBar, ptCreate); 
    pBar->EnableDocking(0); 

    // pass the cached band info to the new frame to store:
    CMiniReBarDockFrameWnd* pMiniFrame = 
                       (CMiniReBarDockFrameWnd*)pBar->GetParentFrame();

    pMiniFrame->SetRebarBandInfo(rbi, dwStyle); 

    // user continues to drag the    new parent-frame:
    pBar->GetParentFrame()->SendMessage(WM_NCLBUTTONDOWN, HTCAPTION, 
                                              MAKELPARAM(pt.x, pt.y));
    m_hwndBand = NULL;
    AfxGetMainWnd()->RedrawWindow();
}

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.

Next 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.

With the 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, namely CFrameWnd::FloatControlBar(). This function takes a control bar and re-parents it into a newly created CMiniDockFrameWnd. Unfortunately, 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 WM_NCLBUTTONDOWN message, which is the message generated by a user manually clicking in the frame's caption area.

I said a moment ago that there was some magic involved to have the CFrameWnd make us a CMiniReBarDockFrameWnd instance instead of a CMiniDockFrameWnd. The magic is revealed if you check out the DECLARE_REBAR_DOCKING() macro and the corresponding call to EnableRebarDocking(). This code does what the CFrameWnd::EnableDocking() method does: it tells the CFrameWnd what class to use for hosting floating control bars.

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 CMiniReBarDockFrameWnd::OnMoving():

void CMiniReBarDockFrameWnd::OnMoving(UINT fwSide, LPRECT pRect) 
{
    // default:
    CMiniDockFrameWnd::OnMoving(fwSide, pRect);

    // are we going to dock?
    DWORD dwInsertPos(0);
    bool  bHorizontal(false);
    CTearOffReBar* pRebar = CalcDockingReBar(&dwInsertPos, &bHorizontal);
    if (NULL == pRebar) return;

    // get the toolbar...
    CControlBar*pBar = (CControlBar*)GetWindow(GW_CHILD)->GetWindow(GW_CHILD);
    pBar->m_dwStyle = m_dwStyle; pBar->
    CalcFixedLayout(0, bHorizontal); 

    // insert a new band into the rebar: pRebar->
    SendMessage(RB_INSERTBAND, dwInsertPos, (LPARAM)&m_rbi); 
    CRect rclReBar;
    pBar->GetWindowRect(&rclReBar); 
    CPoint pt(rclReBar.left-5, rclReBar.top+5);
    m_wndDockBar.RemoveControlBar(pBar);pBar->m_pDockBar =  NULL; 

    // fake a button click so the  dragging operation 
    // moves seamlessly into the rebar internal dragging code:
    ::MapWindowPoints(NULL, pRebar->GetSafeHwnd(), &pt,1);
    pRebar->SetActiveWindow();
    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 function CMiniReBarDockFrameWnd::CalcDockingBar() to 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.

Wrap-up

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSimple addition : DblClk Sends back to dock. Pin
MOX8-Mar-04 5:24
MOX8-Mar-04 5:24 
GeneralCControlBar CMainFrame Document/View arch. Pin
pierre3123-Apr-03 4:08
pierre3123-Apr-03 4:08 
QuestionHow can i make this Tear off for the toolbar only Pin
MaTrIX2k218-May-02 18:58
MaTrIX2k218-May-02 18:58 
GeneralMicrosoft's paint Toolbar. Pin
kursatkaraca9-Apr-02 10:50
kursatkaraca9-Apr-02 10:50 
GeneralTear-off Rebars Pin
9-Apr-02 3:25
suss9-Apr-02 3:25 
Generalno document-view Pin
5-Apr-02 7:05
suss5-Apr-02 7:05 
GeneralRebars and C# Pin
TigerNinja_18-Feb-02 7:50
TigerNinja_18-Feb-02 7:50 
Generalmenu Pin
Nick Hudson1-Feb-02 18:39
Nick Hudson1-Feb-02 18:39 
GeneralReally good article, but questions Pin
Jim A. Johnson5-May-01 7:03
Jim A. Johnson5-May-01 7:03 
Generalalign Pin
arkon9-Jul-00 23:45
arkon9-Jul-00 23:45 
GeneralRedraw Problem Pin
Member 268618-Feb-00 2:33
Member 268618-Feb-00 2:33 
GeneralRefreshing Pin
Jordana Thomadsen28-Jan-00 13:26
sussJordana Thomadsen28-Jan-00 13:26 
GeneralNeato, Benefito Pin
Paul Selormey27-Jan-00 19:36
Paul Selormey27-Jan-00 19:36 
GeneralRe: Neato, Benefito Pin
Nick Hodapp28-Jan-00 3:14
sitebuilderNick Hodapp28-Jan-00 3:14 

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

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