Superclassed Ownerdrawn Standard Windows Control Using ATL

This article is an initial attempt to create an ActiveX control based on window’s control. In this article I
have spuerclassed ListBox control to create an ownerdraw control. This ListBox contains text strings and
user can specify the zero based index value to put divider lines in the list. For creation of the skeleton code
for the control please refer to the Microsoft’s sample SUBEDIT, POLYGON tutuorial and my other article
in this section “ActiveX Control Tutorial”. I will just explain the steps which are different or need some
explanation for superclassing.

Superclassing is a technique that allows an application to create a new window class with the basic
functionality of the existing class, plus enhancements provided by the application. A superclass is based on
an existing window class called the base class. Frequently, the base class is a system global window class
such as an edit control, static control, etc. but it can be any window class.
To create this control, follow all the steps for inserting a full control into the control project using ATL
Object Wizard. The only difference will be on the Miscellaneous page of the Object Wizard for Add
control based on option.

ATL Class Wizard

Since we are creating a ListBox, so from all the available options (Edit, Button, Static, ListBox,
ComboBox, Scroll Bar, etc.) pick ListBox. And make sure that Windowed Only checkbox is also checked.

After the code has been inserted, the constructor code should look something like this


CListBoxControlX::CListBoxControlX () :
m_ctlListBox (_T(“ListBox”), this, 1)
{
m_bWindowOnly = TRUE;
}

The constructor sets the m_bWindowOnly to TRUE. This data member is inherited from
CComControlBase and is used to instruct ATL never to allow windowless activation. Otherwise the control
will not be able to handle WM_CREATE message for the control window and control will not get created
in response to that message. The WM_CREATE message is used to create the one time creation of the ATL
control. The Object Wizard adds code in the WM_CREATE handler for the control to create the specified
(in this case ListBox) common control. It also adds code to handle the correct sizing of the contained
control based on the size of the ATL control.
The code for control creation looks like this,


LRESULT
CListBoxControlX::OnCreate (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DWORD dwStyle = (WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
WS_BORDER | LBS_DISABLENOSCROLL | LBS_OWNERDRAWFIXED | LBS_NOTIFY |
LBS_HASSTRINGS);

RECT rc;
GetWindowRect(&rc);
rc.right -= rc.left;
rc.bottom -= rc.top;
rc.top = rc.left = 0;
m_ctlListBox.Create(m_hWnd, rc, NULL, dwStyle);
return 0;
}

The thing to note in the function is window styles used. Since it’s a ownerdraw ListBox containing strings
only, there we put LBS_OWNERDRAWFIXED, LBS_HASSTRINGS styles. And this ListBox is to notify its
parent about the messages it generates it has to have LBS_NOTIFY style also.

Being an ownerdraw control, its painting will not be handled by OnPaint. The parent window sends
WM_DRAWITEM message to the control window to draw itself. There we need to handle this message in
the message map of our ActiveX control. The map looks like this:


BEGIN_MSG_MAP(CListBoxControlX)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
MESSAGE_HANDLER(WM_COMMAND, OnSelectionChange)
CHAIN_MSG_MAP(CComControl)
DEFAULT_REFLECTION_HANDLER ()
ALT_MSG_MAP(1)
// Replace this with message map entries for superclassed ListBox
END_MSG_MAP()

You can see we have handled WM_COMMAND message in the map. This has been added to handle the
clicks inside the ListBox control to inform the parent window that selected item has changed. The way this
works is, parent window gets LBN_SELCHANGE message when the ListBox selection change. The parent
window reflects it to the control window. Since the parent of ListBox gets all the messages through
WM_COMMAND, there we can handle the LBN_KILLFOCUS, LBN_DBLCLK, etc messages. This is
necessary to handle these events in more COM-centric way. After the message is reflected to control
window, then we make sure that user has not selected divider line. If so, we will not fire any event for this
selection and let the default message handler take care of it. The message handling code is:


LRESULT
CListBoxControlX::OnSelectionChange (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
bHandled)
{
long curSel;
DWORD wNotifyCode, wID;
HWND hwndCtl = NULL;

// Get the notification message and the handle to control window.

wNotifyCode = HIWORD (wParam);
wID = LOWORD (wParam);
hwndCtl = HWND (lParam);

if (wNotifyCode == LBN_SELCHANGE && !FAILED (GetCurSel (&curSel)) &&
hwndCtl == m_ctlListBox.m_hWnd) {
if (FindDividerIndex (curSel) == -1) {
Fire_SelectionChange ();
return 0;
}
}

m_ctlListBox.DefWindowProc(uMsg, wParam, lParam);
return 0;
}

Similarly to handle all other messages, we can perform appropriate checks and add events for connection
points. Therefore when you are inserting control with the help of Object Wizard, the option for adding
connection point support is checked.

I have added some other interfaces in the control’s inheritance, like IObjectSafetyImpl,
IQuickActivateImpl, IPersistPropertyBagImpl and IPersistStreamInitImpl. The significance of these
interfaces has been discused in my ActiveX Control Tutorial article. These don’t get added by wizard,
instead you will have to add then in the header file manually.

For specifying color of divider line, I have added a property of type OLE_COLOR to the interface and to
manipulate this at design time, an entry for this has been added in the property map so that it appears in the
color property page of the interface.


BEGIN_PROP_MAP(CListBoxControlX)
PROP_DATA_ENTRY(“_cx”, m_sizeExtent.cx, VT_UI4)
PROP_DATA_ENTRY(“_cy”, m_sizeExtent.cy, VT_UI4)
PROP_ENTRY(“Font”, DISPID_FONT, CLSID_StockFontPage)
PROP_ENTRY(“DividerLine Color”, DISPID_DIVIDERLINECOLOR,
CLSID_StockColorPage)
END_PROP_MAP()

I have tried to put all the useful methods for ListBox manipulation which have one to one correspondence
with MFC’s ClistBox class for handling ListBox.


STDMETHOD(GetCount)(/*[out]*/ long *plNumItems);
STDMETHOD(FindString)(/*[in]*/ long lIndexAfter, /*[in]*/ BSTR bstrItem, /*[out]*/ long *plRetIndex);
STDMETHOD(ResetContent)();
STDMETHOD(DeleteString)(/*[in]*/ long lIndex, /*[out]*/ long *plNumItems);
STDMETHOD(InsertString)(/*[in]*/ long lIndex, /*[in]*/ BSTR bstrItem, /*[out]*/ long *plRetPos);
STDMETHOD(RemoveDividerIndex)(/*[in]*/ long lIndex, /*[out]*/ long *plRetPos);
STDMETHOD(AddDividerIndex)(/*[in]*/ long lIndex);
STDMETHOD(SetCurSel)(/*[in]*/ long lIndex);
STDMETHOD(GetCurSel)(/*[out]*/ long *plIndex);
STDMETHOD(AddString)(/*[in]*/ BSTR bstrItem);

The difference being that following the COM rules, each of these methods return HRESULT value. And to
implement each one of the methods, Win32 API calls have been used because we are not taking any MFC
support for it. For example SetCurSel method for setting current selected object has been implemented like
this:


STDMETHODIMP
CListBoxControlX::SetCurSel (long lIndex)
{
// Do some initial checks.

if (!::IsWindow(m_hWnd)) {
return Error (OLESTR (“Invalid or NULL window handler for the ListBox control”),
IID_IListBoxControlX, E_FAIL);
}

if (::SendMessage (m_ctlListBox.m_hWnd, LB_SETCURSEL, (int)lIndex, 0L) == LB_ERR) {
return Error (OLESTR (“Failed to set the current selection to requested index”),
IID_IListBoxControlX, E_FAIL);
}

return S_OK;
}

It sends LB_SETCURSEL message to ListBox control’s window.

To store the divider line indices, I have made use of vector data type from STL. At top of
ListBoxControlX.h file, I have defined typedef for this data type storing long type values.


typedef vector<long> LONGVECTOR;

To use STL some minor modifications need to be made in the project. In the stdafx.h file, you need to put
the following block of code. Otherwise vector type will not be recoganized by compiler.


#include <vector>
#if _MSC_VER > 1020
using namespace std;
#endif

And in the project settings, goto to C++ tab. In the category type, choose C++ Language and then on this
page make sure that Enabling exception handling checkbox is checked.

I have tried to put very descriptive remarks at the start of each function in the source code. Since I can’t
explain each and every thing in this article, therefore I would appreciate if you could take some time to read
those descriptions.

The attached code includes a project, which demonstrates how to use this ListBox control in a dialog box. I
have tried to take care of most the bugs and problems with the use of this control. This is kind of Version 1
for this control. I will add more functionality to this control in future. But if you come across some serious
problem with the control, feel free to bring it to my notice. I will really appreciate that.

ATL Class Wizard

The attached code has been compiled using VC++6 (SP2) on WinNT (SP 4).

Control Project – ATLListBoxControl
Client Project – ListBoxClientApp

Download source – 44 KB

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read