UCanCode Software

 

产品与方案▼  免费下载  价格与购买▼  技术支持  客户列表  操作视频▼

 

100%开源组态,CAD,建模仿真PLC平台!
 


028-85354645

VX++跨平台工业C++源码库
100%C++, QT, JAVA源码
 产品特点 
  跨平台工控组态源码
  嵌入式Linux组态源码
  跨平台建模仿真源码
E-Form++可视化图形源码库企业版本
100%C++, VB, C#源码
 产品特点 
  HMI &SCADA源码
 PLC组态编程源码
 CAD设计控件源码
 HMI 报表源码
GIS制图源码
电力系统源码
条码账单源码
工作流程源码
煤炭行业源码
仪器仪表源码
报表打印源码
图形建模源码
电子表单源码
Visio制图源码
工业控制源码
BPM业务流程源码
工业监控源码
流程图控制流源码
组织关系图源码
图形编辑器源码
 Win CE组态源码
UML编辑器源码
地图演示源码
建筑平面制图源码
 关于UCanCode
 

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-85354545 (18981891030)                   传真:+86-28-85354645    
Copyright 1998-2023 UCanCode.Com Software, ©版权所有。
其他的产品和公司名称或注册的商标属于其各公司版权所有。

任何问题或者建议请与我们联系:webmaster@ucancode.net