dcsimg
 

Multi-Line ListBox

Friday Feb 5th 1999 by Scott Milne

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

Home
Mobile Site | Full Site
Copyright 2018 © QuinStreet Inc. All Rights Reserved