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



Comments

  • Remeber the limit of 255 for lpMIS->itemHeight

    Posted by Legacy on 02/13/2004 12:00am

    Originally posted by: Craig

    If you return a height larger than this, then the list box just appears to guess the height when it comes to drawing. I added the following in the MeasureItem code

    lpMIS->itemHeight = min(itemHeight, 255);

    Hope this helps


    Reply
  • This would be awesome as a CListCtrl

    Posted by Legacy on 11/17/2000 12:00am

    Originally posted by: Matt Philmon

    Anybody know how this could be done as a CListCtrl? I need a two column list control where the first column contains a number (code) and the right column contains a description of that code that will often times be multi-lined. It would be great if it could wordwrap getting:
    
    

    Code Description
    2 This is a description that is taking up too
    much space so I'll have to word wrap.
    15 Short description.

    Reply
  • Missing code to handle case when itemID = -1 in DrawItem

    Posted by Legacy on 02/07/2000 12:00am

    Originally posted by: Po

    In DrawItem, code is needed to handle the case when itemID is -1.

    Reply
  • Works great, but I have a problem

    Posted by Legacy on 07/30/1999 12:00am

    Originally posted by: Matt Swensson

    I implemented this in my program and it works great except for one thing. It appears that if I add about 7-8 entries out of a database (basically just text fields, same as if I called AddEntry() 8 times) it has problems drawing the data unless I scroll down and then back up a few times, then all the data shows. Have you had this problem at all? Do you have any idea why this would be occurring? The data is there and I am using no colors for backdrops and none of the entries are selected. For some reason, it just doesn't draw all the text until I scroll around a bit. Any ideas?

    Matt

    Reply
  • Focus Rectangle

    Posted by Legacy on 07/29/1999 12:00am

    Originally posted by: Eli Vingot

    You may want to add the following coe to the end of CMultiLineListBox::DrawItem()

    if (lpDIS->itemAction & ODA_FOCUS)
    {
    // Draw the focus rect
    pDC->DrawFocusRect(&(lpDIS->rcItem));
    }

    Reply
  • Messaging in Multi Line List box

    Posted by Legacy on 04/04/1999 12:00am

    Originally posted by: Tejal

    I have down loaded this application and it is working fine.
    I used your class in my application also That also working fine.
    But the problem is when I mapped message on List box but it is not responsing. I want to map onselectionchange massage to be mapped.
    Pl. any solution for the same?

    Reply
  • owner draw list box

    Posted by Legacy on 02/10/1999 12:00am

    Originally posted by: David Watts

    I tried to do the same multi-line listbox implementing a horizontal scroll bar. I could get the horizontal
    scroll bar working by setting the window origin of the listbox depending on the scroll position. For some
    reason that I didn't have time to look into the scroll bar clashed with using the arrow keyboard keys and
    caused an endless message loop of redraws. I imagine that the problem is due to the device context passed as
    part of the LPDRAWITEMSTRUCT only refering to a particular item in the list box and not for the whole listbox
    viewable area. Therefore shifting the window origin will only work for the item's region of the listbox and
    not the other items with undesiriable results. It would be nice to know a suitable work around for this
    problem.
    

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date