Multi-Column Tabbed Checked List Box


Download Source Code and Example

Checked list boxes are often used to show multiple choices that are from multiple-column data source. You might want to try to put a tab between words to separate each item in the list, like its parent, list box.

But if you set LBS_USETABSTOPS for your checked list box style and call SetTabStops() function for your checked list box, all you get is some black marks where the tabs suppose to be in your checked list box.

You might solve this problem by using fonts with constant stroke width (fixed-pitch), like Pica, Elite, and Courier New, then set fixed length for the strings you add. If you are not using these fonts in your checked list boxes, the class CTabCheckListBox is the easiest way to do it.

The class, CTabCheckListBox, implements owner drawn checked list box derived from the CCheckListBox, which overrides the CCheckListBox::DrawItem(), adds SetTabStops() functions that are inherited from CListBox. You are going to use the same ways to setup tab stops as CListBox do. The class CTabCheckListBox is simple, and using this class is simple too, just like normal list box you use tabs, and follow the rules for building normal checked list box.

The code of CTabCheckListBox::DrawItem() mostly comes from CCheckListBox::DrawItem() by using TabbedTextOut() instead of ExtTextOut().

void CTabCheckListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	ASSERT((GetStyle() & (LBS_OWNERDRAWFIXED | LBS_HASSTRINGS)) ==
		(LBS_OWNERDRAWFIXED | LBS_HASSTRINGS));

	CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

	if (((LONG)(lpDrawItemStruct->itemID) >= 0) &&
		(lpDrawItemStruct->itemAction & (ODA_DRAWENTIRE | ODA_SELECT)))
	{
		int cyItem = GetItemHeight(lpDrawItemStruct->itemID);
		BOOL fDisabled = !IsWindowEnabled() || !IsEnabled(lpDrawItemStruct->itemID);
		COLORREF newTextColor = fDisabled ?
			RGB(0x80, 0x80, 0x80) : GetSysColor(COLOR_WINDOWTEXT);  // light gray
		COLORREF oldTextColor = pDC->SetTextColor(newTextColor);
		COLORREF newBkColor = GetSysColor(COLOR_WINDOW);
		COLORREF oldBkColor = pDC->SetBkColor(newBkColor);

		if (newTextColor == newBkColor)
			newTextColor = RGB(0xC0, 0xC0, 0xC0);   // dark gray

		if (!fDisabled && ((lpDrawItemStruct->itemState & ODS_SELECTED) != 0))
		{
			pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
			pDC->SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
		}

		if (m_cyText == 0)
			VERIFY(cyItem >= CalcMinimumItemHeight());

		CString strText;
		GetText(lpDrawItemStruct->itemID, strText);

		pDC->ExtTextOut(lpDrawItemStruct->rcItem.left,
			lpDrawItemStruct->rcItem.top + max(0, (cyItem - m_cyText) / 2),
			ETO_OPAQUE, &(lpDrawItemStruct->rcItem), "", 0, NULL);

		pDC->TabbedTextOut(lpDrawItemStruct->rcItem.left,
			lpDrawItemStruct->rcItem.top + max(0, (cyItem - m_cyText) / 2),
			strText, strText.GetLength(), m_nTabStops, m_lpnEachStop, lpDrawItemStruct->rcItem.left);

		pDC->SetTextColor(oldTextColor);
		pDC->SetBkColor(oldBkColor);
	}

	if ((lpDrawItemStruct->itemAction & ODA_FOCUS) != 0)
		pDC->DrawFocusRect(&(lpDrawItemStruct->rcItem));
}

In order to use TabbedTextOut(), we need to convert dialog unit to logical unit (or device unit). For the dialog box using the system font, ::GetDialogBaseUnits() will do the work; I couldn't find a exact way to get the dialog unit for using the non-system font. But the code here does do the work we want.

int CTabCheckListBox::GetAverageCharWidths()
{
	CFont* pFont = GetFont();
	LOGFONT lf;
	pFont->GetLogFont(&lf);
	int nBaseUnit = lf.lfWidth;
	if(nBaseUnit == 0)
		nBaseUnit = LOWORD(GetDialogBaseUnits());
	return nBaseUnit;
}
To use CTabCheckListBox:
  1. Add a list box control to your dialog resource. Setup the list box style by selecting LBS_OWNERDRAWFIXED, LBS_HASSTRINGS (without LBS_SORT selected). If you want to use the other checked list box styles, you are going to make your own class.
  2. Insert TabCheckListBox.cpp and TabCheckListBox.h files into your project.
  3. Add an instance of CTabCheckListBox in your dialog box member data.
  4. CTabCheckListBox m_ctrlCheckListBox;

  5. Call SubclassDlgItem() to initialize the class in your OnInitDialog();
  6. m_ctrlCheckListBox.SubclassDlgItem(IDC_LIST1, this);

  7. Set tab-stop position before you add strings to your checked list box. There are 3 functions to set the tab-stop, same as the CListBox::SetTabStops().
  8. void SetTabStops( );

    BOOL SetTabStops( const int& cxEachStop );

    BOOL SetTabStops( int nTabStops, LPINT rgTabStops );

    To set equal tab stops to the default size of 2 dialog units, call the parameterless version of this member function. To set equal tab stops to a size other than 2, call the version with the cxEachStop argument.

    To set tab stops to an array of sizes, use the version with the rgTabStops and nTabStops arguments. A tab stop will be set for each value in rgTabStops, up to the number specified by nTabStops.

    Referring CListBox::SetTabStops for how to using these functions.

  9. Add Strings to your checked list box, like

CString str;

str.Format(_T("%s\t%s\t%d"), "aaZAaa", "dddaaa", 1);

m_ctrlCheckListBox.AddString(str);

The demo project shows how to use CTabCheckListBox by setting different tab stops and using different font.

Last updated: 6 June 1998