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()
{
...
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)
...
ON_MESSAGE(OCM_COMMAND, OnOcmCommand)
ON_MESSAGE(OCM_DRAWITEM, OnOcmDrawItem)
...
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;
...
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:
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.