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

ActiveX MFC Status Bar

By , 8 May 2003
 

Sample Image - statbar1.jpg

Introduction

Many, many moons ago I extended the MFC status bar by creating a version capable of housing almost any type of control. I seem to recall that my code even supported VBX controls. (VBX? yuk!) Anyway, this article presents a simpler version that targets ActiveX controls. The demo program illustrates things by hosting two ActiveX controls on the status bar of an MFC application.

The inside scoop

I’ll start by presenting my hosting technique, beginning with the CStatusBarChildWnd class. I derive CStatusBarChildWnd from CWnd as shown here:

class CStatusBarChildWnd : public CWnd
{
public:

    CStatusBarChildWnd( int nMaxWidth )
    {
        m_nMaxWidth = nMaxWidth;
    } // End CStatusBarChildWnd()

    inline int GetMaxWidth( void ) const
    {
        return m_nMaxWidth;
    } // End GetMaxWidth()

private:
    int m_nMaxWidth;
};

In addition to being a placeholder for ActiveX controls this class also exposes the GetMaxWidth property, which is used by the status bar to determine the maximum width of a control at runtime. The status bar class itself is named CStatusBarEx, and is defined as follows:

class CStatusBarEx : public CStatusBar
{
public:

    CStatusBarEx( void );
    virtual ~CStatusBarEx( void );

    BOOL AddChildWindow( LPCTSTR lpszClass, UINT nID, int nWidth,
        DWORD dwStyle = 0 );
    
    BOOL RemoveChildWindow( UINT nID );

    CWnd* GetChildWindow( UINT nID ) const;

protected:

    //{{AFX_MSG(CStatusBarEx)
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnDestroy();
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()

private:

    CMap<UINT, UINT, CStatusBarChildWnd*, 
           CStatusBarChildWnd*> m_mapChildren;

    BOOL _RebuildPanes( void );
    BOOL _ResizeChildWindows( void );
};

The first public function declared by the CStatusBarEx class is AddChildWindow, which is used to add new controls to the status bar. This function is implemented as shown here:

BOOL CStatusBarEx::AddChildWindow( LPCTSTR lpszClass, UINT nID, int nWidth,
    DWORD dwStyle )
{
    CStatusBarChildWnd* pWnd = NULL;

    // Verify that the specified child window isn't already in the map.
    if ( TRUE == m_mapChildren.Lookup( nID, pWnd ) )
        return FALSE;

    try
    {
        // Create a new child window wrapper object.
        pWnd = ::new CStatusBarChildWnd( nWidth );

        // Attempt to create a window element for the wrapper object.
        if ( FALSE == pWnd->CreateControl( lpszClass, NULL, 
            dwStyle | (WS_CHILD | WS_VISIBLE), CRect( 0, 0, 0, nWidth ), 
            this, nID ) )
            AfxThrowUserException();

        // Add the child window to the map.
        m_mapChildren[ nID ] = pWnd;

        // Rebuild the panes for the status bar.
        return _RebuildPanes();
    } // End try

    catch ( CException* e )
    {
        e->Delete();

        // Cleanup the window wrapper.
        ::delete pWnd;

        return FALSE;
    } // End catch
} // End AddChildWindow()

This code creates an ActiveX control as a child window of the status bar and then saves a pointer to that control in a map for later access. As a final step, a call is made to _RebuildPanes, which begins a process designed to manage the layout of all the controls on the status bar. The _RebuildPanes function is implemented as shown below:

BOOL CStatusBarEx::_RebuildPanes( void )
{
    UINT* pIndicators = NULL;

    try
    {
        // Get a count of all the child windows in the map - adding 
        //   one to account for the status area.
        int nCount = m_mapChildren.GetCount() + 1;
        
        // Create a buffer to hold dummy identifiers. 
        pIndicators = ::new UINT[ nCount ];
        ::memset( pIndicators, ID_SEPARATOR, sizeof( UINT ) * nCount );

        // Attempt to update the status bar.
        if ( FALSE == SetIndicators( pIndicators, nCount ) )
            AfxThrowUserException();

        // Cleanup the buffer.
        ::delete[] pIndicators;
        pIndicators = NULL;
        
        // Attempt to get an iterator for the map.
        POSITION pos = m_mapChildren.GetStartPosition();
        UINT nIndex = 1;

        UINT nID = 0;
        CStatusBarChildWnd* pWnd = NULL;

        // At this point, we must modify all the panes that correspond
        //   to child windows by forcing each one to contain the proper 
        //   width, style, and identifier. 

        while ( NULL != pos )
        {
            // Attempt to get the next child window from the map.
            m_mapChildren.GetNextAssoc( pos, nID, pWnd );
            ASSERT_VALID( pWnd );

            // Update the properties for this pane.
            SetPaneInfo( nIndex, nID, SBPS_NOBORDERS, 
                pWnd->GetMaxWidth() );

            // Point to the next pane.
            nIndex++;
        } // End while there are more child windows in the map.

        // Resize all the child windows.
        return _ResizeChildWindows();
    } // End try

    catch ( CException* e )
    {
        e->Delete();

        // Cleanup the buffer.
        ::delete[] pIndicators;

        return FALSE;
    } // End catch
} // End _RebuildPanes()

This function takes advantage of the fact that MFC internally manages the layout of indicator panes. What I do is create an indicator for each control, then I position the associated control over indicator pane. With help from MFC, I get layout logic for almost free. This function looks complicated because I was forced to perform some workarounds in order to live in harmony with the framework. The first problem is that MFC stores indicator pane information in a fixed array, making it difficult to add or remove individual panes at runtime. I solved this by rebuilding all the indicators every time a control is added or removed. The next problem has to do with the SetIndicators function, which causes an assertion if a string resource isn’t available for each indicator in the array. To solve this I temporarily set each identifier to ID_SEPARATOR (since separators don’t use string resources) before calling SetIndicators. Afterwards, I loop through and plug all the actual properties into each indicator as a separate step. I admit that this approach is a little ugly, but it works, and it makes layout management much easier.

The last step of the _RebuildPanes function is a call to _ResizeChildWindows, which simply iterates through the map and repositions and resizes each control to match the footprint of its corresponding indicator pane. This function is implemented as follows:

BOOL CStatusBarEx::_ResizeChildWindows( void )
{
    // Get a count of all the child windows in the map.
    int nCount = m_mapChildren.GetCount();

    // Are there any windows to resize?
    if ( 0 == nCount )
        return TRUE;

    // Attempt to get an iterator for the map.
    POSITION pos = m_mapChildren.GetStartPosition();
    UINT nIndex = 1;

    UINT nID = 0;
    CStatusBarChildWnd* pWnd = NULL;

    // Attempt to begin a deferred window position operation. 
    HDWP hDwp = ::BeginDeferWindowPos( nCount );

    // Loop and resize child windows.
    while ( NULL != pos )
    {
        // Attempt to get the next child window from the map.
        m_mapChildren.GetNextAssoc( pos, nID, pWnd );

        // Calculate a footprint for child window by using the
        //   footprint of the underlying status bar pane.
        CRect rc;
        GetItemRect( nIndex, &rc );

        // Perform a deferred move operation.
        ::DeferWindowPos( hDwp, pWnd->GetSafeHwnd(), 
            GetSafeHwnd(), rc.left, rc.top, rc.Width(), rc.Height(), 
            SWP_NOZORDER );

        // Point to the next pane.
        nIndex++;
    } // End while there are more child windows in the map.

    // End the deferred window operation.
    ::EndDeferWindowPos( hDwp );
        
    return TRUE;    
} // End _ResizeChildWindows()

The use of deferred window positioning in this loop may seem a bit obscure to some readers, but this mechanism allows multiple sibling windows to be resized/repositioned within a single update operation. Using these functions greatly increases the efficiency of the entire layout process, and reduces the possibility of screen flicker.

Removing controls from the status bar is accomplished through the RemoveChildWindow function, which is implemented like this:

BOOL CStatusBarEx::RemoveChildWindow( UINT nID )
{
    CStatusBarChildWnd* pWnd = NULL;

    // Attempt to locate the specified child window in the map.
    if ( FALSE == m_mapChildren.Lookup( nID, pWnd ) )
        return FALSE;

    // Destroy the window element.
    pWnd->DestroyWindow();
    
    // Cleanup the wrapper object.
    ::delete pWnd;

    // Attempt to remove the child window from the map.
    VERIFY( TRUE == m_mapChildren.RemoveKey( nID ) );

    // Rebuild the panes for the status bar.
    return _RebuildPanes();
} // End RemoveChildWindow()

All that is happening here is that a pointer is being pulled from the map and used to destroy the associated ActiveX control. Afterwards, a call to _RebuildPanes performs layout management exactly as was previously described, except that this time a pane will be removed rather than added.

The final public function on CStatusBarEx is GetChildWindow, which may be used to obtain a pointer to any embedded control. This function is implemented like this:

CWnd* CStatusBarEx::GetChildWindow( UINT nID ) const
{
    // Attempt to locate the specified child window in the map.
    CStatusBarChildWnd* pWnd = NULL;
    m_mapChildren.Lookup( nID, pWnd );

    return pWnd;
} // End GetChildWindow()

The sample application

The sample uses a class named CDemoStatusBar, derived from CStatusBarEx. As an application, it doesn't do much more than demonstrate adding controls and processing event notifications. The CDemoStatusBar is defined like this:

class CDemoStatusBar : public CStatusBarEx  
{
public:

    CDemoStatusBar( void );
    virtual ~CDemoStatusBar( void );

protected:

    //{{AFX_MSG(CDemoStatusBar)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnChangeDtpicker();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    DECLARE_EVENTSINK_MAP()
};

The OnCreate handler is called from MFC when the status bar is created, and is implemented like this:

int CDemoStatusBar::OnCreate( LPCREATESTRUCT lpCreateStruct ) 
{
    // Create the extended status bar.
    if ( -1 == CStatusBarEx::OnCreate( lpCreateStruct ) )
        return -1;

    // Make the status bar slightly taller in order to contain 
    //   the controls.
    SendMessage( SB_SETMINHEIGHT, 22, 0 );

    // Add a date/time picker control.
    AddChildWindow( _T("MSComCtl2.DTPicker"), IDC_DTPICKER, 100 );

    // Add a slider control.
    AddChildWindow( _T("COMCTL.Slider"), IDC_SLIDER, 150 );
    
    return 0;
} // End OnCreate()

Notice that the first parameter to each AddChildWindow call is in fact the program identifier of an ActiveX control. These strings can be located on your system by selecting the “Project/Add To Project/Components and Controls” menu choice, and selecting the “Registered ActiveX Controls” item in the resulting dialog. This action produces a list of ActiveX controls, where each control is shown using a program identifier. Simply copy the string into your own code and you’re in business!

Intercepting and handling the onchange event from the date/time picker involves adding the following handler to the event map on the CDemoStatusBar class:

BEGIN_EVENTSINK_MAP(CDemoStatusBar, CStatusBarEx)
    //{{AFX_EVENTSINK_MAP(CDemoStatusBar)
    ON_EVENT(CDemoStatusBar, IDC_DTPICKER, 
       2 /* Change */, OnChangeDtpicker, VTS_NONE)
    //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

This causes MFC to route all onchange notification events from the date/time picker to the OnChangeDtPicker handler. In the sample we have written OnChangeDtPicker to simply display the selected date/time value on the status bar, as shown here:

void CDemoStatusBar::OnChangeDtpicker( void ) 
{
    // Get a pointer to the dtpicker control.
    CDTPicker* pWnd = (CDTPicker*)GetChildWindow( IDC_DTPICKER );
    ASSERT_VALID( pWnd );

    // Get the currently selected date.
    CString strDate;
    strDate.Format( _T("Date Selected: %d/%d/%d"), pWnd->GetMonth().iVal,
        pWnd->GetDay().iVal, pWnd->GetYear().iVal );
    
    // Show the date on the status bar.
    GetParentFrame()->SetMessageText( strDate );
} // End OnChangeDtpicker()

Final thoughts

Keep in mind that you will not need to derive from CStatusBarEx in your project unless you intend to process control notifications. In a simpler scenario, CStatusBarEx could be used as a drop-in replacement for the CStatusBar class when events are not an issue.

I’m sure you will come up with many creative ways to use this tool in your projects.

Have fun! :o)

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

About the Author

Martin Cook
Web Developer
United States United States
Member
I am a C# developer specializing in creating object-oriented software for Microsoft Windows. When I am not programming I enjoy reading, playing the guitar/piano, running, watching New York Ranger hockey, designing and building wacky electronic devices, and of course enjoying good times with my wife and children.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalproblem with activexsussAnonymous6 Jul '04 - 4:19 
Generalnewbie: READING(not changing) browser's status bar.memberpassomkar12 Nov '03 - 19:10 
GeneralLooks gooood!! But question...memberOleg_S22 Sep '03 - 12:44 
GeneralRe: Looks gooood!! But question...memberMartin Cook22 Sep '03 - 15:35 
GeneralRe: Looks gooood!! But question...memberOleg_S23 Sep '03 - 6:27 
GeneralRe: Looks gooood!! But question...memberMartin Cook24 Sep '03 - 3:00 
GeneralRe: Messages removed.sussTrunks16 May '03 - 5:26 
GeneralGood job!memberGregPage12 May '03 - 9:11 
GeneralRe: Good job!memberMartin C Cook13 May '03 - 2:24 
GeneralRe: Good job!memberzmpapaya20 Sep '03 - 4:47 
GeneralRe: Debug Assertion Failed !memberMartin C Cook12 May '03 - 4:12 
GeneralRe: Debug Assertion Failed !sussAnonymous15 May '03 - 3:45 
GeneralRe: Debug Assertion Failed !sussAnonymous15 May '03 - 3:58 
GeneralRe: Debug Assertion Failed !sussAnonymous15 May '03 - 4:55 
GeneralRe: Debug Assertion Failed !memberAnna-Jayne Metcalfe15 May '03 - 3:00 

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130513.1 | Last Updated 9 May 2003
Article Copyright 2003 by Martin Cook
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid