Using a drop down list to change a subitem | CodeGuru

Using a drop down list to change a subitem

Sometimes, instead of allowing the user to arbitrarily change the value of an item, you want to present the user with a set of choices. You can do this by bringing up a drop down list instead of an edit control. To implement this, we follow a pattern very similar to that used for editable […]

Written By
CodeGuru Staff
CodeGuru Staff
Aug 6, 1998
4 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Sometimes, instead of allowing the user to arbitrarily


change the value of an item, you want to present the user with a set of


choices. You can do this by bringing up a drop down list instead of an


edit control. To implement this, we follow a pattern very similar to that


used for editable subitems.


Step1: Derive a class from CListCtrl


Derive a new class from CListCtrl or make the modification to an existing


sub-class. If you are already using a class for editable subitems as described


above, you can use that class.



Step 2: Define HitTestEx()


Define an extended HitTest function for the CMyListCtrl class. This function


will determine the row index that the point falls over and also determine


the column. The HitTestEx() has already been listed in an earlier section.


We need this function if the user interface to initiate the edit is a mouse


click or a double click. See the section

Detecting
column index of the item clicked

.



Step 3: Add function to create the Drop Down List


This function is similar to the function EditSubLabel() described in the


previous section. The difference is that, at the end, it creates a combobox


from the CInPlaceList class. Note that it also requires a list of strings


as an argument. This list is used to populate the drop down list. The last


argument is the index of the item that should be initially selected in


the drop down list.

// ShowInPlaceList		- Creates an in-place drop down list for any 
//				- cell in the list view control
// Returns			- A temporary pointer to the combo-box control
// nItem			- The row index of the cell
// nCol				- The column index of the cell
// lstItems			- A list of strings to populate the control with
// nSel				- Index of the initial selection in the drop down list
CComboBox* CMyListCtrl::ShowInPlaceList( int nItem, int nCol,
					CStringList &lstItems, int nSel )
{
	// The returned pointer should not be saved

	// Make sure that the item is visible
	if( !EnsureVisible( nItem, TRUE ) ) return NULL;

	// Make sure that nCol is valid 
	CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
	int nColumnCount = pHeader->GetItemCount();
	if( nCol >= nColumnCount || GetColumnWidth(nCol) < 10 )
		return NULL;

	// Get the column offset
	int offset = 0;
	for( int i = 0; i < nCol; i++ )
		offset += GetColumnWidth( i );

	CRect rect;
	GetItemRect( nItem, &rect, LVIR_BOUNDS );

	// Now scroll if we need to expose the column
	CRect rcClient;
	GetClientRect( &rcClient );
	if( offset + rect.left < 0 || offset + rect.left > rcClient.right )
	{
		CSize size;
		size.cx = offset + rect.left;
		size.cy = 0;
		Scroll( size );
		rect.left -= size.cx;
	}

	rect.left += offset+4;
	rect.right = rect.left + GetColumnWidth( nCol ) - 3 ;
	int height = rect.bottom-rect.top;
	rect.bottom += 5*height;
	if( rect.right > rcClient.right) rect.right = rcClient.right;

	DWORD dwStyle = WS_BORDER|WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL
					|CBS_DROPDOWNLIST|CBS_DISABLENOSCROLL;
	CComboBox *pList = new CInPlaceList(nItem, nCol, &lstItems, nSel);
	pList->Create( dwStyle, rect, this, IDC_IPEDIT );
	pList->SetItemHeight( -1, height);
	pList->SetHorizontalExtent( GetColumnWidth( nCol ));


	return pList;
}
Advertisement


Step 4: Handle the scroll messages


The CInPlaceList class is designed to destroy the drop down list control


and delete the object when it loses focus. Clicking on the scrollbars of


the list view control does not take away the focus from the drop down list


control. We therefore add handlers for the scrollbar messages which force


focus away from the drop down list control by setting the focus to the


list view control itself.



void CMyListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if( GetFocus() != this ) SetFocus();
	CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}

void CMyListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if( GetFocus() != this ) SetFocus();
	CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}


Step 5: Handle EndLabelEdit


Like the built in edit control, our drop down list control also sends the


LVN_ENDLABELEDIT notification when the user has selected an item. If this


notification message isnt already being handled, add a handler so that


any changes can be accepted.





void CMyListCtrl::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult)
{
	LV_DISPINFO  *plvDispInfo = (LV_DISPINFO *)pNMHDR;
 	LV_ITEM		 *plvItem = &plvDispInfo->item;

	if (plvItem->pszText != NULL)
	{
		SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText);
	}
	*pResult = FALSE;
}


Step 6: Add means for the user to initiate the edit


The sample code below is the handler for the WM_LBUTTONDOWN message. It


creates a drop down list when the user clicks on a subitem after the item


already has the focus. The code checks for the LVS_EDITLABELS style before


it creates the drop down list. Of course, this is a very simplistic implementation


and has to be modified to suit your needs.



void CMyListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	int index;
	CListCtrl::OnLButtonDown(nFlags, point);

	int colnum;
	if( ( index = HitTestEx( point, &colnum )) != -1 )
	{
		UINT flag = LVIS_FOCUSED;
		if( (GetItemState( index, flag ) & flag) == flag )
		{
			// Add check for LVS_EDITLABELS
			if( GetWindowLong(m_hWnd, GWL_STYLE) & LVS_EDITLABELS )
			{
				CStringList lstItems;
				lstItems.AddTail( "First Item");
				lstItems.AddTail( "Second Item");
				lstItems.AddTail( "Third Item");
				lstItems.AddTail( "Fourth Item");
				lstItems.AddTail( "Fifth Item");
				lstItems.AddTail( "Sixth Item");
				ShowInPlaceList( index, colnum, lstItems, 2 );
			}
		}
		else
			SetItemState( index, LVIS_SELECTED | LVIS_FOCUSED ,
					LVIS_SELECTED | LVIS_FOCUSED);
	}
}
Advertisement


Step 7: Subclass the CComboBox class


We need to subclass the CComboBox class to provide for our special requirement.


The main requirements placed on this class is that




  • It should send the LVN_ENDLABELEDIT message when the user finishes selecting
    an item

  • It should destroy itself when the edit is complete

  • The edit should be terminated when the user presses the Escape or the Enter
    key or when the user selects an item or when the control loses the input
    focus.


The listing of the header file precedes that of the implementation file.


The CInPlaceList declares five private variables. These are used for initializing


the drop down list and when the control sends the LVN_ENDLABELEDIT notification.



 
// InPlaceList.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CInPlaceList window

class CInPlaceList : public CComboBox
{
// Construction
public:
	CInPlaceList(int iItem, int iSubItem, CStringList *plstItems, int nSel);

// Attributes
public:

// Operations
public:

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CInPlaceList)
	public:
	virtual BOOL PreTranslateMessage(MSG* pMsg);
	//}}AFX_VIRTUAL

// Implementation
public:
	virtual ~CInPlaceList();

	// Generated message map functions
protected:
	//{{AFX_MSG(CInPlaceList)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnKillFocus(CWnd* pNewWnd);
	afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnNcDestroy();
	afx_msg void OnCloseup();
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()
private:
	int 	m_iItem;
	int 	m_iSubItem;
	CStringList m_lstItems;
	int 	m_nSel;
	BOOL	m_bESC;				// To indicate whether ESC key was pressed
};

/////////////////////////////////////////////////////////////////////////////

The listing of the implementation file now follows. The CInPlaceList constructor
simply saves the values passed through its arguments and initializes m_bESC
to false. The OnCreate() function creates the drop down list and initializes
it with the proper values.

The overridden PreTranslateMessage() is to ascertain that the escape
and the enter key strokes do make it to the combobox control. The escape
key and the enter key are normally pre-translated by the CDialog or the
CFormView object, we therefore specifically check for these and pass it
on to the combobox.

OnKillFocus() sends of the LVN_ENDLABELEDIT notification and destroys
the combobox control. The notification is sent to the parent of the list
view control and not to the list view control itself. When sending the
notification, the m_bESC member variable is used to determine whether to
send a NULL string.

The OnNcDestroy() function is the appropriate place to destroy the C++
object.

The OnChar() function ends the selection if the escape or the enter
key is pressed. It does this by setting focus to the list view control
which force the OnKillFocus() of the combobox control to be called. For
any other character, the OnChar() function lets the base class function
take care of it.

The OnCloseup() function is called when the user has made a selection
from the drop down list. This function sets the input focus to its parent
thus terminating the item selection.

 

// InPlaceList.cpp : implementation file
//

#include "stdafx.h"
#include "InPlaceList.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CInPlaceList

CInPlaceList::CInPlaceList(int iItem, int iSubItem, CStringList *plstItems, int nSel)
{
	m_iItem = iItem;
	m_iSubItem = iSubItem;

	m_lstItems.AddTail( plstItems );
	m_nSel = nSel;
	m_bESC = FALSE;
}

CInPlaceList::~CInPlaceList()
{
}


BEGIN_MESSAGE_MAP(CInPlaceList, CComboBox)
	//{{AFX_MSG_MAP(CInPlaceList)
	ON_WM_CREATE()
	ON_WM_KILLFOCUS()
	ON_WM_CHAR()
	ON_WM_NCDESTROY()
	ON_CONTROL_REFLECT(CBN_CLOSEUP, OnCloseup)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CInPlaceList message handlers

int CInPlaceList::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CComboBox::OnCreate(lpCreateStruct) == -1)
		return -1;

	// Set the proper font
	CFont* font = GetParent()->GetFont();
	SetFont(font);

	for( POSITION pos = m_lstItems.GetHeadPosition(); pos != NULL; )
	{
		AddString( (LPCTSTR) (m_lstItems.GetNext( pos )) );
	}
	SetCurSel( m_nSel );
	SetFocus();
	return 0;
}

BOOL CInPlaceList::PreTranslateMessage(MSG* pMsg)
{
	if( pMsg->message == WM_KEYDOWN )
	{
		if(pMsg->wParam == VK_RETURN
				|| pMsg->wParam == VK_ESCAPE
				)
		{
			::TranslateMessage(pMsg);
			::DispatchMessage(pMsg);
			return TRUE;				// DO NOT process further
		}
	}

	return CComboBox::PreTranslateMessage(pMsg);
}

void CInPlaceList::OnKillFocus(CWnd* pNewWnd)
{
	CComboBox::OnKillFocus(pNewWnd);

	CString str;
	GetWindowText(str);

	// Send Notification to parent of ListView ctrl
	LV_DISPINFO dispinfo;
	dispinfo.hdr.hwndFrom = GetParent()->m_hWnd;
	dispinfo.hdr.idFrom = GetDlgCtrlID();
	dispinfo.hdr.code = LVN_ENDLABELEDIT;

	dispinfo.item.mask = LVIF_TEXT;
	dispinfo.item.iItem = m_iItem;
	dispinfo.item.iSubItem = m_iSubItem;
	dispinfo.item.pszText = m_bESC ? NULL : LPTSTR((LPCTSTR)str);
	dispinfo.item.cchTextMax = str.GetLength();

	GetParent()->GetParent()->SendMessage( WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo );

	PostMessage( WM_CLOSE );
}

void CInPlaceList::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if( nChar == VK_ESCAPE || nChar == VK_RETURN)
	{
		if( nChar == VK_ESCAPE )
			m_bESC = TRUE;
		GetParent()->SetFocus();
		return;
	}

	CComboBox::OnChar(nChar, nRepCnt, nFlags);
}

void CInPlaceList::OnNcDestroy()
{
	CComboBox::OnNcDestroy();

	delete this;
}

void CInPlaceList::OnCloseup()
{
	GetParent()->SetFocus();
}

 

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.