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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read