Advertising

Creating ActiveX Controls with MFC

Contents

Introduction.

Current documentation on this subject fails to explain how to create ActiveX controls with acceptable user-interfaces. This document explains the bizarre, yet ultimately simple, series of steps necessary to Create ActiveX controls which themselves use other windows controls such as buttons or edit boxes.

This involves creating your controls, positioning your controls, and handling events from your controls - all without the aid of Visual C++'s layout tools.

Once you have created such an ActiveX control you may use it in Visual C++ or Visual Basic in the same way you would use any other control. You should find that this packaging of controls makes programming applications much simpler. See Using ActiveX Controls with MFC.

These instructions are relevant to Visual C++ 5 or 6. Later versions may provide 'visual' methods of achieving these same aims, but probably will not.

Creating the Project.

Select New from the File menu and create a new 'MFC Active X Control'.

When asked, give your control project a name.

N.B. If the project is called Example then the resultant ActiveX Control will be listed as 'Example Control'.

Dynamically Created Child Controls.

Right-click on ExampleCtl. Select 'Add Member Variables' to add member variables for the child controls.

#Include necessary files for child controls. MSDN should say which you need.

Select 'View Resource Symbols' from the View menu. Add IDR symbols for the child controls. e.g. IDR_SUBBUTTON1.

OnCreate

Right-click on ExampleCtl. Select 'Add Windows Message Handler'. Add a message handler for WM_CREATE.

Double-click ExampleCtl::OnCreate. Use ::Create to construct the controls and use the IDR symbol in the ::Create call. E.g.

m_AddButton.Create(...);

You may have to set up the initial position via a Rect. You may calculate this using the ClientRect. This positioning code will be duplicated and overridden in OnDraw - but you may as well get it correct here for the sake of neatness. Alternatively, set up member variable CRects for each of the child windows and calculate their values in a member function called from both OnCreate and OnDraw.

OnDraw

Double-click ExampleCtl::OnDraw

Use 'ULONG savedDC = pdc->SaveDC();' at the start of OnDraw
and 'pdc->RestoreDC(savedDC);' at the end of OnDraw, to preserve the existing Device Context.

Use 'pdc->FillRect( rcBounds, &CBrush(TranslateColor( AmbientBackColor() )) );' to clear the background with the preferred colour as specified by the container.

Use ::MoveWindow and ::ShowWindow to position the child controls. You may duplicate the Rect calculations in OnCreate.

e.g.

ULONG savedDC = pdc->SaveDC();
      
pdc->FillRect( rcBounds, &CBrush(TranslateColor( AmbientBackColor() )) );
      
CalculateChildWindowRects(rcBounds);
      
m_Edit.MoveWindow(m_EditRect, TRUE);	
m_Edit.ShowWindow(SW_SHOW);
      
pdc->RestoreDC(savedDC);

Handling Events.

Our dynamically created classes are not dealt with by ClassWizard. We must define the message map manually.

In the declaration (.h) file:

Declare the message handler manually, outside of the AFX_MSG section. e.g

// Message maps
     //{{AFX_MSG(CSubSectionCtrl)
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     //}}AFX_MSG
     afx_msg void OnExampleButtonClicked();
     DECLARE_MESSAGE_MAP()
     COM INTERFACES    

The afx_msg void OnExampleButtonClicked() line is the one we added.

In the definition (.cpp) file:

Define the message map manually, outside of the AFX_MSG_MAP section. e.g

BEGIN_MESSAGE_MAP(CSubSectionCtrl, COleControl)
//{{AFX_MSG_MAP(CSubSectionCtrl)
ON_WM_CREATE()
//}}AFX_MSG_MAP
ON_BN_CLICKED(IDR_EXAMPLEBUTTON, OnExampleButtonClicked)
ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)
END_MESSAGE_MAP()

The ON_BN_CLICKED... line is the one we added.

Then implement the message handler method.

e.g.

void OnExampleButtonClicked()
{
    ...
}

Firing Custom ActiveX Events.

Right Click on the '_ExampleEvents' Interface in class view. Class view shows interfaces with a grey symbol of a dot and a circle joined by a line.

Choose 'Add Event...'

In the dialog box, enter the name of the Event. You could choose from the pop-up list if you wish to fire a standard 'Stock Event'. Visual C++ will generate a name for the C++ method. You may add a list of parameters that are sent with the Event. When defining the parameter types you will be confined to using COM-compatible types.

Visual C++ will add the handler to the declaration(.h) file for the control, defining it inline.

e.g.

// Event maps
     //{{AFX_EVENT(CExampleCtrl)
     void FireSomeEvent()
     {FireEvent(eventidSomeEvent,EVENT_PARAM(VTS_NONE));}
     //}}AFX_EVENT
     DECLARE_EVENT_MAP()

You may now fire this custom event simply by calling the FireSomeEvent() method.

Adding COM methods and properties.

Right Click on the '_Example' Interface in class view.

Choose 'Add Method...' or 'Add Properties'. You will be shown a dialog box where you may enter the details. As with adding an ActiveX Event, you may choose to add Stock Methods or Properties.

Persistence.

COleObject uses DoPropExchange instead of Serialize.

DoPropExchange uses Macros such as PX_STRING and PX_FLOAT to store and retrieve data from a source. This is fine for simple data types, but is far less powerful than the Serialize method, which allows us to delegate persistence down to self-contained units. Clearly this presents a problem if our ActiveX control contains child windows as described above.

In theory we should be able to use the PX_BLOB macro to persist these objects as a stream of binary data. There is what appears to be an example of this at Article ID: Q141274, in the Microsoft Knowledge Base, but the example does not work and after much fiddling I have not been able to fix it.

The alternative is to extract the properties from the Child Windows and persist those properties. In some more complicated situations this may be unsatisfactory, in which case you should try as I have to persist using normal serialization. Please contact me if you succeed.

Persisting Child Window Properties:

You must extract the properties into simple data types in order to use them in the PX macro. For this purpose you should add some member variables to your control's class. Don't forget to set default values for these variables in the constructor.

Because the DoPropExchange method is called before the OnCreate method, you must make some changes to ensure that you do not attempt to extract or set these properties before the Child Windows have been Created. Do this by adding a Boolean flag member variable which is set to false in the constructor, tested before Creating child windows, and set to true after Creating child windows.

You should create a member function which sets the child window properties then sets the boolean to true.

You should call this function in the OnCreate function so that, when OnCreate is called after DoPropExchange has loaded a new Control, the loaded values will be used.

You should also test this Boolean flag in DoPropExchange before extracting the child window properties and before setting them.

e.g.
   
void CExampleCtrl::DoPropExchange(CPropExchange* pPX)
{
    ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
     COleControl::DoPropExchange(pPX);
   
     if(m_bChildWindowsCreated) {
          m_Edit.GetWindowText(m_EditPXString);
     }
	
     PX_String(pPX, _T("EditText"), m_EditPXString, _T(""));
	
     if(m_bChildWindowsCreated) {
          SetChildWindowsProperties();
     }
}
   

Copyright © Murray Cumming. Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.

Murray's Web Pages