Creating a CListCtrl Class with Item Style Features (CListCtrlStyled Class)

Environment: VC++ 6.0, Windows 2000

Overview

This class derives from the "CListCtrl" class and allows the user to define a style for an item or subitem. My goal was to be able to do the entire job transparently to allow the same behaviour of all existing methods from its base class CListCtrl.

In fact, the usage of this class is the same than the CListCtrl class except for a few new methods that let you define a style for an item.

These NEW methods to customize an item or subitem are:

  void SetItemStyle(int nItem,int nSubItem,DWORD Style,
      bool redraw = true);
  void SetItemTxtColor(int nItem,int nSubItem,
      COLORREF txtColor,bool redraw = true);
  void SetItemBgColor(int nItem,int nSubItem,
      COLORREF txtBgColor,bool redraw = true);
  void SetItemFont(int nItem,int nSubItem,CFont * pFont,
      bool redraw = true);

This applies to a style on an entire Row, use this code:

  void SetRowStyle(int nRow,DWORD Style,bool redraw = true);
  void SetRowTxtColor(int nRow,COLORREF txtColor,
      bool redraw = true);
  void SetRowBgColor(int nRow,COLORREF txtBgColor,
      bool redraw =true);
  void SetRowFont(int nRow,CFont * pFont,bool redraw = true);

And now, to apply a style on a entire Column, use this code:

  void SetColStyle(int nCol,DWORD Style,bool redraw = true);
  void SetColTxtColor(int nCol,COLORREF txtColor,
      bool redraw = true);
  void SetColBgColor(int nCol,COLORREF txtBgColor,
      bool redraw = true);
  void SetColFont(int nCol,CFont * pFont,bool redraw = true);

Theses methods are pretty easy to use, but a little knowledge about the "DWORD Style" parameter is needed. It's a flag that can hold a combination of these values:

    LIS_BOLD           // Bold Style
    LIS_ITALIC         // Italic Style
    LIS_UNDERLINE      // Underline Style
    LIS_STROKE         // StrikeOut Style
    LIS_TXTCOLOR       // The Text Color value is valid
    LIS_BGCOLOR        // The Background Color value is valid

    LIS_NO_COL_STYLE   // The Column Style has no effect on this
                       // item/subitem
    LIS_NO_ROW_STYLE   // The Row Style has no effect on this
                       // item/subitem

    LIS_FIXED_STYLE    // The Row and Column Styles have no effect
                       // on this item/subitem

Required: If you want to set a text color or background color, you must do it with two steps:

  1. Set the desired color with a Set....TxtColor() or Set.....BgColor() method.
  2. Enable its use with a Set....Style() method and LIS_BGCOLOR or LIS_TXTCOLOR attribute values.

In addition, a few complementary methods allow you to define the style of items/subitems, rows, and also columns when they are selected.

  void SetItemSelectedStyle(int nItem,int nSubItem,
      DWORD Style,bool Redraw = true);
  void SetItemSelectedTxtColor(int nItem,int nSubItem,
      COLORREF txtColor,bool redraw = true);
  void SetItemSelectedBgColor(int nItem,int nSubItem,
      COLORREF txtBgColor,bool redraw = true);
  void SetItemSelectedFont(int nItem,int nSubItem,
      CFont * pFont,bool redraw = true);

  void SetRowSelectedStyle(int nRow,DWORD Style,
      bool redraw = true);
  void SetRowSelectedTxtColor(int nRow,COLORREF txtColor,
      bool redraw = true);
  void SetRowSelectedBgColor(int nRow,COLORREF txtBgColor,
      bool redraw = true);
  void SetRowSelectedFont(int nRow,CFont * pFont,
      bool redraw = true);

  void SetColSelectedStyle(int nCol,DWORD Style,
      bool redraw = true);
  void SetColSelectedTxtColor(int nCol,COLORREF txtColor,
      bool redraw = true);
  void SetColSelectedBgColor(int nCol,COLORREF txtBgColor,
      bool redraw = true);
  void SetColSelectedFont(int nCol,CFont * pFont,
      bool redraw = true);

And to let the user to define highlight colors to use by default for selection appearence:

  void SetHighlightTextColor(COLORREF Color);
  void SetHighlightColor(COLORREF Color);

You have special methods that let the programmer set "user fonts;" then, you use these methods. You must know that the memory management will not be performed by the class. You must do the creation, storing, and destruction of theses fonts yourself! But all internal fonts created by the class are completely managed by it.

The style properties for an item/subitems can be a merging result, as in the following example.

Example of Styles Combination

By example, this CListCtrlStyled scheme (called m_list) has the following attributes:

 
0
1
2
3
0
SpiderMan
Matrix
Shrek
Gladiator
1
Terminator
Alien IV
BraveHeart
Monsters, Inc.
2
Apollo 13
Armageddon
Usual Suspects
Code Quantum

If we set styles like this:

  m_list.SetRowStyle(0,LIS_BOLD);
  m_list.SetColStyle(2,LIS_ITALIC);

  m_list.SetItemTxtColor(1,1,RGB(0,0,255),false);
  m_list.SetItemStyle(1,1,LIS_TXTCOLOR);

You will have a list with this apearance:

 
0
1
2
3
0
SpiderMan
Matrix
Shrek
Gladiator
1
Terminator
Alien IV
BraveHeart
Monsters, Inc.
2
Apollo 13
Armageddon
Usual Suspects
Code Quantum

The CListCtrlStyled class is managed entirely by the item drawing and CFont objects (except for user-CFont objects).

How to Use This Class

You have nothing special to do!! It's exactly the same as using a CListCtrl Object except for setting a custom style. But if you create a new class that derives from CListCtrlStyled, you must verify that your MESSAGE MAP management is correctly defined like it in your .cpp file:

  BEGIN_MESSAGE_MAP(##YOURCLASS##, CListCtrlStyled)
  //{{AFX_MSG_MAP(##YOURCLASS##)
  //}}AFX_MSG_MAP
  END_MESSAGE_MAP()

An existing application should be accepted to use CListCtrlStyled instead of CListCtrl without any changes except for the declarations type. If it's not the case, maybe I forgot something. If you find a problem or bug, you can report it to me at maximus@oreka.com.

Required: If you change an existing derivate class of CListCtrl into a derivative class of CListCtrlStyled, don't forget to see the MESSAGE MAP declaration, as shown below.

In the available sample application, the code that set this CListCtrlStyled uses this code in the OnInitDialog method:

  // Set a Global Style
  //
  m_list.SetExtendedStyle(LVS_EX_GRIDLINES | LVS_EX_FLATSB |
      LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES );

  // Columns Creation
  //
  LPTSTR lpszCols[] = {_T("Column 0"),_T(>"Column 1"),
                       _T("Column 2"),_T("Column 3"),0};

  LV_COLUMN   lvColumn;
  lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvColumn.fmt = LVCFMT_LEFT;
  lvColumn.cx = 110;
  for(int x = 0; lpszCols[x] != NULL; x++)
  {  lvColumn.pszText = lpszCols[x];
  m_list.InsertColumn(x,&lvColumn);
  }

  // Create some rows
  //
  LVITEM  itemData;

  char* lpszRow[]={_T("SpiderMan"),_T("Matrix"),_T("Shrek"),
      _T("Gladiator"),_T("Terminator"),_T("Alien IV"),
      _T("BraveHeart"),_T("Monsters, Inc."),
      _T("Apollo 13"),_T("Armageddon"),_T("Usual Suspects"),
      _T("Code Quantum"),_T("Die Hard 1"),
      _T("Coyote Ugly"),_T("Final Fantasy"),_T("Ninja Scroll"),
      _T("Pearl Harbor"),_T("Titan AE"),_T("Scary Movie 2"),
      _T("Lord of the Rings"),_T("The One"),_T("Tarzan"),
      _T("StalinGrad"),_T("Cube")};

  int strIndex = -1;
  for(int nRow = 0; nRow < 6 ; nRow++)
  {
    for(int nSubItem = 0; nSubItem < 4; nSubItem++)
    {
      strIndex++;
      LPTSTR lpszItem = lpszRow[strIndex];

      itemData.iItem = nRow;
      itemData.iSubItem = nSubItem;
      itemData.state = LVIS_SELECTED;
      itemData.stateMask = LVIS_SELECTED;
      itemData.pszText = (char*)lpszItem;
      itemData.cchTextMax = strlen(itemData.pszText);
      itemData.iImage = 0;
      itemData.lParam = 1;
      itemData.iIndent = 0;
      if(nSubItem == 0)
      {  itemData.mask = LVIF_TEXT | LVIF_PARAM;
        m_list.InsertItem( &itemData );
      }
      else
      {  itemData.mask = LVIF_TEXT;
        m_list.SetItem( &itemData );
      }
    }
  }

  // Set a BackGround color
  // 
  m_list.SetTextBkColor( RGB(217,245,245) );


  // ******************************************
  //  START SPECIFIC CListCtrlStyled methods 
  // ******************************************


  // Row 0 will draw in bold
  //
  m_list.SetRowStyle(0,LIS_BOLD);

  // Set the Text color of an item
  //
  m_list.SetItemTxtColor(1,1,RGB(0,0,255),false);
  m_list.SetItemStyle(1,1,LIS_TXTCOLOR);

  // Column 2 will draw with a
  // background color and all items will be strikeout
  //
  m_list.SetColBgColor(2, RGB(220,180,180) ,false);
  m_list.SetColStyle(2, LIS_BGCOLOR | LIS_STROKE );

  // Row 4 will use Text and Background color,
  // and items will be underlined
  //
  m_list.SetRowBgColor(4, RGB(240,240,170) ,false);
  m_list.SetRowTxtColor(4, RGB(0,0,192) ,false);
  m_list.SetRowStyle(4,
                     LIS_UNDERLINE | LIS_TXTCOLOR | LIS_BGCOLOR);

  // Set a subitem style, and we specify with
  // LIS_FIXED_STYLE that it not use Column or Row style.
  //
  m_list.SetItemBgColor(0,2, RGB(0,0,168) ,false);
  m_list.SetItemTxtColor(0,2, RGB(255,255,255) ,false);
  m_list.SetItemStyle(0,2, LIS_TXTCOLOR | LIS_BGCOLOR | LIS_BOLD
      | LIS_FIXED_STYLE );


  // ---------------------------------
  // -- Selection style definition  --
  // ---------------------------------


  // General HIGHLIGHT Colors when selection
  //
  m_list.SetHighlightColor( RGB(160,210,210) );
  m_list.SetHighlightTextColor( RGB(0,0,0) );

  // Selected style for Column 2
  //
  m_list.SetColSelectedBgColor(2,RGB(185,160,160),false);
  m_list.SetColSelectedStyle(2,LIS_BGCOLOR);

  // Selected style for Row 4
  //
  m_list.SetRowSelectedBgColor(4, RGB(210,210,180) , false);
  m_list.SetRowSelectedStyle(4, LIS_BGCOLOR);

  // Specific style for an item (0,2)
  //
  m_list.SetItemSelectedBgColor(0,2,RGB(165,165,240),false);
  m_list.SetItemSelectedTxtColor(0,2,RGB(240,230,230),false);
  m_list.SetItemSelectedStyle(0,2,LIS_BGCOLOR | LIS_TXTCOLOR);

Why I Created This Class

I'm a newbie in an MFC/GUI way. I don't know how to create an owner-draw control and I'm currently managing a personal development that needs to use CListCtrl objects. These controls should be able to display a specific row's cells in bold or italic; then I was hurt by a lot of problems when I tried do do that. Some people asked me to define an owner-draw control!!! Urghh!! Just to draw a bold item??? It's boring to re-invent the wheel!!! I started to search someone else's stuff!!! I found on CodeGuru some nice and very powerful stuff!! But in all cases, these things had an owner interface and my wish is to have just a best "CListCtrl" with the same behaviours and uses!! At the same time, I found some really nice articles from CodeGuru on how use the "Custom Draw" capabilities by Roger Onslow!! His articles can be found here: Part I and Part II.

After a few roll eyes, I think that it's the best way to have a nice CListCtrl that allows the programmer to customize any styles for any item/subitem!!! But the design of his job doesn't match my need. Then I decided to write my own class and try to do a useful class.

How This Class Works

Now you know that for drawing items/subitems, I use the Custom Draw feature. This part is well described in Roger Onslow's articles, but now I will describe my way of storing the style of an item/subitem.

All items (no subitems) have an application value that Microsoft calls the "lParam" member; it's a 32-bit value. It's the only "easy" way to link a value to an item entry on the CListCtrl!! For this reason, I decided to use it! I store into the lParam of an item, and the pointer to its structure style. This structure style holds and wraps the "user-lParam" for all methods' transparancy. The item structure style also stores into a list of all subitems' structures' styles (because subitems haven't their own lParam members). With this design, the main job is to do all methods' transparancy for the user (especially for the user-lParam access), because the user must be able to use his lParam for his own application.

All components that need to store a style use the same structure, as the one that follows:

  typedef struct iLS_item
  {  LPARAM lParam;         // The user-32 bits data lParam member
    bool   mParam;          // let you know if the original item has
                            // a lParam significant member

    DWORD StyleFlag;        // The style of this item
    bool in_use;            // Allows you to know if a custom font
                            // is needed (except colors properties)

    COLORREF txtColor;      // Text color if LIS_TXTCOLOR style
                            // (default otherwise)
    COLORREF bgColor;       // BackGround color if LIS_BGCOLOR style
                            // (default otherwise)

    CArray<struct iLS_item  // Allows an individual style for
        *,struct iLS_item *> // subitems. (In some cases, this
                            // member is not significant and
        subitems;           // has a null size.)
    struct iLS_item         // Access to the row
        * row_style;        // style (valid only for the ITEM
                            // style, for other components this
                            // member is set to NULL).
    struct iLS_item *       // Access to the "SELECTED" style for
        selected_style;     // this component

    CFont * cfont;          // The CFont object pointer is used
                            // to draw this item or subitem
    bool ifont;             // Allows you to know if the CFont
                            // is an internal or user Cfont object
                            // and allows you to know if we must
                            // memory manage it!
    CFont * merged_font;    // A combination of different fonts is
                            // needed (significant only for
                            // Items/SubItems; for other components,
                            // this member is set to NULL).
  } LS_item;

Typically, this is the code for retrieving the Style structure of an item or subitem:

  int nItem = 2;              // Item index
  int nSubItem = 1            // Subitem index

  // Get the LVItem corresponding to the ITEM ( subitem = 0 )
  //
  LVITEM pItem;
  pItem->mask = LVIF_PARAM;
  pItem->iItem = nItem;
  pItem->iSubItem = 0;
  CListCtrl::GetItem(&pItem);      // Now we have access to the lParam
                                   // member that holds the pointer
                                   // to this Structure style

  LS_item * lpLS_root = (LS_item*) pItem.lParam;
  LS_item * lpLS_item = NULL;

  // But if we want a subitem style, we must access it now
  //
  if(nSubItem > 0)
    lpLS_item = lpLS_item->subitems[ nSubItem - 1 ];
  else
    lpLS_item = lpLS_root;

  // Now we have into the lpLS_item the style corresponding
  // to your desired item or subitem

With the following code, we retrieve the structure style for an item/subitem. Now, to find structures of corresponding row and columns, the following code is enough:

  LS_item * lpLS_row = lpLS_root->row_style;           // It's all
  LS_item * lpLS_col = this->columns[ nSubItem ];

    // The structure style of columns is stored into a private array
    // of the CListCtrlStyledClass

After all, we have all structures style needed to manage the style of a particular item. By the way, when the item is selected, we need to do an indirection like ->selected_style on all structures.

To retrieve the user-lParam member, it's always pretty easy; the root item structure stores the user-lParam, as shown in this code:

  LPARAM user_lParam = NULL;
  if(lpLS_root->mParam)
    user_lParam = lpLS_root->lParam;

Now I will explain a little bit more about my CFont management; all structures' style can store a pointer into a CFont object. When the structure is created, this pointer is set to NULL; when an item is being saved to be drawing, the class tests whether we need a special font or not!! If it's no, we have nothing to do.

How to Tell If the Class Knows That an Item/Subitem Needs a Special Font

Step 1

The class must find all significant structures' style for the current item/subitem!!

  • Item/Subitem Structure style
  • Row Structure style
  • Column Structure Style

Required: A little trick to perform when an item is selected. This class takes all structures' style for the selected state. Check colors' values that it needs to be used for this item. But after this, the class checks whether the structure is used for the font style (member in_use); if not, it takes back the normal structure style!!

Step 2

Retrieve all CFont objects' pointers for each structure style!

  CFont * pCFontItem;     // Item/SubItem Font
  CFont * pCFontRow;      // Row Font
  CFont * pCFontCol;      // Column Font

For each structure, the CFont pointer is the cfont member. When we want to retrieve the CFont object pointer, the class retrieves it with this scheme:

  • NULL if no special font needs
  • If a special font is need and the cfont member is NULL then we create it now
  • The last case, a special font is need and the cfont member is not NULL, then we have the font that we need

Step 3

See if only one CFont object pointer is not null. If it's the case, the font that we must use for this item/subitem is this.

In the other case, we create a merged CFont object from previous fonts!! This merged CFont object pointer is store into the merged_font of the current item/subitem structure style and use it!

In addition, to prevent CFont creation abuse, if the merged_font member is not NULL for the current item, we don't create the Font, and directly use this stored font. By the way, each time a style changes for an item, the merged_font is deleted.

Memory Usage

This management has a cost—the memory usage for store all styles, CFont objects, and so forth...

To resume all:

  • Maximum of two Structures Style for an item
  • Maximum of two Structures Style for a row
  • Maximum of two Structures Style for a column

So we can have a maximum of 2*(nCol*nRow) + 2*(nCol+nRow) Structures Style.

  • Maximum of three CFont objects for an item
  • Maximum of two CFont objects for a row
  • Maximum of two CFont objects for a column

So we can have a maximum of 3*(nCol*nRow) + 2*(nCol+nRow) CFont objects.

So this example is the poorest we can have; it's when ALL items have a particular Style and Selected Style, idem for all Rows and Columns.

Happily, it's not often the case, but you must know it!!!

Required: To improve this memory management, you can use these kinds of methods that let the user manage these fonts (but not merged fonts) himself.

  void SetItemFont(int nItem,int nSubItem,CFont * pFont,bool
      redraw = true);
  void SetRowFont(int nRow,CFont * pFont,bool redraw = true);
  void SetColFont(int nCol,CFont * pFont,bool redraw = true);
  void SetItemSelectedFont(int nItem,int nSubItem,CFont *
      pFont,bool redraw = true);
  void SetRowSelectedFont(int nRow,CFont * pFont,bool
      redraw = true);
  void SetColSelectedFont(int nCol,CFont * pFont,bool
      redraw = true);

Conclusion

I hope that this class can help someone. This is my first job on the MFC class, and I don't know whether this implementation is good enough, and give me your feedback. In all cases, this class fits my current needs, and I send it to the CodeGuru Web Site in case somebody needs something like it.

Downloads

CStyleSample Source project example - 23 Kb
CListCtrlStyled source code - 12 Kb


Comments

  • 'CListCtrl' : base class undefined

    Posted by tomjey on 11/16/2006 08:25am

    why ?

    Reply
  • 'CListCtrl' : base class undefined

    Posted by tomjey on 11/16/2006 08:00am

    why ?

    Reply
  • Change grid color

    Posted by PaulaPaziani on 08/17/2006 02:18pm

    How to change grid color of list control?

    Reply
  • helpful class

    Posted by amduka on 05/17/2006 05:24pm

    This class was very helpful to me. It enabled me to do things I couldn't do easily using the original CListCtrl class. keep up the good work.

    Reply
  • Merge Cell

    Posted by lhfiorini on 03/10/2006 01:59pm

    Hi, Does anyone merge cells with the CListCtrlStyled ? I hope so, cause it will be helpfull for me. Thanks.

    Reply
  • slowly DeleteAllItems

    Posted by bmv_guru on 03/30/2004 10:04am

    Need write in DeleteItem: if(m_bDelAllItems) return TRUE else return (CListCtrl::DeleteItem(nItem));

    • Solution to speed up DeleteAllItems()

      Posted by kirc on 02/15/2006 07:44am

      To speed up DeleteAllItems() replace the method with
      the version listed bellow.
      
      Have fun
      Kirc
      
      BOOL CListCtrlStyled::DeleteAllItems()
      {
      	// Get number of Rows
      	//
      	int nItems = CListCtrl::GetItemCount();
      
      	// Delete Each row (One by One for managed our structure destruction)
      	//
      	// 2006-02-15 KIRC
      	for(int nItem = 0; nItem < nItems; nItem++)
      	{
      		LVITEM pItem;
      		InitLVITEM(nItem,0,&pItem);
      
      		LS_item * lpLS_item = NULL;
      		lpLS_item = (LS_item *)pItem.lParam;
      
      		// Free his structure style
      		//
      		Free_LS_item(lpLS_item);
      	}
      
      	// Call the base class DeleteAllItems (maybe some treatments must be do)
      	//
      	return (CListCtrl::DeleteAllItems());
      }

      Reply
    • ???

      Posted by alforno on 10/12/2004 02:21pm

      m_bDelAllItems is not a member of the class.

      Reply
    Reply
  • Problems with GetItemData() and SetItemData.

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

    Originally posted by: William

    Hi, I have problems with SetItemData() and GetItemData(), somebody know the solution?

    Thank you very much.

    William G.S.

    Reply
  • row number 28 's problem

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

    Originally posted by: Rajesh Kumar Singh

    sir I am using your CListCtrlStyled class,the problem which I am facing is in row number 28 the function
    " LRESULT OnChangeListEdit(WPARAM wParam,LPARAM lParam) " does not get invoked at all.

    But in the rows above and below 28 work fine with the same.

    Please reply me as early as possible.

    Reply
  • Possible to bold/color only some of a cell

    Posted by Legacy on 12/29/2003 12:00am

    Originally posted by: Craig Houston

    Is it possible to only bold or color only a part of a specific cell? If so, how?

    Reply
  • How to use Tab key?

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

    Originally posted by: Rajesh Kumar Singh

    1.How to use Tab key to move cursor from one cell to another cell for editing?
    2. while editing a particular column, I want to open another dialog box on a function key like "F5".

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Live Event Date: September 19, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT In response to the rising number of data breaches and the regulatory and legal impact that can occur as a result of these incidents, leading analysts at Forrester Research have developed five important design principles that will help security professionals reduce their attack surface and mitigate vulnerabilities. Check out this upcoming eSeminar and join Chris Sherman of Forrester Research to learn how to deal with the influx of new device …

  • Specialization and efficiency are always in need. Whether it's replacing an aging roof, getting a haircut, or tuning up a car, most seek the assistance of trusted experts. The same is true in the business world, where an increasing number of companies are seeking the help of others to administer their IT systems and services. This special edition of Unleashing IT highlights a new breed of IT caretaker -- Cisco Powered service providers -- and the business advantages and operational efficiencies they …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds