主頁  |  産品  | 我們的客戶 | 技術支持 | 下載 | 購買 | 關于UCanCode   

同UCanCode一起釋放Visual C++的巨大能量!
 


028-85354645

購買與價格
免費評估版
E-Form++可視化圖形源碼庫企業版本
100%C++, VB, C#源碼
 産品特點 
  HMI &SCADA源碼
 PLC組态編程源碼
 CAD設計控件源碼
 HMI 報表源碼
GIS制圖源碼
電力系統源碼
條碼賬單源碼
工作流程源碼
煤炭行業源碼
儀器儀表源碼
報表打印源碼
圖形建模源碼
電子表單源碼
Visio制圖源碼
工業控制源碼
BPM業務流程源碼
工業監控源碼
流程圖控制流源碼
組織關系圖源碼
圖形編輯器源碼
 Win CE組态源碼
UML編輯器源碼
地圖演示源碼
建築平面制圖源碼
價格與購買
 價格與購買
技術支持
  軟件注冊
  在線升級
  在線文檔
  開發課程
  開發範例
  開發知識庫
  軟件Bug報告
  改進建議
 關于UCanCode
  與我們聯系
友情連接
VC++ Source Code
VC++ Tutorial
 

ActiveX Control with MFC Source Code CStatusBar

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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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:

Collapse Copy Code
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)

 

 

[ 主頁 | 産品 | 新聞 | 下載 | 購買 | 技術支持 | 與我們聯系 ]


粵ICP備05040024

UCanCode Software中國.成都
地址:中國.成都高新區永豐路24号附1号 (郵編:610041)
電話: +86-28-85354645                   傳真:+86-28-85354645    
Copyright 1998-2019 UCanCode.Com Software, ©版權所有。
其他的産品和公司名稱或注冊的商标屬于其各公司版權所有。

任何問題或者建議請與我們聯系:webmaster@ucancode.net