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 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;
}

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);
	}
}

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();
}
 



Comments

  • Where can I download fully code of this topic?

    Posted by lwk on 09/19/2010 05:28am

    This article is very useful for me, How can I get all code?

    • Using a drop down list to change a subitem

      Posted by KOOK Yan on 09/25/2012 02:16am

      I need it,thank you!

      • listctrl and combox

        Posted by ujsmk on 10/16/2012 08:07pm

        Thank you for help,I need a copy of this reference .Thanks!

        Reply
      Reply
    Reply
  • Plz. attach Sample

    Posted by Legacy on 02/09/2004 12:00am

    Originally posted by: NiceGuy

    Can anyone please attach the sample project. I really need it!!.

    Thanks,

    Reply
  • Problems with PropertySheet

    Posted by Legacy on 10/22/2003 12:00am

    Originally posted by: Avinash

    Hi,
    I am using a similar way to create the combo box in the list control. The list control is placed on a property page. Now the problem is after the combo box recieves killfocus message the whole property sheet(Wizard) closes.

    But the same code works fine with a dialog based application.

    • hope you are doing the following

      Posted by vijaymohite on 08/26/2004 11:44am

      Even I'm using this list control in one of the propertypage, i'm not getting such a problem. Possible reason could be, you may not be handling OnKillFocus() in the class which is derived form CComboBox. Hope you still need this solution.. otherwise somebody else may find it useful.. ~Vijay.

      Reply
    Reply
  • Thank you

    Posted by Legacy on 08/18/2003 12:00am

    Originally posted by: Ruediger Schindler


    great article!

    Easy to understand and to implement. It helped me very much.

    Brgds
    Ruediger

    Reply
  • Tabbing Between Cells

    Posted by Legacy on 06/18/2003 12:00am

    Originally posted by: Scott Roberts

    Thanks for this article,

    Since reading this article I have now managed to implement an edit control and a datetime picker as cells in the ListCtrl, I do however have one other problem.

    I now need to be able to TAB between the editable cells. I only have a ListCtrl with two columns, the first is a fieldname row and the second is where the user enters their data.
    Having to require the user to click on each cell before things become active, is proving a time consuming action, specially if the ListCtrl contains a lot of rows. What I would rather do is to allow the user to tab from the control and then have the focus set into the next control down.

    Can anyone help?

    Scott

    Reply
  • Great Article!

    Posted by Legacy on 05/15/2003 12:00am

    Originally posted by: Ryan Steckler

    Thank you for writing this. You saved a bunch of my time. It ported well over to the PocketPC as well.

    -r

    Reply
  • But the Combobox didn't show,why?

    Posted by Legacy on 04/24/2003 12:00am

    Originally posted by: Tina Chu

    I did as the above,but I found the combobox did't show though the related functions was executed.who know why

    Reply
  • List box clipped

    Posted by Legacy on 02/03/2003 12:00am

    Originally posted by: Wolffram

    Hi,
    I use the InPlaceList on top of a rather small listCtrl. If the InPlaceList is larger than the height of the listCtrl the lower entries are clipped.
    Any hint for forcing a non-clipping?
    When using the parent of the listCtrl as the parent for the InPlaceList, the complete list is visible, but the message flow does not work properly.

    Best regards,
    Joachim

    • Try this article

      Posted by jjwalters on 09/22/2004 04:16am

      http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/comments.php/c979/?thread=3527

      Reply
    Reply
  • OnChar in dropdown combo

    Posted by Legacy on 12/14/2002 12:00am

    Originally posted by: Stefan

    I need to get into the OnChar method. But i never get there when i use a dropdown instead of a dropdown list.

    What to do?

    Reply
  • SOMEONE COULD HELP PLEASE?

    Posted by Legacy on 09/30/2002 12:00am

    Originally posted by: Michael C.

    I have been searching some information about listctrl and how could include combobox in its cells. Now i find this article but i dont know execute.
    Could someone pass me complete code?
    Thanks.

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds