Click here to Skip to main content
13,900,985 members
Click here to Skip to main content
Add your own
alternative version

Stats

32.5K views
3.4K downloads
26 bookmarked
Posted 13 Feb 2011
Licenced CPOL

Owner Drawn CListBox - Version 2

, 13 Feb 2011
Rate this:
Please Sign up or sign in to vote.
An owner drawn CListBox, expands items to look like a CTreeCtrl

11.png

Introduction

This is a little similar to the previous article whose link is here. The difference is, this CMultiLineListBox class supports dynamic multi-line display. When user clicks or chooses an item in the ListBox, the item will be expanded to show more information. Its look like a CTreeCtrl control.

Implementation

The CMultiLineListBox is derived from CListBox. Important: you must override DrawItem and MeasureItem virtual functions. The two functions complete the main drawing operation. In addition, it handles window messages like WM_ERASEBKGND, WM_KEYDOWN, WM_LBUTTONDOWN, WM_MOUSEMOVE and custom messages MSG_UPDATEITEM. The MSG_UPDATEITEM message is posted when the user clicks an item, drags the mouse or presses a direction key (up / down keys).

In this class, we define an important struct named LISTBOX_INFO. this struct stores information for each item in ListBox. The struct definition is like this:

// This struct store information for each item in ListBox
typedef struct _LISTBOX_INFO_
{
public:
 typedef struct _SUBNODE_INFO_ // Subnode properties
 {
 public:
  CString strText; // text content, default value is _T("")
  COLORREF fgColor; // foreground color, default color is black
  COLORREF bgColor; // background color, default color is white
  _SUBNODE_INFO_() // constructor
  {
   clean();
  }
  ~_SUBNODE_INFO_() // destructor
  {
   clean();
  }
 protected:
  inline void clean(void) // inline function used to initialize member variable
  {
   strText.Empty();
   fgColor = RGB_FOREGROUND;
   bgColor = RGB_BACKGROUND;
  }
 }SUBNODEINFO, *PSUBNODEINFO;
public:
 vector<SUBNODEINFO*> subArray; // Node properties, pre item maybe include many of subnode
 CString strText;  // text content, default value is _T("")
 COLORREF fgColor;  // foreground color, default color is black
 COLORREF bgColor;  // background color, default color is white
 _LISTBOX_INFO_()  // constructor
 {
  clean();
 }
 ~_LISTBOX_INFO_()  // destructor
 {
  clean();
 }
protected:
 inline void clean(void)  // inline function used to initialize member variable
 {
  subArray.clear();
  strText.Empty();
  fgColor = RGB_FOREGROUND;
  bgColor = RGB_BACKGROUND;
 }
}LISTBOXINFO, * PLISTBOXINFO;

In order to use this LISTBOXINFO struct, the custom member functions InsertString, AddString, AddSubString help us to add context to the ListBox.

Using the code

  • InsertString: Custom member function, used to provide a public interface for external calls. This function has four parameters, insert index, text content and the foreground / background color that you set.
/* Custom member function, Insert string and set foreground and background color for each item in ListBox. The 
return value is current insert index value. */
int CMultiLineListBox::InsertString(int nIndex, LPCTSTR pszText, COLORREF fgColor, COLORREF bgColor)
{
 LISTBOXINFO* pListBox = new LISTBOXINFO; // new and initialize
 ASSERT(pListBox);
 ASSERT((nIndex >= 0) && (nIndex <= GetCount()));
 pListBox->strText = pszText;
 pListBox->fgColor = fgColor;
 pListBox->bgColor = bgColor;
 m_sArray.insert(m_sArray.begin() + nIndex, pListBox); // insert list
 return CListBox::InsertString(nIndex, pszText); // call base class InsertString function
}        
  • AddString: Custom member function, used to provide public interface for external call. This function has three parameters, text content and foreground/background color you set.
/* Custom member function, append string and set foreground and background color for each item in ListBox. The 
return value is current insert index value. */
int CMultiLineListBox::AddString(LPCTSTR pszText, COLORREF fgColor, COLORREF bgColor)
{
 LISTBOXINFO* pListBox = new LISTBOXINFO; // new and initialize
 ASSERT(pListBox);
 pListBox->strText = pszText;
 pListBox->fgColor = fgColor;
 pListBox->bgColor = bgColor;
 m_sArray.push_back(pListBox); // add to list
 return CListBox::AddString(pszText); // call base class AddString function
}
  • AddSubString: Custom member function, used to provide a public interface for external calls. This function has four parameters, insert index, text content and the foreground / background color that you set.
/* Custom member function, append subnode string and set foreground and background color for each item in ListBox. 
The return value is current insert index value. */
void CMultiLineListBox::AddSubString(int nIndex, LPCTSTR pszText, COLORREF fgColor, COLORREF bgColor)
{
 ASSERT((nIndex >=0) && (nIndex < GetCount()));
 
 ASSERT(!m_sArray.empty());
 LISTBOXINFO* pListBox = m_sArray.at(nIndex);
 ASSERT(pListBox);
 LISTBOXINFO::SUBNODEINFO* pSubNode = new LISTBOXINFO::SUBNODEINFO; // new and initialize
 ASSERT(pSubNode);
 pSubNode->strText = pszText;
 pSubNode->fgColor = fgColor;
 pSubNode->bgColor = bgColor;
 pListBox->subArray.push_back(pSubNode); // add to subnode list
}
  • DrawItem: Override virtual function, used to draw text and set foreground / background color for each item in the ListBox.
/* DrawItem virtual function, draw text and color for each item and subnode. */
void CMultiLineListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
 // TODO:  Add your code to draw the specified item
 ASSERT(lpDrawItemStruct->CtlType == ODT_LISTBOX);
 int nIndex = lpDrawItemStruct->itemID;
 if((!m_sArray.empty())  && (nIndex < static_cast<int>(m_sArray.size())))
 {
  CDC dc;
  dc.Attach(lpDrawItemStruct->hDC);
  // Save these value to restore them when done drawing.
  COLORREF crOldTextColor = dc.GetTextColor();
  COLORREF crOldBkColor = dc.GetBkColor();
  // If this item is selected, set the background color 
  // and the text color to appropriate values. Also, erase
  // rect by filling it with the background color.
  CRect rc(lpDrawItemStruct->rcItem);
  
  LISTBOXINFO* pListBox = m_sArray.at(nIndex);
  ASSERT(pListBox);
  if ((lpDrawItemStruct->itemAction | ODA_SELECT) &&
   (lpDrawItemStruct->itemState & ODS_SELECTED))
  {
   dc.SetTextColor(pListBox->bgColor);
   dc.SetBkColor(pListBox->fgColor);
   dc.FillSolidRect(&rc, pListBox->fgColor);
   // Draw item the text.
   CRect rect(rc);
   int nItemCount = 1;
   nItemCount += static_cast<int>(pListBox->subArray.size());
   int nItemHeight = rc.Height() / nItemCount;
   rect.bottom = rect.top + nItemHeight;
   dc.DrawText(pListBox->strText, pListBox->strText.GetLength(), CRect(rect.left + 5, rect.top, 
rect.right, rect.bottom), DT_SINGLELINE | DT_VCENTER);
   
   // Draw subitem the text.
   CRect rcItem;
   rcItem.SetRectEmpty();
   rcItem.top = rect.bottom;
   rcItem.left = rect.left;
   rcItem.right = rect.right;
   rcItem.bottom = rcItem.top + nItemHeight;
   
   vector<LISTBOXINFO::SUBNODEINFO*>::const_iterator iter = pListBox->subArray.begin();
   for(; iter != pListBox->subArray.end(); ++iter)
   {
    LISTBOXINFO::SUBNODEINFO* pSubNode = *iter;
     dc.SetTextColor(pSubNode->fgColor);
     dc.SetBkColor(pSubNode->bgColor);
     dc.FillSolidRect(&rcItem, pSubNode->bgColor);
    CRect rectItem(rcItem);
    rectItem.left += 22;
    dc.DrawText(pSubNode->strText, pSubNode->strText.GetLength(), &rectItem, 
DT_SINGLELINE | DT_VCENTER);
    
    rcItem.top = rcItem.bottom;
    rcItem.bottom = rcItem.top + nItemHeight;
   }
   dc.DrawFocusRect(rc); // Draw focus rect
  }
  else
  {
   dc.SetTextColor(pListBox->fgColor);
   dc.SetBkColor(pListBox->bgColor);
   dc.FillSolidRect(&rc, pListBox->bgColor);
   // Draw the text.
   CRect rect(rc);
   rect.left += 5;
   dc.DrawText(pListBox->strText, pListBox->strText.GetLength(), &rect, DT_SINGLELINE | 
DT_VCENTER);
  }
  // Reset the background color and the text color back to their
  // original values.
  dc.SetTextColor(crOldTextColor);
  dc.SetBkColor(crOldBkColor);
  dc.Detach();
 }
}
  • MeasureItem: Override virtual function, used to calculate current text height for each item in the ListBox.
// MeasureItem virtual function, calculate text height, but the height value is fixed value in here.
void CMultiLineListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
 // TODO:  Add your code to determine the size of specified item
 ASSERT(lpMeasureItemStruct->CtlType == ODT_LISTBOX);
 lpMeasureItemStruct->itemHeight = ITEM_HEIGHT;
}
  • OnEraseBkgnd: WM_ERASEBKGND message handler function, draws background color in the ListBox.
BOOL CMultiLineListBox::OnEraseBkgnd(CDC* pDC)
{
 // Set listbox background color
 CRect rc;
 GetClientRect(&rc);
 
 CDC memDC;
 memDC.CreateCompatibleDC(pDC);
 ASSERT(memDC.GetSafeHdc());
 CBitmap bmp;
 bmp.CreateCompatibleBitmap(pDC, rc.Width(), rc.Height());
 ASSERT(bmp.GetSafeHandle());
 CBitmap* pOldbmp = (CBitmap*)memDC.SelectObject(&bmp);
 memDC.FillSolidRect(rc, LISTBOX_BACKGROUND); // Set background color which you want
 pDC->BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);
 memDC.SelectObject(pOldbmp);
 bmp.DeleteObject();
 memDC.DeleteDC();
 return TRUE;
}
  • OnKeyDown: WM_KEYDOWN message handler function, when user press direction key.
void CMultiLineListBox::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
 // TODO: Add your message handler code here and/or call default
 CListBox::OnKeyDown(nChar, nRepCnt, nFlags);
 UpdateItem();
}
  • OnLButtonDown: WM_LBUTTONDOWN message handler function, when user click item.
void CMultiLineListBox::OnLButtonDown(UINT nFlags, CPoint point)
{
 // TODO: Add your message handler code here and/or call default
 CListBox::OnLButtonDown(nFlags, point);
 UpdateItem();
}
  • OnMouseMove: WM_MOUSEMOVE message handler function, when user drag mouse.
void CMultiLineListBox::OnMouseMove(UINT nFlags, CPoint point)
{
 // TODO: Add your message handler code here and/or call default
 CListBox::OnMouseMove(nFlags, point);
 UpdateItem();
}
  • UpdateItem: Custom member function, used to user click item, press direction key or drag mouse. The windwos message WM_LBUTTONDOWN /WM_KEYDOWN / WM_MOUSEMOVE handler function call this custom function to update item in ListBox.
void CMultiLineListBox::UpdateItem()
{
 // If per item height not equal, you must calculate area between the current focus item with last one,
 // otherwise you must calculate area between the current focus item with previously focus item.
 int nIndex = GetCurSel();
 if((CB_ERR != nIndex) && (m_nFocusIndex != nIndex))
 {
  PostMessage(MSG_UPDATEITEM, (WPARAM)m_nFocusIndex, (LPARAM)nIndex);
  m_nFocusIndex = nIndex; // Set current select focus index
 }
}
  • OnUpdateItem: Custom message handler function, used to handler cutom message MSG_UPDATEITEM to refresh item status.
LRESULT CMultiLineListBox::OnUpdateItem(WPARAM wParam, LPARAM lParam)
{
 // MSG_UPDATEITEM message handler
 int nPreIndex = static_cast<int>(wParam);
 int nCurIndex = static_cast<int>(lParam);
 if(-1 != nPreIndex)
 {
  SetItemHeight(nPreIndex, ITEM_HEIGHT);
 }
 
 if(-1 != nCurIndex)
 {
  int nItemCount = 1;
  LISTBOXINFO* pListBox = m_sArray.at(nCurIndex);
  ASSERT(pListBox);
  nItemCount += static_cast<int>(pListBox->subArray.size());
  SetItemHeight(nCurIndex, ITEM_HEIGHT * nItemCount);
 }
  Invalidate(); // Update item
 return 0;
}

How to Use the Control

To integrate MultiLineListBox into your own project, you first need to add the following files to your project:

  • MultiLineListBox.h
  • MultiLineListBox.cpp

Two methods to use this control class. The One is static associate, the other is dynamic create.
First, you will also need add ListBox control to dialog template. Next, include header file MultiLineListBox.h in dialog's h file, and create a CMultiLineListBox variable (or use Class Wizard to generate a variable for CListBox object, but revised CListBox to CMultiLineListBox in .h and .cpp files).

Note: This ListBox must have styles: Owner draw:variable, Selection:Single, Has strings: TRUE, Sort: FALSE.
Finally, add the following code to OnInitDialog function in dialog.cpp file.

// OnInitDialog
...
COLORREF clr[][2] = 
 {
  {RGB(53, 0, 27), RGB(236, 255, 236)},
  {RGB(66, 0, 33), RGB(233, 255, 233)},
  {RGB(85, 0, 43), RGB(204, 255, 204)},
  {RGB(106, 0, 53), RGB(191, 255, 191)},
  {RGB(119, 0, 60), RGB(9, 255, 9)},
  {RGB(136, 0, 68), RGB(0, 236, 0)},
  {RGB(155, 0, 78), RGB(0, 225, 0)},
  {RGB(168, 0, 84), RGB(0, 204, 0)},
  {RGB(170, 0, 85), RGB(0, 185, 0)},
  {RGB(187, 0, 94), RGB(0, 170, 0)},
  {RGB(206, 0, 103), RGB(0, 151, 0)},
  {RGB(211, 0, 111), RGB(0, 136, 0)},
  {RGB(236, 0, 118), RGB(0, 117, 0)},
  {RGB(255, 108, 182), RGB(0, 98, 0)},
  {RGB(255, 121, 188), RGB(0, 89, 0)},
  {RGB(255, 138, 197), RGB(0, 70, 0)},
  {RGB(255, 157, 206), RGB(0, 53, 0)},
  {RGB(255, 170, 212), RGB(0, 36, 0)},
  {RGB(255, 193, 224), RGB(0, 21, 0)}
 };
 CString strText(_T(""));
 int nIndex = -1;
 for(int i=0; i<sizeof(clr)/sizeof(clr[0]); i++) // Add item in ListBox
 {
  strText.Format(_T("%02d - Hello, World!"), i+1);
  nIndex = m_listBox.AddString(strText, clr[i][0], clr[i][1]);
  if(i % 2)
  {
   for(int j=0; j<3; j++) // Add subnode to item in ListBox
   {
    strText.Format(_T("%02d.%02d - Hello, World!"), i+1, j+1);
    m_listBox.AddSubString(nIndex, strText, clr[i][1], clr[i][0]);
   }
  }
  else
  {
   for(int j=0; j<2; j++)
   {
    strText.Format(_T("%02d.%02d - Hello, World!"), i+1, j+1);
    m_listBox.AddSubString(nIndex, strText, clr[i][1], clr[i][0]);
   }
  }
 }
...

The other way is dynamic create, use member function Create to generate a CMultiLineListBox object.

Note: You must set LBS_OWNERDRAWVARIABLE and LBS_HASSTRINGS styles in the Create function call.

First, you include header file MultiLineListBox.h in dialog's file. Next, create a CMultiLineListBox variable (or use Class Wizard generate a variable for CListBox object, but revised name CListBox to CMultiLineListBox in .h and .cpp files). Finally, add the following code to OnInitDialog function in dialog.cpp file.

#define IDC_LISTBOX 0x11 // define resource ID
// OnInitDialog
...
CRect rc;
GetClientRect(&rc);
rc.bottom -= 35;
rc.DeflateRect(CSize(10, 10));
m_listBox.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL |
LBS_OWNERDRAWVARIABLE | LBS_HASSTRINGS, rc, this, IDC_LISTBOX);
...

After add this code, you can append the above code to add items and subnodes to the ListBox control.

Of course, I believe you can do better than this. Now try it yourself.
Good luck, and thank you!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Visual-Eleven
Software Developer (Senior)
China China
No Biography provided

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 4 Pin
maplewang9-Aug-12 5:12
membermaplewang9-Aug-12 5:12 
GeneralMemory leak? Pin
Jone_liuVC12-Jun-11 17:13
memberJone_liuVC12-Jun-11 17:13 
GeneralRe: Memory leak? Pin
Visual-Eleven12-Jun-11 19:44
memberVisual-Eleven12-Jun-11 19:44 
GeneralDownload files corrupted Pin
Smitha Nishant4-Mar-11 9:44
protectorSmitha Nishant4-Mar-11 9:44 
GeneralRe: Download files corrupted Pin
un11imig5-Apr-11 16:04
memberun11imig5-Apr-11 16:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190306.1 | Last Updated 14 Feb 2011
Article Copyright 2011 by Visual-Eleven
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid