Multi-Line ListBox

For a project I have been working on for the last few months,
I wanted to add in a ListBox that would allow the user to select
a "block" of text instead of just a single line.

The resulting class CMultiLineListBox is a derived
class of CListBox. The original code for this class was taken
from the CColorListBox found in the Help documents of Visual C++
5.0 under the title "CTRLTEST Demo".

The actual ListBox must have the follow values set:

  1. Set the Ownerdraw to Variable.
  2. Make sure the chekbox for HAS_STRINGS is checked.

 

To use the class, follow these steps:

  1. Add a member variable to the desired class (CDialog,
    CFormView, etc.)

     CMultiLineListBox m_List; 
  2. Subclass the list box variable just created.
     void CMultiLineDlg::DoDataExchange(CDataExchange* pDX)
     {
        CDialog::DoDataExchange(pDX);
        //{{AFX_DATA_MAP(CCharityReportDlg)
        DDX_Control(pDX, IDC_LIST, m_List);
        //}}AFX_DATA_MAP
     }
                
  3. To add an item value for listbox item nIndex, call
    AddEntry( LPCTSTR lpszItem, COLORREF color, int nIndex );

     m_List.AddEntry( "Text" , RGB(255,255,0), 0); 

The Code

At the moment, this class is very basic to look at code wise.
But since I havent seen anything like this on this website,I
figured I would share it. It is all done using only two
overrides. The first one being WM_MEASUREITEM. This allows the
class to get the chance to calculate the required height to make
each selection block and to set this value in the ListBox. The
following values allowed me to do the work required to do the
required calculations:

The following text was taken from
Visual C++ 5.0 Help documents:

DT_WORDBREAK Specifies word-breaking. Lines are automatically
broken between words if a word would extend past the edge
of the rectangle specified by lpRect. A carriage
return/linefeed sequence will also break the line.
DT_CALCRECT Determines the width and height of the rectangle. If
there are multiple lines of text, DrawText will use the
width of the rectangle pointed to by lpRect and extend
the base of the rectangle to bound the last line of text.
If there is only one line of text, DrawText will modify
the right side of the rectangle so that it bounds the
last character in the line. In either case, DrawText
returns the height of the formatted text, but does not
draw the text.

void CMultiLineListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMIS)
{
// all items are of fixed size
// must use LBS_OWNERDRAWVARIABLE for this to work

	int nItem = lpMIS->itemID;
	CPaintDC dc(this);
	CString sLabel;
	CRect rcLabel;

	GetText( nItem, sLabel );
	GetItemRect(nItem, rcLabel);

// Calculate the required rectangle for the text and set the item height for this
// specific item based on the return value (new height).

	int itemHeight = dc.DrawText( sLabel, -1, rcLabel, DT_WORDBREAK | DT_CALCRECT );
	lpMIS->itemHeight = itemHeight;
}

The second override was for the WM_DRAWITEM call:


void CMultiLineListBox::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
	CDC* pDC = CDC::FromHandle(lpDIS->hDC);

	COLORREF rColor = (COLORREF)lpDIS->itemData; // RGB in item data

	CString sLabel;
	GetText(lpDIS->itemID, sLabel);

	// item selected
	if ((lpDIS->itemState & ODS_SELECTED) &&
		(lpDIS->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
	{
		// draw color box
		CBrush colorBrush(rColor);
		CRect colorRect = lpDIS->rcItem;

		// draw label background
		CBrush labelBrush(::GetSysColor(COLOR_HIGHLIGHT));
		CRect labelRect = lpDIS->rcItem;
		pDC->FillRect(&labelRect,&labelBrush);

		// draw label text
		COLORREF colorTextSave;
		COLORREF colorBkSave;

		colorTextSave = pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
		colorBkSave = pDC->SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
		pDC->DrawText( sLabel, -1, &lpDIS->rcItem, DT_WORDBREAK );

		pDC->SetTextColor(colorTextSave);
		pDC->SetBkColor(colorBkSave);

		return;
	}

	// item brought into box
	if (lpDIS->itemAction & ODA_DRAWENTIRE)
	{
		CBrush brush(rColor);
		CRect rect = lpDIS->rcItem;
		pDC->SetBkColor(rColor);
		pDC->FillRect(&rect,&brush);
		pDC->DrawText( sLabel, -1, &lpDIS->rcItem, DT_WORDBREAK );

		return;
	}

	// item deselected
	if (!(lpDIS->itemState & ODS_SELECTED) &&
		(lpDIS->itemAction & ODA_SELECT))
	{
		CRect rect = lpDIS->rcItem;
		CBrush brush(rColor);
		pDC->SetBkColor(rColor);
		pDC->FillRect(&rect,&brush);
		pDC->DrawText( sLabel, -1, &lpDIS->rcItem, DT_WORDBREAK );

		return;
	}
}

And just to show how the AddEntry( LPCTSTR lpszItem, COLORREF
color, int nIndex ) code looks:

void CMultiLineListBox::AddEntry(LPCTSTR lpszItem, COLORREF color, int nIndex /* 0 */)
{
	int index = InsertString(nIndex, lpszItem);
	SetItemData(index,color);
}

This class can be easily extended to provide for text colors
& fonts. However at this time it was not required for the
project I am working on. Feel free to extend this as you require.
I have included both a sample project and a sample executable for
you to look at.

Download demo project and source – 20
KB

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read