Click here to Skip to main content
15,890,282 members
Articles / Desktop Programming / MFC
Article

CWnd/WTL based HTML List Control with different row heights

Rate me:
Please Sign up or sign in to vote.
4.90/5 (65 votes)
2 May 2006CPOL3 min read 233K   4.3K   128   83
This is a CWnd/WTL based list control; it supports basic HTML tags and multiple row heights.

Image 1

Introduction

As a shareware developer I use a lot of custom controls and stuff to make the application pretty as well as useful. For my last project, I had to build a list control with all sorts of funky stuff happening in the list items and I thought it would be nice to have a list control that could render the individual item's text in HTML.

As we know the changing of row height in CListCtrl is a real pain, funny methods like using large fonts and stuff exist but they are not pretty and largely of no use to me, so I decided to write the whole thing from scratch.

Using CHTMLListCtrl is very easy just include the required headers and call Create when you want to create it (typically in OnInitdialog).

In the .h file declare a variable:

CHTMLListCtrl m_list;

In the .cpp file:

//Dimentions for the list ctrl
CRect rcList(10,10,400,400);
m_list.Create(this,rcList,123/*Control Id*/); 

//Add imagelist if we want to display images
m_ImageList.Create(16,16,ILC_COLOR24|ILC_MASK,4,4);
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_TEST));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDR_MAINFRAME));

m_list.SetImageList(&m_ImageList);

To insert items in the ListCtrl, use the InsertItem function. It takes a few arguments:

int InsertItem(CString sText,UINT uiImage,
                int nStyle=HTML_TEXT,int nHeight=AUTO);
  • sText: The item text (it can be a simple text or a text with HTML tags depending on the nStyle parameter).
  • uiImage: The image number (if an ImageList has been attached, otherwise 0).
  • nStyle: You can specify HTML_TEXT, NORMAL_TEXT and SINGLE_LINE_TEXT (SINGLE_LINE_TEXT will end in ellipses if the text too long).
  • nHeight: You can specify the height of this row or CHTMLListCtrl will calculate it for you.

An example of InsertItem:

CString sText = 
   "<font color=#ff0000><b>Red Bold Font are scary</b></font>";
m_list.InsertItem(sText,0,HTML_TEXT);

To further customize the control's behaviour, you can use the SetExtendedStyle function:

m_list.SetExtendedStyle(HTMLLIST_STYLE_GRIDLINES|
         HTMLLIST_STYLE_CHECKBOX|HTMLLIST_STYLE_IMAGES);
  • HTMLLIST_STYLE_GRIDLINES: Shows/hides gray lines between the items.
  • HTMLLIST_STYLE_CHECKBOX: Adds checkboxes to the items.
  • HTMLLIST_STYLE_IMAGES: Adds images to the items (you have to set an ImageList through the SetImageList function for this to work).

To receive events, you need to handle the WM_NOTIFY Windows message. CHTMLListCtrl supports the following events:

  • HTMLLIST_SELECTIONCHANGED: Fired when list selection changes.
  • HTMLLIST_LBUTTONDOWN: Fired when the left mouse button is down.
  • HTMLLIST_RBUTTONDOWN: Fired when the right mouse button is down.
  • HTMLLIST_LBUTTONDBLCLICK: Fired when the control is double clicked.
  • HTMLLIST_ITEMCHECKED: Fired when the check box state changes.

You will not be able to use the class wizard to handle these events, you will have to manually add the entry in the message map.

In the message map write the following:

BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
   //{{AFX_MSG_MAP(CMyDlg)
   .
   .
   ON_NOTIFY(HTMLLIST_SELECTIONCHANGED,
         123/*list id*/,OnHTMLList_SelChanged)
END_MESSAGE_MAP()

Now add the function:

void CMyDlg::OnHTMLList_SelectionChanged(NMHDR* pNMHDR, 
                                               LRESULT*)
{
    NM_HTMLLISTCTRL *pListNMHDr = 
                  (NM_HTMLLISTCTRL*) pNMHDR;
    if(pListNMHDr->nItemNo != NONE_SELECTED)
    { 
       MessageBox(pListNMHDr->sItemText);
    }
}

Extending the control's functionality

The DrawItem function is implemented as a virtual function, so users can derive from CHTMLListCtrl and draw their own items, for example:

class CMyHTMLListCtrl : public CHTMLListCtrl 
{
public:
   void DrawItem(CDC *pDC,CRect rcItem,
          HTMLLIST_ITEM *pItem,BOOL bSelected)
   {
      //Draw individual items yourself
      ......
   }
}

Credits

The HTML rendering code is taken from Ukkie9's excellent article.

The following points are taken from Ukkie9's article:

  • The only supported tags are <p>, <br>, <font>..</font>, <b>..</b>, <i>..</i>, <u>..</u>, <strong>..</strong>, <em>..</em>, <sub>..</sub> and <sup>..</sup>. The tags <strong>..</strong> are equivalent to <b>..</b>, and <em>..</em> map to <i>..</i>.
  • The <font> tag is used only for changing the text color. It is also the only tag that can take a parameter, and this parameter should be "color" with a value in the well known HTML hexadecimal notation. For example, "<font color='#ffff00'>".
  • With the exception of tags that take parameters (currently, only the <font> tag), there can be no spaces in the tags; <p> is okay, but <p align='right'> will be considered as two words "<p" and "align='right'>". That's right: when DrawHTML() considers that something is not a valid HTML tag, it prints it as a word.
  • Special characters like < and à are not supported, you must type in the correct characters. That is, you can just use the characters "à" and "&" in the text, and "<" too.

CHTMLListCtrl also uses the CMemDC written by Keith Rules, which can be downloaded from here (it is also included in the zip files above).

History

  • 26th April, 2006
    • CalculateItemHeight bug fixed.
    • WTL version added (thanks to Ernest Laurentin).
    • SetCursor bug fixed.
    • Resizing bug fixed.
  • 23rd March, 2006
    • CFont handle released in OnDestroy.
    • Invalidate(FALSE); added in OnSize.
    • GetItemCount function added (DUH).
  • 17th March, 2006
    • Initial release.

License

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


Written By
Web Developer
India India
Programming Since the the Dreaded 286 Assembly Days

currently MFC and ATL NUT


Don't take life seriously because you can't come out of it alive.
-Warren Miller

Comments and Discussions

 
Questiondinamic update items in HtmlListCtrl.display items one by one must show in control Pin
sunil.m12324-Nov-11 1:38
sunil.m12324-Nov-11 1:38 
QuestionHICON memory leak Pin
sunil.m12316-Sep-11 20:56
sunil.m12316-Sep-11 20:56 
QuestionClistctrl related Pin
decoder_8523-Nov-10 0:43
decoder_8523-Nov-10 0:43 
GeneralSome code update (small bug found) [modified] Pin
VaKa3-Dec-09 10:10
VaKa3-Dec-09 10:10 
Questionhow to multi select items use ctrl and left mouse button Pin
bunpkin6-Sep-09 4:09
bunpkin6-Sep-09 4:09 
AnswerRe: how to multi select items use ctrl and left mouse button Pin
bunpkin6-Sep-09 7:12
bunpkin6-Sep-09 7:12 
Generalicon and picture Pin
sapna_ds16-Jun-09 0:41
sapna_ds16-Jun-09 0:41 
GeneralClear selection when clicking on a whitespac Pin
Michael Rusakow23-Apr-09 2:08
Michael Rusakow23-Apr-09 2:08 
GeneralGreat work! Plus mods. Pin
JustinDOliver22-Sep-08 10:23
JustinDOliver22-Sep-08 10:23 
GeneralRe: Great work! Plus mods. Pin
Monty222-Sep-08 15:55
Monty222-Sep-08 15:55 
QuestionHow to create this control with C# 2005? Pin
minhvc6-Mar-08 17:01
minhvc6-Mar-08 17:01 
GeneralRe: How to create this control with C# 2005? Pin
Monty26-Mar-08 19:00
Monty26-Mar-08 19:00 
Questionneed help on getting item height of DBCS string Pin
Cookie FJ7-Jan-08 17:05
Cookie FJ7-Jan-08 17:05 
GeneralMemory Leak Pin
Halloko20-Sep-07 14:09
Halloko20-Sep-07 14:09 
GeneralControl updated [modified] Pin
Vince Ricci10-Sep-07 3:24
Vince Ricci10-Sep-07 3:24 
Hi,

recently I needed to use your control on a wince platform, and I discovered lots of bugs I have fixed.
I have also updated the code so that it works as a standard CListCtrl, implementation is very simple and naive but in my case it works fine.
You can test the two versions by changing in stdafx.h :

#undef USE_ORIGINAL
//#define USE_ORIGINAL

So basically I have fixed drawing issues on PocketPC, add some notify messages :
SendSelChangeNotification(LVN_ITEMCHANGING, i, LVIF_STATE, 0x0000, LVIS_SELECTED );
SendSelChangeNotification(LVN_ITEMCHANGED, i, LVIF_STATE, 0x0000, LVIS_SELECTED );
SendSelChangeNotification(LVN_ITEMCHANGING, i, LVIF_STATE, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED );
SendSelChangeNotification(LVN_ITEMCHANGED, i, LVIF_STATE, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED );

There are still some bugs with scrollbar, I will tell you how to reproduce if you are intereted...

Another thing is I have slightly modified the way font was handled.
By default Htmllistctrl was using Tahoma font with size 11, why ????
It's more logical to use the current selected font, so I have added this in the PreSubClassWindow
:

CFont* pFont = GetFont();
if (pFont == NULL){
pFont = CFont::FromHandle( (HFONT) ::GetStockObject(SYSTEM_FONT) );
TRACE( _T("CxReportCtrl::Init : Taking default font\n") );
}
LOGFONT lf = {0};
pFont->GetLogFont(&lf);
m_font.CreateFontIndirect(&lf);
TRACE( _T("CxReportCtrl::Init : Typeface name of font = %s, height=%d\n"), lf.lfFaceName, lf.lfHeight);

On WINCE it's important to test if GetFont is not null because sometimes it is.

In DrawHTML, instead of using SYSTEM_FONT now I get the current selected font

/* get the "default" font from the DC */
SavedDC = SaveDC(hdc);
hfontBase = (HFONT) ::GetCurrentObject(hdc, OBJ_FONT);
if (hfontBase == NULL){
hfontBase = (HFONT)SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT) );
}

and by the way DrawHTML is a good idea in theory but pratically this function is not very well implemented.
For instance, there are too many approximations :

Styles = 0; /* assume the active font is normal weight, roman, non-underlined */

/* get font height (use characters with ascender and descender);
* we make the assumption here that changing the font style will
* not change the font height
*/
GetTextExtentPoint32(hdc, _T("Åy"), 2, &size);


A few year ago, I wrote a small control called CxStatic and its implementation is not very good(sorry I was young) but the thing is to use GetTextExtent to count each character and to rely on
tag.tmHeight and tag.tmInternalLeading, ...
For now I haven' seen a robust HTML drawing implementation(I am only talking about B,I,U,BR, FONT and not the full HTML standard). All the existing are too big for this simple task at least DrawHTML is getting in the right direction...

The updated control can be found as this URL :
http://www.smartdev.fr/Downloads/HTMLListControl_Updated.zip


A good idea would have been to use the standard LVITEM and to add your extensions after.


//typedef struct tagLVITEMW
//{
// UINT mask;
// int iItem;
// int iSubItem;
// UINT state;
// UINT stateMask;
// LPWSTR pszText;
// int cchTextMax;
// int iImage;
// LPARAM lParam;
//#if (_WIN32_IE >= 0x0300)
// int iIndent;
//#endif
// int iGroupId;
//} LVITEMW, FAR* LPLVITEMW;


struct HTMLLIST_ITEM
{
HTMLLIST_ITEM()
{
nItemNo = INVALID_ITEM;
lParam = 0;
nHeight = 0;
nStyle = NORMAL_TEXT;
rcItem.SetRectEmpty();
bChecked = FALSE;
bHeightSpecified = FALSE;
}
}


Anyway your code helps me a lot.
Now you can add Pocket PC and Smartphone as supported platforms Wink | ;-)
I have also started to integrate a HeaderControl,but I think that's too much work for me to implement, I let this to a brave developper crazy enough to do it.


I have also modified the text position when using HTML_TEXT, now text is vertically centered.

//Center text vertically by first calculating
//text height in memory buffer(DT_CALCRECT)
RECT rcVert;
rcVert = rc;
int nHeight = DrawHTML(pDC->GetSafeHdc(), pItem->sItemText,
pItem->sItemText.GetLength(), &rcVert, DT_LEFT|DT_WORDBREAK|DT_CALCRECT);

if (nHeight > 0)
{
int nDiff = ((rc.bottom - rc.top) - (nHeight)) / 2 ;
rc.top += nDiff;
rc.bottom = rc.top + nHeight;
}

// now we really draw
DrawHTML(pDC->GetSafeHdc(),pItem->sItemText,pItem->sItemText.GetLength(),
&rc,DT_LEFT|DT_WORDBREAK);

instead of

if(!pItem->bHeightSpecified)
{
rc.top += ITEM_PADDING_TOP;
}
else{
//Center text
RECT rcVert;
rcVert = rc;
int nHeight = DrawHTML(pDC->GetSafeHdc(), pItem->sItemText,
pItem->sItemText.GetLength(), &rcVert, DT_LEFT|DT_WORDBREAK|DT_CALCRECT);

if (nHeight > 0)
{
int nDiff = ((rc.bottom - rc.top) - (nHeight)) / 2 ;
rc.top += nDiff - ITEM_PADDING_TOP;
rc.bottom = rc.top + nHeight;
}
}

DrawHTML(pDC->GetSafeHdc(),pItem->sItemText,pItem->sItemText.GetLength(),
&rc,DT_LEFT|DT_WORDBREAK/*|DT_VCENTER*/);




-- modified at 3:42 Friday 21st September, 2007
Generalexcellent work one question Pin
lilesh12-Jul-07 2:33
lilesh12-Jul-07 2:33 
GeneralRe: excellent work one question Pin
Monty213-Jul-07 21:23
Monty213-Jul-07 21:23 
GeneralVery Impressive Pin
thready18-Jun-07 13:30
thready18-Jun-07 13:30 
GeneralAdding a hyper link Pin
Cohen Shwartz Oren7-May-07 23:40
Cohen Shwartz Oren7-May-07 23:40 
GeneralRe: Adding a hyper link Pin
Monty27-May-07 23:51
Monty27-May-07 23:51 
Generalwhich font family do you use in the htmllist ctrl Pin
Cohen Shwartz Oren6-May-07 21:01
Cohen Shwartz Oren6-May-07 21:01 
GeneralRe: which font family do you use in the htmllist ctrl Pin
AlexVM6-May-07 21:12
AlexVM6-May-07 21:12 
Generalchanging the font size Pin
Cohen Shwartz Oren5-May-07 10:08
Cohen Shwartz Oren5-May-07 10:08 
GeneralRe: changing the font size Pin
Monty27-May-07 22:13
Monty27-May-07 22:13 
QuestionNational Language Support Pin
AlexVM4-May-07 21:16
AlexVM4-May-07 21:16 

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.