Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using the CComboBox Control

0.00/5 (No votes)
23 Mar 2001 1  
An entry level tutorial on using the CComboBox control

Introduction

This tutorial will show how use the CComboBox class in a very basic dialog based application. It will cover the following points:

  • Adding a CComboBox to your dialog
  • Changing the height of the Dropdown List
  • The difference in behaviour of the 3 styles of CComboBox
  • Inserting/Adding items to the CComboBox
  • Changing the width of the dropdown list
  • Using item data
  • Determining which item is selected
  • Selecting items
  • Handling CComboBox messages
  • Enhancements

A ComboBox is a composite control that consists of an Edit control and a List box control. This tutorial assumes the you are comfortable with creating a dialog based application using the VC++ 6 Class Wizard. If you require information on this topic, consult the article A Beginners Guide to Dialog Based Applications - Part 1.

Adding a CComboBox to your Dialog

When your dialog-based application is generated, go to the Resources in the Workspace window. Select the dialog IDD_COMBOBOXTUTORIAL_DIALOG in the Dialog section. To insert the ComboBox control, select it from the Control palette. The cross shaped cursor indicates where the centre of the edit control portion of the combobox will be placed.

Changing the Height of the Dropdown List

Right click on the ComboBox's 'dropdown' button. This will indicate the present size the combo box, including the extended list box. Then resize it by dragging out the rectangle as you would any other control.

The Different Behaviour of the 3 Styles of CComboBox

There are three different styles of combo box with different selection properties. To change the style of the combo box, right click on it and select Properties. Then select the Styles tab.

  • Simple type has a permanently open list box and you are able to type in Edit control.
  • Dropdown type has a 'closed' list box which can be opened by clicking on the 'drop down' button. The Edit control WILL accept input.
  • Drop List type has a 'closed' list box which can be opened by clicking on the 'drop down' button. The Edit control will NOT accept any input.

Inserting/Adding Items to the CComboBox

It is possible to add items to the combo box at coding time. This is handy for data that will never need to changed. To do this, select the Data tab and add the item, one per line. Use Ctrl+Return to go to the next line.

It is however more usual and more flexible to load the items into the combo box at run time. This way, they can be loaded from resources and be localized or be changed according other user choices.

In the demonstration, you will see that Dropdown combo is loaded from string resources, the Drop List initially uses the list that was set up in the Data tab, and the Simple combobox is left empty.

To ensure that the Simple combobox is empty, we simply call the CComboBox class ResetContent() function.

m_Simple.ResetContent();

The Dropdown is loaded from string resources by the following for loop, simply call the AddString() member function of the CComboBox class.

CString tmp;
for( int i=0; i<6; i++ )
{
    tmp.LoadString(IDS_STRING102 + i);
    m_DropDown.AddString(tmp);
}

If you then click on the Insert Long Text Item check box, the string "" will inserted after the 3rd Option in the Drop List combo box.

Firstly, the index of the string needs to found, and we simply call the InsertString() member function of the CComboBox class.

int idx = m_DropList.FindString(0, "3rd Option");
CString str;
str.LoadString(IDS_STRING108);
// we add one because we want it after
m_DropList.InsertString(idx+1, str);

The main thing to be careful of when using InsertString() is that index represents the position before it will be inserted. If the index is -1, the insert will be at the end of the list. (i.e., the same as AddString())

Changing the width of the dropdown List

It is best to design the dialog so the combo box can be large enough to fully display the longest option. This is not always possible however. In this case, it is handy to widen the drop list so that the entire string can be seen while selecting.

The CComboBox class has a function SetDroppedWidth() for this purpose. This function's input parameter is the outside width of the of the drop list in pixels. First, you need the Device Context of the combo box. Then check the length of all the strings in pixels to find the longest.

Note: We must also allow for the width of the Scroll bar and window border.

CDC* pDC = m_DropList.GetDC();
CSize sz;
int newWidth = 0;
int nWidth = m_DropList.GetDroppedWidth();
for( int i=0; i < m_DropList.GetCount();  i++ )
{
   m_DropList.GetLBText( i, str );
   sz = pDC->GetTextExtent(str);

   if(sz.cx > newWidth)
      newWidth = sz.cx;
}
// Add allowance for vertical scroll bar and edges
newWidth += (GetSystemMetrics(SM_CXVSCROLL) + 2*GetSystemMetrics(SM_CXEDGE));
nWidth = max(nWidth, newWidth);
m_DropList.SetDroppedWidth( nWidth );
m_DropList.ReleaseDC(pDC);

Check if the string is longer than the existing drop list, and don't forget to call ReleaseDC().

Calling m_DropList.GetWindowRect( &rc ); will return the width of the edit box and can therefore be used to return the drop list to its original size.

When using a simple, it's best to set the Auto Horizontal scroll Style.

Determining Which Item Is Selected

There are two ways to determine which item is selected. You can use Class Wizard to associate a control and data variable with the control.

Also, add an OnSelChanged function for the combo box with Class Wizard.

Drop List combo boxes can have an integer data variable which returns the index of the selected item, and the code can be as simple as below. UpdateData(); transfers the index to the variable.

void CComboBoxTutorialDlg::OnDropListSelchange()
{
    UpdateData();

    if( m_nDropListIndex < 0 ) return;

    CString str;
    m_DropList.GetLBText( m_nDropListIndex, str );
    CString Out;
    Out.Format( "Drop List Selection => index %d\n%s", m_nDropListIndex, str );
    AfxMessageBox( Out );
}

DropDown and Simple comboboxes can have a CString data variable which returns the selected string. To get the string, you can again call UpdateData();, or call GetCurSel().

void CComboBoxTutorialDlg::OnSimpleSelchange()
{
    CString str;
    int idx = m_Simple.GetCurSel();
    if( idx < 0 ) return;

    m_Simple.GetLBText( idx, str );
    CString Out;
    Out.Format( "Drop List Selection => index %d\n%s", idx, str );
    AfxMessageBox( Out );
}

You will notice that Class Wizard will only allow you to attach an integer variable to a DropList Combo box and a CString variable to the DropDown and Simple Combo boxes.

You can add the Data Exchange entry to attach the alternate variable type manually, but the entry and the variable declaration must be placed outside the AFX_DATA_MAP and AFX_DATA tags so that Class Wizard won't remove them.

.
.
	int		m_nDropListIndex;
	//}}AFX_DATA
	CString	m_strDropList;
.
.
.
.
	DDX_CBIndex(pDX, IDC_COMBO2, m_nDropListIndex);
	//}}AFX_DATA_MAP
	DDX_CBString(pDX, IDC_COMBO2, m_strDropList);
}

Check the string in CBN_SELCHANGE handler after the call to UpdateData().

Selecting Items

Selecting an item can be done by setting the index as below. If you use the integer variable call UpdateData(FALSE);

You can use SetCurSel( index )

OR use SelectString( after, str ) where after is the index after which to start searching. As you can see from the demo, the items are added in a different order to which they are displayed and the data remains matched.

m_nDropListIndex = 2;
UpdateData(FALSE);
m_Simple.SetCurSel(2);

CString str;
str.LoadString(IDS_STRING104);
m_DropDown.SelectString(0,str);

If you know that the index can never be set to one larger than the number of items in the list, the code can be left as it is. If there is a risk of this however, then it is wise to check and either set the index to a default item or send a message box to the user warning of the error.

if( m_Simple.GetCount() <= 2 )
{
    m_Simple.SetCurSel(0);
    AfxMessageBox( "Index is out of range, selecting default" );
}

Using Item Data

A combo box returns a zero based index of the selected item or the string as shown above. Often, the program actually uses a value other than the string or the index, and what's more, you need to get this value correct whether the items in the list are sorted or not. If the application is to support multiple languages this is even more important because sorting can be different.

To assists with this, each item in the list can have a DWORD associated with it, and this remains attached to the string regardless of the sorting.

Use SetItemData( idx, dwData ) and GetItemData( idx ). When setting the data, use the index returned by the AddString() or InsertString() functions to ensure you get it right.

int idx = m_DropDown.AddString( str.Left(pos));
m_DropDown.SetItemData( idx, dw );

To retrieve the data, get the item index and call GetItemData(idx).

.
		int idx = m_DropDown.GetCurSel();
.
.
		DWORD dw = m_DropDown.GetItemData( idx );
.

Handling CComboBox Messages

The notification messages available for combo boxes are the following:

  • CBN_ERRSPACE notification message is sent when a combo box cannot allocate enough memory.
  • CBN_SELCHANGE notification message is sent when the user changes the current selection in the list box of a combo box.
  • CBN_DBLCLK notification message is sent when the user double-clicks a string in the list box of a combo box.
  • CBN_SETFOCUS notification message is sent when a combo box receives the keyboard focus.
  • CBN_KILLFOCUS notification message is sent when a combo box loses the keyboard focus.
  • CBN_EDITCHANGE notification message is sent after the user has taken an action that may have altered the text in the edit control portion of a combo box.
  • CBN_EDITUPDATE notification message is sent when the edit control portion of a combo box is about to display altered text.
  • CBN_DROPDOWN notification message is sent when the list box of a combo box is about to be made visible.
  • CBN_CLOSEUP notification message is sent when the list box of a combo box has been closed.
  • CBN_SELENDOK notification message is sent when the user selects a list item, or selects an item and then closes the list.
  • CBN_SELENDCANCEL notification message is sent when the user selects an item, but then selects another control or closes the dialog box.

The demo handles the CBN_SELCHANGE and CBN_EDITUPDATE messages. The CBN_EDITUPDATE is handy if you like to prevent some characters from being typed.

The code below is for demonstration only and this kind of validation is best carried out by the control itself. This would involve subclassing the control and overriding of some functions. This more complex topic is discussed elsewhere on this site.

void CComboBoxTutorialDlg::OnSimpleEditupdate()
{
    UpdateData();
    if(!m_strSimple.GetLength()) return;
    DWORD dwSel =  m_Simple.GetEditSel();
    // Only do this if no characters are selected
    if( LOWORD( dwSel ) != LOWORD( dwSel ) ) return;

    if( ispunct( m_strSimple[LOWORD( dwSel )-1] ))
    {
        MessageBeep(0);
        m_strSimple = m_strSimple.Left(LOWORD( dwSel )-1) + m_strSimple.Mid(LOWORD( dwSel ));
        UpdateData(FALSE);
        // put caret back where it was
        m_Simple.SetEditSel( LOWORD( dwSel ), LOWORD( dwSel ) );
//      AfxMessageBox( "Punctuation Characters are not permitted." );
    }
}

A word or two of caution. Validation can also be done in the CBN_KILLFOCUS handler. If you do this, take care in the way you change incorrect data back and pop up message boxes that also take focus from the control. The program can finish up in an unfortunate loop of validation and message box display.

Enhancements

Some code you may like to play with, is to vary the number of lines of items displayed in the drop down list. This will prevent empty lines at the end of the list and change the height from what was set in the resource editor. The framework does handle some of these things already but sometimes it's good to have the control.

void CComboBoxTutorialDlg::SetNumberOfLines(int nLines)
{
    // This limits the maximum number of visible entries to 7
    nLines = min( max(nLines, 1), 7 );
    CRect lprect;
    m_DropDown.GetWindowRect( &lprect );
    lprect.bottom = lprect.top + nLines * m_DropDown.GetItemHeight( -1 ) + lprect.Height();
    m_DropDown.SetWindowPos(NULL, 0, 0, lprect.Width(), lprect.Height(), SWP_NOMOVE | SWP_NOZORDER );
}

The ENTER key, by default, is only processed by the combo box when the drop list is open, and in this case it has the same effect as a mouse click and makes the selection. A Simple combo box does not process the ENTER key at all and is passed to the parent dialog. If you wish to handle the ENTER key in any other way, the combo box will need to be subclassed and handler written for the purpose. You may like to check out Implementing an autocompleting Combobox - By Chris Maunder as a starting point to which the handler can be added.

Conclusion

The Message handlers in this tutorial are the most commonly used and should indicate that they need not be very complex. More complex issues are handled elsewhere on this site.

Happy programming!

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.

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