Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating an ActiveX Button from an MFC owner-draw Button

0.00/5 (No votes)
25 Apr 2002 2  
Basic steps to transform an MFC button in an ActiveX control

Introduction

There are many owner-draw buttons written using C++ and the MFC classes. There are also many ActiveX controls, mostly written in Visual Basic. While with C++, you can easily use both the MFC and the OCX controls, with Visual Basic, you need to convert the MFC objects to an ActiveX control. Unluckily, the class wizard doesn't cover all the messages and the events sent to an ActiveX control, and some messages are different, so most of the magic must be hand written.

Because sometimes, a piece of code is more clear than the article itself, I included the porting of CxShadeButton to an ActiveX control, but it's just an example. In the article, I will speak about a generic AxButtonCtrl.

The MFC Activex Control Wizard & the Class Wizard

With a couple of clicks, the ActiveX Control Wizard writes for us about 600 lines of commented code that probably we will never read. Just remember to select "BUTTON" in the combo-box where the wizard asks: "Which window class, if any, should this control subclass?", and the framework is ready.

Using the Class Wizard, you can add the member functions to process the basic messages:

  • WM_CREATE
  • WM_ERASEBKGND
  • WM_KEYDOWN
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP
  • WM_MOUSEMOVE
  • WM_SIZE
  • PreSubclassWindow

Some of these messages are optional, but I'm thinking about a custom control, so there are good chances to handle more messages than a normal button.
The WM_DRAWITEM message is not in the list, it's not a mistake, and I will explain this later.

PreSubclassWindow

void AxuttonCtrl::PreSubclassWindow() 
{
	... //custom style initialization code	
	COleControl::PreSubclassWindow();
	ModifyStyle(0, BS_OWNERDRAW|BS_NOTIFY);
}

In this method, you can copy the same code used in a MFC control; the difference is in the last 2 lines: the button is now derived from COleControl (in place of CButton). You must set the BS_OWNERDRAW style to paint the button with your custom graphics, and the BS_NOTIFY style if you need some special notification messages, like BN_DISABLE, BN_KILLFOCUS, ...

WM_CREATE & WM_SIZE

These messages sometimes are not used by MFC controls, but in Visual Basic, the WYSIWYG philosophy requires that the developer can see the appearance while she/he builds the GUI.
The control receives the WM_CREATE message and creates the object with COleControl::OnCreate(lpCreateStruct), after this call the button exists, and you can use all the windows functions (CWnd members in MFC) to initialize the graphic objects.
The WM_SIZE message is sent after WM_CREATE, and when the button size has been changed; here you must build (or rebuild) the position and/or the dimensions of the graphic objects.

Keyboard & Mouse Messages

These messages are useful for the tooltips and the hover functionality, see later.

Drawing the Button - Reflected Window Messages

The Class Wizard provides an AxButtonCtrl::OnDraw member for the drawing functions.

void AxButtonCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	DoSuperclassPaint(pdc, rcBounds);
}

You can leave it as it is. Our button waits the WM_DRAWITEM message, but the mechanism with activex controls is slightly different: COleControl creates an extra window called "reflector", in the same position of the control window. The reflector intercepts certain window messages and sends them to the control with a different name. The reflected messages are:

Message sent by control Message reflected to control
WM_COMMAND OCM_COMMAND
WM_CTLCOLOR OCM_CTLCOLOR
WM_DRAWITEM OCM_DRAWITEM
WM_MEASUREITEM OCM_MEASUREITEM
WM_DELETEITEM OCM_DELETEITEM
WM_VKEYTOITEM OCM_VKEYTOITEM
WM_CHARTOITEM OCM_CHARTOITEM
WM_COMPAREITEM OCM_COMPAREITEM
WM_HSCROLL OCM_HSCROLL
WM_VSCROLL OCM_VSCROLL
WM_NOTIFY OCM_NOTIFY
WM_PARENTNOTIFY OCM_PARENTNOTIFY

For these messages, you must add the message handlers manually: in the control class .H file, declare a handler function like this:

class AxButtonCtrl : public COleControl
{
...
 protected:
LRESULT OnOcmCommand(WPARAM wParam, LPARAM lParam);
LRESULT OnOcmDrawItem(WPARAM wParam, LPARAM lParam);
...
}

In the control class .CPP file, add the ON_MESSAGE entries to the message map:

BEGIN_MESSAGE_MAP(AxButtonCtrl, COleControl)
//{{AFX_MSG_MAP(AxButtonCtrl)
...
ON_MESSAGE(OCM_COMMAND, OnOcmCommand)
ON_MESSAGE(OCM_DRAWITEM, OnOcmDrawItem)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

And implement the member functions to process the reflected messages. The wParam and lParam parameters are the same as those of the original window message.

LRESULT AxButtonCtrl::OnOcmDrawItem(WPARAM wParam, LPARAM lParam)
{
	UINT nIDCtl = (UINT) wParam;
	LPDRAWITEMSTRUCT lpDrawItemStruct = (LPDRAWITEMSTRUCT) lParam;
	... //drawing code as used in WM_DRAWITEM
	return 0;
}

Events

The Class Wizard gives some stock handlers for the standard events, however the resulting functionality could be limited. If you add the "Click" event with the stock handler implementation, when someone clicks the button with the mouse, COleButton automatically calls the FireClick method, so you don't need to process the OCM_COMMAND message. But if the button has the focus and someone presses the space bar, the FireClick method is not called, although the button receives the BN_CLICKED notification through the OCM_COMMAND message.

In the end, use the custom event handler implementation, in this way you can control exactly the behavior of the button, without hidden calls or messages.

LRESULT AxButtonCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
{

	...
	switch (wNotifyCode)
	{
	case BN_CLICKED:
		// Fire click event when button is clicked
		FireClick();
		break;
	case BN_KILLFOCUS:
	...

	}
	return 0;
}

ToolTips

The MSDN article Q141871 (HOWTO: Add Tooltips to ActiveX Controls) describes the steps to implement tooltips. The resume: add the RelayEvent method and a CToolTipCtrl m_ttip member variable; create and activate the tooltip; in the handlers for WM_LBUTTONDOWN, WM_LBUTTONUP, and WM_MOUSEMOVE call RelayEvent to relay appropriate messages to the ToolTip control.

Hover Functionality

You must use a bool m_tracking member variable to track the mouse position. When the mouse is over the button, the WM_MOUSEMOVE messages are sent, and here activate the tracking.

...
if (!m_tracking) {
   TRACKMOUSEEVENT t = {sizeof(TRACKMOUSEEVENT),TME_LEAVE,m_hWnd,0};
   if (::_TrackMouseEvent(&t)) {
      m_tracking = true;
      Invalidate();
   }
}

To detect when the mouse leaves, you must add this message handler in the control class .H file:

LRESULT OnMouseLeave(WPARAM, LPARAM);

and in the control class .CPP file:

ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)

LRESULT AxButtonCtrl::OnMouseLeave(WPARAM, LPARAM)
{
    ASSERT (m_tracking);
    m_tracking = false;
    Invalidate();
    return 0;
}

Conclusion

This article treats how to convert an owner draw button in an ActiveX control, this is just the beginning, to build a complete control I should talk about automation, property sheet, and so on ..., but these topics are out of the scope of the article. For detailed information about COM, please read these articles.

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.

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