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



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • This paper introduces IBM Java on the IBM PowerLinux 7R2 server and describes IBM's implementation of the Java platform, which includes IBM's Java Virtual Machine and development toolkit.

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds