Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Converting MFC Toolbars into Taskbars


Dr. Dobb's Journal September 1997: Converting MFC Toolbars into Taskbars

You can do it -- in just four simple steps!

Mark, a software engineer for Novell, can be contacted at [email protected].


The taskbar is an object new to Windows. Until I read the article "Extend the Windows 95 Shell with Application Desktop Toolbars," by Jeffrey Richter (Microsoft Systems Journal, March 1996), I did not fully appreciate all the possible uses for a taskbar. In his article, Richter provides the code for a generic class that implements the functionality of a taskbar. By deriving from his class, you can easily create your own taskbar. The class is derived from CDialog, so the taskbar can easily contain any type of control.

One practical use for a taskbar is to display information to users. The taskbar is ideally suited for this because, when it is docked to the shell, it cannot be covered up by any other window. However, this requires users to forfeit some precious screen real estate. While taskbars do have an "autohide" property, they are still docked to the shell. But what if a taskbar could be undocked from the shell and docked to an application's window like a toolbar? And when an application window is hidden or closed, the taskbar would go with it? This gives users a choice: If they want the information to always be displayed, they can dock the taskbar to the Windows 95 shell; otherwise, they can dock it to the application window.

I have created an object that exhibits the properties of both a toolbar and taskbar. The object behaves exactly like a standard MFC toolbar, except that when it is positioned on the screen's edges, it becomes a taskbar and docks to the shell. The strategy I used to create this object was to start with a basic toolbar, so that it inherited all of the functionality for docking to a framed window. Then I added the necessary code from Richter's generic class to support taskbar behavior. For this article, I have used a toolbar, but the procedure I describe can be applied to any class derived from CControlBar (the parent class of CToolBar). The complete source code and related files for a sample application that implements these techniques are available electronically; see "Availability," page 3.

Modifying the behavior of a normal toolbar to mimic that of a taskbar requires an understanding of how MFC implements the toolbar and the docking process. The MFC class CToolBar encapsulates the functionality of a standard toolbar. The docking of this toolbar to a framed window is provided by the help of three other MFC classes:

  • CFrameWnd, the base class for a framed window. The frame handles the docking and floating of the toolbar.
  • CDockContext, the class that handles the dragging and resizing of a toolbar object. The dock context prompts the frame when the toolbar needs to be docked or floated.
  • CMiniDockFrameWnd, the class that implements the frame window that appears around a toolbar when it is floated.

By deriving your own classes from these classes and massaging the MFC code a little, you can -- in just four steps -- create a toolbar that can be docked to both a framed window and the system shell.

Step 1: Modify Class CMiniDockFrameWnd

The CMiniDockFrameWnd class requires the most work. A frame of this class is created around a toolbar any time the toolbar is undocked from the main frame of the application. You need to derive your own class from this base class and add all of the taskbar functionality. The result: When a toolbar with your frame class is dragged to the edge of the screen, the frame will change shape and dock to the shell. Voilà! A combination toolbar and taskbar!

You need to incorporate the implementation of Richter's CAppBar class into a class derived from CMiniDockFrameWnd, rather than CDialog. My CFloatingFrame class shows how this is done. The key methods to understand are the handlers OnWindowPosChanging, OnWindowPosChanged, and SetState. While the window's position is changing, the state of the floating frame is calculated based on the current position of the window when the message is received. This state is remembered in the internal variable m_uStateProposedPrev. Then, when a WM_WINDOWPOSCHANGED message is received, the state of the floating frame is set by a call to SetState. The SetState method handles the core of the work required to dock the frame to the system shell. SetState and the methods that it calls will adjust the size and position of the frame window, as well as notify the shell of the movement. For a more detailed explanation of the meaning of the internal variables and methods, refer to Richter's original article. The class definition for CMiniDockFrameWnd is located in the MFC file AfxPriv.h.

Step 2: Modify Class CFrameWnd

Now that you have created a floating-frame class with taskbar properties, you have to ensure that the framework will use your class when the toolbar is floated, rather than a standard CMiniDockFrameWnd. The main frame window of the application is actually responsible for creating the floating frame. The three methods on the class CFrameWnd that need modification are EnableDocking, CreateFloatingFrame, and FloatControlBar. The implementation for all three of these methods is in WinFrm2.cpp. You need to derive your own class from CFrameWnd and redefine these methods so that your floating-frame class is substituted everywhere the class CMiniDockFrameWnd occurs. My sample application includes a CMainFrame class, which demonstrates these changes. Listing One resents CreateFloatingFrame.

Step 3: Modify Class CDockContext

The method FloatControlBar on the framed window is called in response to several methods on a CDockContext object. The class CDockContext handles dragging or resizing of the toolbar by the user. The definition for CDockContext is in the header file AfxPriv.h and the implementation is in the file DockCont.cpp. Specifically, the method EndDrag of the class CDockContext calls FloatControlBar or DockControlBar, depending on the position of the toolbar window. Unfortunately, the method FloatControlBar is not declared virtual in the CFrameWnd class. When the user stops dragging the toolbar, the base class version of the method will be called rather than the overridden version in your derived class. To remedy this situation, you need to derive your own class from CDockContext. You override the method EndDrag so that the pointer to the main frame is typecast to your derived framed window class before any methods like FloatControlBar are called. Listing Two is my modified version of EndDrag.

The EndDrag method is not declared virtual either. The methods of the class CDockContext that call EndDrag also must be overridden so that the derived version of EndDrag is called by the framework. The StartDrag method is a virtual method that is called by the framework when the user begins to drag the toolbar object. StartDrag, in turn, calls a method, Track, which monitors the movement of the toolbar. It is the Track method that calls EndDrag. By overriding these two methods in your derived class, your version of EndDrag will be called. Neither StartDrag nor Track require any changes, so you can cut and paste them into your derived class.

Similar to the StartDrag and EndDrag methods, there are also the StartResize and EndResize methods. The EndResize method calls FloatControlBar on the frame when users resize the toolbar. Follow the same just-mentioned procedure and override the EndResize method so that the pointer to the frame is typecast to your derived framed window class. Likewise, you need to override the StartResize method so that your derived version of EndResize is called.

The ToggleDocking method is called when users double-click on the toolbar. Double-clicking toggles between a floating and docked toolbar. This method is declared virtual, so you can just cut and paste the method into your derived dock-context class and typecast the main frame pointer to your derived frame-window class.

There is one last change to the CDockContext class that should be made. This is not a necessary change, but one of style. The Move method of the dock-context class handles the sizing of the drag rectangles for a toolbar. By overriding the Move method you can change the shape of the drag rectangle when it is positioned over the edges of the shell. This gives users a visual clue that the toolbar may be docked to the shell. You can resize the drag rectangle by using a helper method such as GetDimsFromPoint. I based my GetDimsFromPoint method on the method GetEdgeFromPoint from Richter's CAppBar class. The method uses the point that is passed in as a parameter to determine if the toolbar needs to be docked. In the derived version of the Move method, save the original size of the drag rectangle. Then call GetDimsFromPoint to modify the size of the rectangle, if necessary. After drawing the rectangle with DrawFocusRect, return the saved size of the drag rectangle to the original dimensions. Listing Three is the GetDimsFromPoint helper method.

Step 4: Modify Class CToolBar

The last class that needs modification is the CToolBar class. The toolbar must be modified so that it uses your derived context class. The toolbar object maintains a pointer to the dock context in a member variable called m_pDockContext, which is inherited from the base class CControlBar. The instantiation and assignment of the dock context object to this variable occurs in the EnableDocking method of the CControlBar class. The implementation of the EnableDocking method can be found in the MFC source file BarDock.cpp. You need to derive your own class from CToolBar and override the EnableDocking method. Where an object of type CDockContext is normally instantiated, insert an object of your derived dock context class instead; see Listing Four.

Conclusion

That's all there is to it. By following these four steps, you can create a combination taskbar/toolbar for your applications. The majority of the work is already taken care of by MFC and the CAppBar class. All you have to do is put everything together.


Listing One

CFloatingFrame* CMainFrame::CreateFloatingFrame(DWORD dwStyle){
     CFloatingFrame* pFrame = NULL;
     ASSERT(m_pFloatingFrameClass != NULL);
     pFrame = (CFloatingFrame*)m_pFloatingFrameClass->CreateObject();
     if (pFrame == NULL)
        AfxThrowMemoryException();
     ASSERT_KINDOF(CFloatingFrame, pFrame);
     if (!pFrame->Create(this, dwStyle))
        AfxThrowResourceException();
     return pFrame;
}

Back to Article

Listing Two

void CMyDockContext::EndDrag(){
     CancelLoop();
     if (m_dwOverDockStyle != 0)
     {
     CDockBar* pDockBar = GetDockBar(m_dwOverDockStyle);
     ASSERT(pDockBar != NULL);


</p>
     CRect rect = (m_dwOverDockStyle & CBRS_ORIENT_VERT) ?
     m_rectDragVert : m_rectDragHorz;


</p>
     UINT uID = _AfxGetDlgCtrlID(pDockBar->m_hWnd);
     if (uID >= AFX_IDW_DOCKBAR_TOP &&
         uID <= AFX_IDW_DOCKBAR_BOTTOM)
     {
        m_uMRUDockID = uID;
        m_rectMRUDockPos = rect;
        pDockBar->ScreenToClient(&m_rectMRUDockPos);
     }
     // dock it at the specified position, RecalcLayout will snap
     ((CMainFrame*)m_pDockSite)->DockControlBar(m_pBar, pDockBar, &rect);
     ((CMainFrame*)m_pDockSite)->RecalcLayout();
     }
     else if ((m_dwStyle & CBRS_SIZE_DYNAMIC)||(HORZF(m_dwStyle) && !m_bFlip)
    || (VERTF(m_dwStyle) && m_bFlip))
     {
       m_dwMRUFloatStyle = CBRS_ALIGN_TOP | (m_dwDockStyle & CBRS_FLOAT_MULTI);
       m_ptMRUFloatPos = m_rectFrameDragHorz.TopLeft();
       ((CMainFrame*)m_pDockSite)->FloatControlBar(m_pBar,
                                       m_ptMRUFloatPos, m_dwMRUFloatStyle);
     }
     else // vertical float
     {
       m_dwMRUFloatStyle = CBRS_ALIGN_LEFT|(m_dwDockStyle & CBRS_FLOAT_MULTI);
       m_ptMRUFloatPos = m_rectFrameDragVert.TopLeft();
       ((CMainFrame*)m_pDockSite)->FloatControlBar(m_pBar, 
                                       m_ptMRUFloatPos, m_dwMRUFloatStyle);
     }
}

Back to Article

Listing Three

void CMyDockContext::GetDimsFromPoint (CRect& rect, CPoint pt) {
   UINT uState = ABE_FLOAT;   // Assume that the AppBar is floating
   // Let's get floating out of the way first
   // Get the rectangle that bounds the size of the screen
   // minus any docked (but not-autohidden) AppBars.
   CRect rc, rcSav;
   ::SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);


</p>
   rcSav = rc;
   // Leave a + width/height-of-a-scrollbar gutter around the workarea
   rc.InflateRect(-GetSystemMetrics(SM_CXVSCROLL), 
                                         -GetSystemMetrics(SM_CYHSCROLL));
   if (rc.PtInRect(pt)) 
   {
     // If the point is in the adjusted workarea
        return; 
   }
   // If we get here, the AppBar should be docked; determine the proper edge
   // Get the dimensions of the screen
   int cxScreen = GetSystemMetrics(SM_CXSCREEN);
   int cyScreen = GetSystemMetrics(SM_CYSCREEN);


</p>
   // Find the center of the screen
   CPoint ptCenter(cxScreen / 2, cyScreen / 2);


</p>
   // Find the distance from the point to the center
   CPoint ptOffset = pt - ptCenter;


</p>
   // Determine if the point is farther from the left/right or top/bottom
   BOOL fIsLeftOrRight = (AbsoluteValue(ptOffset.y) * cxScreen) <=
      (AbsoluteValue(ptOffset.x) * cyScreen);


</p>
   // If (it should be left/right) 
   if (fIsLeftOrRight){
       rect.top = rcSav.top;
       rect.bottom = rcSav.bottom;
       int width = rect.Width();
       if (0 <= ptOffset.x)
       {
            int width = rect.Width();
            rect.right = rcSav.right;
            rect.left  = rect.right - width;
       }
       else
       {
            rect.left = rcSav.left;
            rect.right = rect.left + width;
       }
   } 
   else if( !fIsLeftOrRight )
    {
            rect.left = rcSav.left;
            rect.right = rcSav.right;
            int height = rect.Height();
            if (0 <= ptOffset.y)
            {
                rect.bottom = rcSav.bottom;
                rect.top =  rect.bottom - height;
            }
            else
            {
                rect.top = rcSav.top;
                rect.bottom = rect.top + height;
            }
    }
}

Back to Article

Listing Four

void CMyToolBar::EnableDocking(DWORD dwDockStyle){
     // must be CBRS_ALIGN_XXX or CBRS_FLOAT_MULTI only
     ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY|CBRS_FLOAT_MULTI)) == 0);
     // CBRS_SIZE_DYNAMIC toolbar cannot have the CBRS_FLOAT_MULTI style
     ASSERT(((dwDockStyle & CBRS_FLOAT_MULTI) == 0) || 
           ((m_dwStyle & CBRS_SIZE_DYNAMIC) == 0));
     m_dwDockStyle = dwDockStyle;
     if (m_pDockContext == NULL)
        // instantiate my own dock context
        m_pDockContext = (CDockContext*) new CMyDockContext(this);
     // permanently wire the bar's owner to its current parent
       m_hWndOwner = ::GetParent(m_hWnd);
}

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.