Oct 072013
 

Code Snippet to Resize CComboBox Drop Down List

I had this post in my draft for a long time. This code snippet resizes a combo box’s drop down list. Please note an application sends the CB_SETDROPPEDWIDTH message to set the minimum allowable width, in pixels, of the list box of a combo box with the CBS_DROPDOWN or CBS_DROPDOWNLIST style.

Note: MFC function CComboBox::SetDroppedWidth is just a wrapper for CB_SETDROPPEDWIDTH.

// Fill out combo box with strings of random width, for demo purpose only
void FillCombo(CComboBox& Combo)
{
	LPCTSTR lpAVeryLongString = _T("This is a very long string, the purpose of which is to test out width calculation of a combo drop down");
	TCHAR Buf[200] = {0};
 
	const int Len = _tcslen(lpAVeryLongString);
 
	Combo.Clear();
 
	for(int Index = 0; Index < 100; ++Index)
	{
		const int TrimLen = rand() % Len;
		_stprintf_s(Buf, _T("%.*s"), TrimLen, lpAVeryLongString);
		Combo.AddString(Buf);
	}
}
 
// Resize combo box drop down width based on contents
void AutoAdjustComboWidth(CComboBox& Combo)
{
	if(!Combo.GetSafeHwnd() || !::IsWindow(Combo.GetSafeHwnd()))
	{
		ASSERT(FALSE);
		return;
	}
 
	CDC& ComboDC = *Combo.GetDC();
	const int RestorePoint = ComboDC.SaveDC();
	CFont& ComboFont = *Combo.GetFont();
 
	ComboDC.SelectObject(&ComboFont);
 
	int MaxX = -1;
 
	const UINT ItemCount = Combo.GetCount();
	for(UINT Index = 0; Index < ItemCount; ++Index)
	{
		CString Text;
		Combo.GetLBText(Index, Text);
 
		// Get width of item string.
		const SIZE sz = ComboDC.GetTextExtent(Text);
 
		if(sz.cx > MaxX)
		{
			MaxX = sz.cx;
		}
	}// End for
 
	if(MaxX >  0)
	{
		const int ScrollWidth = ::GetSystemMetrics(SM_CXVSCROLL);
		const int Border = ::GetSystemMetrics(SM_CXEDGE) * 2;
		Combo.SetDroppedWidth(MaxX + ScrollWidth * 2);
	}
 
	ComboDC.RestoreDC(RestorePoint);
}

Some people might find this inconvenient as the list box width grows bigger. The way to workaround this issue is to use CComboBox::SetHorizontalExtent function as demonstrated here.

If you see bugs let me know,will fix those.

May 282009
 

CComboBox has a function called SetHorizontalExtent which doesn’t work. SDK equivalent is CB_SETHORIZONTALEXTENT which also doesn’t work. The reason for this bug is pretty lame, because WS_HSCROLL style for combo box is not set, which in turn the VS dialog editor does not provide :(. So a workaround is to open .rc file in a text editor and add WS_HSCROLL style manually for this combo.

This is how a combo box will look with a horizontal scrollbar…

Horizontal scrollbar in a combo box

Horizontal scrollbar in a combo box

Here is a sample code from MSDN which is adapted a bit for this purpose…

void CDialoTestDlg::AddHScroll( CComboBox& Cmb )
{
  // Find the longest string in the combo box.
  CString str;
  CSize sz;
  int dx = 0;

  TEXTMETRIC tm = { 0 };
  CDC* pDC = Cmb.GetDC();
  CFont* pFont = Cmb.GetFont();

  // Select the listbox font, save the old font
  CFont* pOldFont = pDC->SelectObject(pFont);

  // Get the text metrics for avg char width
  pDC->GetTextMetrics(&tm);

  for (int i = 0; i < Cmb.GetCount(); i++)
  {
    Cmb.GetLBText(i, str);
    sz = pDC->GetTextExtent(str);

    // Add the avg width to prevent clipping
    sz.cx += tm.tmAveCharWidth;

    if (sz.cx > dx)
      dx = sz.cx;
  }

  // Select the old font back into the DC
  pDC->SelectObject(pOldFont);
  Cmb.ReleaseDC(pDC);

  // Set the horizontal extent so every character of all strings can
  // be scrolled to.
  Cmb.SetHorizontalExtent(dx);
}// End AddHScroll

Setting horizontal extent is useful to prevent the combo’s drop down width from extending beyond screen limits. Note that there is another function called SetDroppedWidth which sets the drop down’s width but with no scrolling.