.: Home :: Contact Us :.  
  



Hans Dietrich Software

free and licensed software

   

XHtmlListBox image image image

Listboxes are ubiquitous in Windows UI applications. They are used for everything from selection to logging. However, standard listboxes are not good at handling very large lists (> 1,000 entries); large lists become slow and unresponsive. Also, listboxes are very limited in actual text presentation. Finally, except for mouse clicks, the entries in a standard listbox offer no opportunity for user interaction. These considerations led to the development of XHtmlListBox, a virtual listbox that offers HTML text formatting and anchor links. XHtmlListBox is a Win32 control with no dependency on MFC; it can be used in MFC and non-MFC applications, and comes with full source code, like all Hans Dietrich Software Components.

XHtmlListBox Demo

The demo project provides a sample app that shows what the XHtmlListBox control looks like:

screenshot

  1. Listboxes in this column are standard CListBox controls.
  2. Listboxes in this column are XHtmlListBox controls.
  3. XHtmlListBox with blue text, image background and HTML text (thanks to Rafal Hyps for the image of his cat).
  4. XHtmlListBox with blue background and HTML text.
  5. XHtmlListBox with green background and plain text.

Using Hyperlinks

The HTML dialog shows use of font, and http: and app: hyperlinks:

screenshot

In the above dialog, the anchor text is displayed in the status bar of the dialog when the dialog receives the registered windows message WM_XHTMLDRAWLB_LINK_HOVER. The message map entry for this message is:

    ON_REGISTERED_MESSAGE(WM_XHTMLDRAWLB_LINK_HOVER, OnLinkHover)
The handler for this message is:
//=============================================================================
// This function shows how the link anchor text can be displayed.
//    wParam - low word = control id of XHtmlListBox
//    wParam - high word = TRUE if mouse is hovering, otherwise FALSE 
//    lParam - pointer to anchor text
LRESULT CHtmlDisplayDialog::OnLinkHover(WPARAM wParam, LPARAM lParam)
//=============================================================================
{
    CString s = _T(" ");
    s += (TCHAR *)lParam;
    if ((LOWORD(wParam) == IDC_LIST1) && HIWORD(wParam) && lParam)
        m_StatusBar.SetPaneText(0, s);
    else
        m_StatusBar.SetPaneText(0, _T(" Ready"));
    return 0;
}
Similarly, when an app: link is clicked, the dialog receives the registered windows message WM_XHTMLDRAWLB_APP_LINK_CLICKED, with the numeric id in the high-order word of the wParam parameter. For example, the word Musa has the app: hyperlink <a href="app:2">Musa</a>. When clicked, this hyperlink will send the WM_XHTMLDRAWLB_APP_LINK_CLICKED message with 2 in the high-order word of wParam. The message map entry for this message is:
    ON_REGISTERED_MESSAGE(WM_XHTMLDRAWLB_APP_LINK_CLICKED, OnAppLinkClicked)
The handler for this message is:
//=============================================================================
// This function handles WM_XHTMLDRAWLB_APP_LINK_CLICKED messages sent by 
// the XHtmlListBox control when an APP: link is clicked.
//    wParam - low word = control id of XHtmlListBox
//    wParam - high word = numeric id of app link 
//    lParam - pointer to anchor text
LRESULT CHtmlDisplayDialog::OnAppLinkClicked(WPARAM wParam, LPARAM)
//=============================================================================
{
    if (LOWORD(wParam) == IDC_LIST1)
    {
        CString s = _T("");

        switch (HIWORD(wParam))
        {
            default:
                s.Format(_T("Unknown id: %d"), HIWORD(wParam));
                break;

            case 1:
                s = _T("Latium is the region of central western Italy in which ")
                    _T("the city of Rome was founded and grew to be the capital ")
                    _T("city of the Roman Empire.");
                break;

            case 2:
                s = _T("Virgil enlists the muse of Epic, Calliope, as a companion ")
                    _T("in the enterprise of recalling Aeneas' story.");
                break;
        }

        AfxMessageBox(s, MB_OK|MB_ICONINFORMATION);
    }

    return 0;
}

XHtmlListBox HTML Tags

The following HTML tags are supported by XHtmlListBox:

Tag Syntax Attributes
A - Anchor <A>...</A> HREF="http://www.hdsoft.org"
HREF="mailto:jon@company.com"
HREF="app:MY_COMMAND_MESSAGE"
BIG - Big Text <BIG>...</BIG>
B - Bold Text <B>...</B>
FONT - Font Change <FONT>...</FONT> COLOR="Color string"
BGCOLOR="Color string"
SIZE="Size adjustment"
FACE="Font face name"
I - Italic Text <I>...</I>
SMALL - Small Text <SMALL>...</SMALL>
STRIKE - Strike-through Text <STRIKE>...</STRIKE>
SUB - Subscript Text <SUB>...</SUB>
SUP - Superscript Text <SUP>...</SUP>
U - Underlined Text <U>...</U>

All of these tags are standard HTML, except the BGCOLOR attribute for FONT and the app: specifier for A.

Using the FONT Tag

When using any of the FONT attributes, note that they must be enclosed in quotes - for example, color="red".

COLOR and BGCOLOR

The COLOR and BGCOLOR attributes both take a color string value, which is a string in one of three forms:

  • "hex-value" - Example: "#FF0000".
  • "rgb-value" - Example: "255,0,0".
  • "color-name" - Example: "red".

    You can use any of the standard named HTML colors, as shown in the table to the right: screenshot
    Click to enlarge.
       
    You can also use names of the common Windows system colors: screenshot
    Click to enlarge.

SIZE

The SIZE attribute currently takes only relative size adjustments - plus or minus. For example, SIZE="+4" or SIZE="-2".

FACE

The FACE attribute sets the font face. Currently only one face name may be specified. Example: FACE="Courier New".

XHtmlListBox Class Members

Construction
  XHtmlListBox() Constructs a XHtmlListBox object.

Data Members
  m_hWnd The HWND attached to this control.
  m_nTabStopPositions[MAXTABSTOPS] Tab stop positions used to expand tabs.

Attributes
  int GetAnchorIndex() const Retrieves the index of the current anchor item, if successful; otherwise LB_ERR.
  COLORREF GetBackgroundColor() const Retrieves current background color.
  BOOL GetBaseUnits(HFONT hFont, SIZE& size) const Retrieves base units for font hFont.
  int GetCaretIndex() const Retrieves the zero-based index of the item that has the focus rectangle in a list box.
  BOOL GetClientRect(LPRECT lpRect) const Retrieves the client rectangle of the control.
  int GetCount() const Retrieves the virtual count of items.
  const MSG * GetCurrentMessage() const Retrieves pointer to last message received by control.
  int GetCurSel() const Retrieves index of current selected item in a single-selection listbox if successful; otherwise LB_ERR.
  HFONT GetFont() const Retrieves HFONT for font currently in use.
  BOOL GetFont(LOGFONT *pLogFont) const Retrieves LOGFONT for font currently in use.
  int GetHorizontalExtent() const Retrieves maximum width in pixels of any visible item.
  DWORD GetInitialStyle() const Retrieves style initially passed to Create().
  DWORD GetInitialStyleEx() const Retrieves extended style initially passed to Create().
  int GetItemHeight() const Retrieves item height of an item. Note that all items have the same height.
  int GetItemRect(int nIndex, LPRECT lpRect) const Retrieves the dimensions of the rectangle that bounds a list-box item as it is currently displayed in the list-box window.
  int GetPageSize() const Retrieves number of visible items, not including partial lines.
  BOOL GetRedraw() const Retrieves current value of redraw flag (TRUE = redraws enabled).
  int GetSel(int index) const Returns a positive number if the specified item is selected; otherwise, it is 0. The return value is LB_ERR if an error occurs.
  int GetSelCount() const Retrieves the count of selected items in a list box. If the list box is a single-selection list box, the return value is LB_ERR.
  int GetSelCountEx() const Retrieves the count of selected items in a list box (single, multiple, or extended selection).
  int GetSelItems(int nMaxItems, LPINT rgIndex) const Fills a buffer with an array of integers that specifies the item numbers of selected items in a multiple-selection list box.
  DWORD GetStyle() const Retrieves control's current style bits (GWL_STYLE).
  DWORD GetStyleEx() const Retrieves control's current extended style bits (GWL_EXSTYLE).
  COLORREF GetTextColor() const Retrieves current text color.
  int GetTopIndex() const Retrieves the zero-based index of the first visible item in a list box. Initially, item 0 is at the top of the list box, but if the list box is scrolled, another item may be at the top.
  int GetVisibleLines() const Retrieves number of visible items.
  BOOL IsVisible(int index) const Returns TRUE if item is visible.
  int ItemFromPoint(CXPoint point) const Returns item number that encloses point, or -1.
  int SelItemRange(BOOL bSelect, int nFirstItem, int nLastItem) Selects multiple consecutive items in a multiple-selection list box.
  int SetAnchorIndex(int index) Sets the anchor in a multiple-selection list box to begin an extended selection.
  COLORREF SetBackgroundColor(COLORREF cr) Sets background color.
  BOOL SetBackgroundImage(HINSTANCE hInstance, LPCTSTR pName, LPCTSTR pType) Set background image (resource pName of type pType).
  int SetCaretIndex(int index) Sets the focus rectangle to the item at the specified index in a multiple-selection list box.
  int SetCount(int nCount) Sets the count of virtual items.
  int SetCurSel(int index) Selects a string and scrolls it into view, if necessary. Use only with single-selection list boxes.
  void SetFont(LPCTSTR lpszFontName, int nPointSize) Sets new font from name and point size.
  void SetFont(LOGFONT& lf) Sets new font from LOGFONT.
  void SetFont(HFONT hFont) Sets new font from HFONT.
  int SetHorizontalExtent(int cx) Sets the maximum pixel width of items.
  int SetItemHeight(int nHeight) Sets the height of items in list box
  BOOL SetRedraw(BOOL bRedraw) Enables/disables drawing of the control.
  int SetSel(int index, BOOL bSelect = TRUE) Selects a string in a multiple-selection list box.
  int SetSelEx(int index, BOOL bSelect) Selects a string in a list box.
  int SetTabStops(int nEachTabStop = 2) Sets the tab stops at every nEachTabStop dialog units. To respond to a call to the SetTabStops function, the list box must have been created with the LBS_USETABSTOPS style.
  int SetTabStops(int nTabStops, LPINT rgTabStops) Sets nTabStops tab stops to the dialog unit values contained in the integer array rgTabStops. To respond to a call to the SetTabStops function, the list box must have been created with the LBS_USETABSTOPS style.
  COLORREF SetTextColor(COLORREF cr) Sets the text color.
  int SetTopIndex(int index) The system scrolls the list box until either the item specified by nIndex appears at the top of the list box or the maximum scroll range has been reached.

Operations
  virtual BOOL Create(HINSTANCE hInstance, DWORD dwStyle, DWORD dwExStyle, const RECT& rect, HWND hParent, UINT nID) Creates a XHtmlListBox object.
  BOOL EnableWindow(BOOL bEnable) Enables (bEnable = TRUE) or disables (bEnable = FALSE) the XHtmlListBox window.
  BOOL EnsureVisible(int index, BOOL bPartialOK) Ensures that the item specified by index is visible in the listbox.
  LRESULT GetDlgCode(LPMSG msg) const Retrieves the current dialog code.
  BOOL ModifyStyle(DWORD dwRemove, DWORD dwAdd) Modifies the window style.
  void ResetContent() Removes all items from listbox.
  LRESULT SendMessage(UINT message, WPARAM wParam = 0, LPARAM lParam = 0) Sends message to listbox.
  BOOL UpdateWindow() Redraws the listbox window.

Functions Not Implemented

Because of the nature of virtual lists, the following functions are not implemented:

AddString()         DeleteString()         Dir()
FindString()         FindStringExact()         GetItemData()
GetItemDataPtr()         GetText()         GetTextLen()
InitStorage()         InsertString()         SelectString()
SetColumnWidth()         SetItemData()         SetItemDataPtr()

XHtmlListBox Notifications

Notification Parameters Description
LBN_DBLCLK
(via WM_COMMAND)
wParam: The LOWORD contains the identifier of the list box; the HIWORD specifies the notification code.
lParam: Handle to the list box.
Notifies the application that the user has double-clicked an item in a list box. This notification code is sent only by a list box that has the LBS_NOTIFY style.
LBN_KILLFOCUS
(via WM_COMMAND)
wParam: The LOWORD contains the identifier of the list box; the HIWORD specifies the notification code.
lParam: Handle to the list box.
Notifies the application that the list box has lost the keyboard focus.
LBN_SELCHANGE
(via WM_COMMAND)
wParam: The LOWORD contains the identifier of the list box; the HIWORD specifies the notification code.
lParam: Handle to the list box.
Notifies the application that the selection in a list box has changed as a result of user input. This notification code is sent only by a list box that has the LBS_NOTIFY style.
LBN_SETFOCUS
(via WM_COMMAND)
wParam: The LOWORD contains the identifier of the list box; the HIWORD specifies the notification code.
lParam: Handle to the list box.
Notifies the application that the list box has received the keyboard focus.
WM_CHARTOITEM ¹ wParam: The LOWORD specifies the character code of the key the user pressed; the HIWORD specifies the current position of the caret.
lParam: Handle to the list box.
Sent by a list box with the LBS_WANTKEYBOARDINPUT style to its owner in response to a WM_CHAR message.
WM_VKEYTOITEM ² wParam: The LOWORD specifies the virtual-key code of the key the user pressed; the HIWORD specifies the current position of the caret.
lParam: Handle to the list box.
Sent by a list box with the LBS_WANTKEYBOARDINPUT style to its owner in response to a WM_KEYDOWN message.
  1. The return value specifies the action that the application performed in response to the message. A return value of –1 or –2 indicates that the application handled all aspects of selecting the item and requires no further action by the list box. A return value of 0 or greater specifies the zero-based index of an item in the list box and indicates that the list box should perform the default action for the keystroke on the specified item (constrained by the nature of the virtual listbox).
  2. The return value specifies the action that the application performed in response to the message. A return value of –2 indicates that the application handled all aspects of selecting the item and requires no further action by the list box. (See Remarks.) A return value of –1 indicates that the list box should perform the default action in response to the keystroke. A return value of 0 or greater specifies the index of an item in the list box and indicates that the list box should perform the default action for the keystroke on the specified item (constrained by the nature of the virtual listbox).

Using Character Entities

Support for character entities was one of the features most requested for the last version. I wanted to support all the commonly used ones, but I did not want a huge table with characters that most people would never use. The compromise: a table-driven entity lookup, with a table that can be easily added to.

Here's how it works:

  1. The entity table is defined in XHtmlListBox.h as
    static XHtmlListBox_CHAR_ENTITIES    m_aCharEntities[];
    Each of the table entries is defined as:
    struct XHtmlListBox_CHAR_ENTITIES
    {
        TCHAR * pszName;    // string entered in HTML - e.g., "&nbsp;"
        TCHAR   cCode;      // code generated by XHtmlListBox
        TCHAR   cSymbol;    // character symbol displayed
    };
  2. The table is specified in XHtmlListBox.cpp:
    XHtmlListBox_CHAR_ENTITIES CXHtmlListBox::m_aCharEntities[] = 
    {
        { _T("&amp;"),     0,  _T('&') },     // ampersand
        { _T("&bull;"),    0,  _T('\x95') },  // bullet      NOT IN MS SANS SERIF
        { _T("&cent;"),    0,  _T('\xA2') },  // cent sign
        { _T("&copy;"),    0,  _T('\xA9') },  // copyright
        { _T("&deg;"),     0,  _T('\xB0') },  // degree sign
        { _T("&euro;"),    0,  _T('\x80') },  // euro sign
        { _T("&frac12;"),  0,  _T('\xBD') },  // fraction one half
        { _T("&frac14;"),  0,  _T('\xBC') },  // fraction one quarter
        { _T("&gt;"),      0,  _T('>') },     // greater than
        { _T("&iquest;"),  0,  _T('\xBF') },  // inverted question mark
        { _T("&lt;"),      0,  _T('<') },     // less than
        { _T("&micro;"),   0,  _T('\xB5') },  // micro sign
        { _T("&middot;"),  0,  _T('\xB7') },  // middle dot = Georgian comma
        { _T("&nbsp;"),    0,  _T(' ') },     // non-breaking space
        { _T("&para;"),    0,  _T('\xB6') },  // pilcrow sign = paragraph sign
        { _T("&plusmn;"),  0,  _T('\xB1') },  // plus-minus sign
        { _T("&pound;"),   0,  _T('\xA3') },  // pound sign
        { _T("&quot;"),    0,  _T('"') },     // quotation mark
        { _T("&reg;"),     0,  _T('\xAE') },  // registered trademark
        { _T("&sect;"),    0,  _T('\xA7') },  // section sign
        { _T("&sup1;"),    0,  _T('\xB9') },  // superscript one
        { _T("&sup2;"),    0,  _T('\xB2') },  // superscript two
        { _T("&sup3;"),    0,  _T('\xB3') },  // superscript three
        { _T("&times;"),   0,  _T('\xD7') },  // multiplication sign
        { _T("&trade;"),   0,  _T('\x99') },  // trademark   NOT IN MS SANS SERIF
        { NULL,            0,  0 }            // MUST BE LAST
    
    To add an entry, just follow the same format as for the other entries. You can the Microsoft Character Map utility, charmap.exe, to obtain the hex display code.
  3. That's it! The XHtmlListBox control will now substitute the display code whenever it sees the entity name.

One caution when using character entities: some fonts, like MS Sans Serif, are very limited in the available glyphs. So you should verify that they display properly, or select another font before displaying them.

The following table shows how the character entities will be displayed:

Entity Display Symbol Description
&amp; & ampersand
&bull; bullet (not in MS SANS SERIF)
&cent; ¢ cent sign
&copy; © copyright
&deg; ° degree sign
&euro; euro sign
&frac12; ½ fraction one half
&frac14; ¼ fraction one quarter
&gt; > greater than
&iquest; ¿ inverted question mark
&lt; < less than
&micro; µ micro sign
&middot; · middle dot = Georgian comma
&nbsp; non-breaking space
&para; pilcrow sign = paragraph sign
&plusmn; ± plus-minus sign
&pound; £ pound sign
&quot; " quotation mark = double quote
&reg; ® registered trademark
&sect; § section sign
&sup1; ¹ superscript one
&sup2; ² superscript two
&sup3; ³ superscript three
&times; × multiplication sign
&trade; trademark (not in MS SANS SERIF)

Using Images

You can use the following types of images as backgrounds: BMP, GIF, JPG, PNG, and TIF. In the .rc file for the demo, there are sample images for each of these types:
/////////////////////////////////////////////////////////////////////////////
//
// RCDATA
//

IDB_BACKGROUND_BMP      RCDATA           "res\\Background.bmp"
IDB_BACKGROUND_GIF      RCDATA           "res\\Background.gif"
IDB_BACKGROUND_JPG      RCDATA           "res\\Background.jpg"
IDB_BACKGROUND_PNG      RCDATA           "res\\Background.png"
IDB_BACKGROUND_TIF      RCDATA           "res\\Background.tif"
Note that the RCDATA type code is used for all image types.

To specify a background image, use the SetBackgroundImage() API:

    VERIFY(m_List1.SetBackgroundImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BACKGROUND_BMP), RT_RCDATA));

Tabbed Output

Displaying columnar output in a listbox is a very common requirement. To output tabbed text, follow these steps:
  1. Use the style LBS_USETABSTOPS when creating the listbox.
  2. Set the position of the tab stops with the SetTabStop() function. This function allows you to specify the tab positions in terms of dialog units. A typical value for the average character width is about 5 pixels. For the standard tab stop set at every 8 characters, this would be 40 pixels. That translates to about 27 dialog units (DLUs) for fonts such as Tahoma. To get an accurate DLU value, you can use the function GetBaseUnits() like this:
        // Get dialog units for 40 pixels
        HFONT hFont = m_List.GetFont();
        int nPixels = 40;
        int nDialogUnits = 0;
        SIZE size;
        if (m_List.GetBaseUnits(hFont, size))
            nDialogUnits = MulDiv(nPixels, 4, size.cx);
Using the sample text
    _T("AAA\tBBB\tCCC\tDDD\tEEE\tFFF\tGGG\tHHH\tIII\tJJJ\tKKK\tLLL\tMMM\tNNN\tOOO")
the Tabbed Output Test dialog shows a CListBox and a CXHtmlListBox:

screenshot

How To Use

Step 1: Integrate XHtmlListBox Files

To integrate XHtmlListBox, add the following files to your project:

  • XHtmlListBox.cpp
  • XHtmlListBox.h
  • XHtmlDrawLB.cpp
  • XHtmlDrawLB.h
  • XNamedColors.cpp
  • XNamedColors.h
  • XString.cpp
  • XString.h
  • CXDC.h
  • CXRect.h
  • CXPoint.h
  • XBitArray.h
Note: Files marked with should be set to Not Using Precompiled Headers in C/C++ compiler options. Be sure to select All Configurations before changing this setting.

Step 2: Create the Control

Create a static control on your dialog, where you want to put the new XHtmlListBox control. Also assign a variable name to this static (e.g., IDC_STATIC_FRAME1) using ClassWizard. Using a static control as a frame is a convenient way to obtain a rect for the XHtmlListBox control. Next, include the header file XHtmlListBox.h in dialog's header file, and insert a variable for the CXHtmlListBox.

    CXHtmlListBox  m_List1;
Now you can create the XHtmlListBox:
    CRect rect;
    GetDlgItem(IDC_STATIC_FRAME1)->GetWindowRect(&rect);
    ScreenToClient(&rect);
    GetDlgItem(IDC_STATIC_FRAME1)->ShowWindow(SW_HIDE);
    DWORD dwStyle = WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE | WS_TABSTOP | WS_BORDER |
        LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_DISABLENOSCROLL |
        LBS_NOINTEGRALHEIGHT | LBS_NOTIFY | LBS_WANTKEYBOARDINPUT;
    DWORD dwExStyle = WS_EX_TRANSPARENT;     // use this for image

    VERIFY(m_List1.Create(AfxGetInstanceHandle(), dwStyle, dwExStyle, 
        rect, m_hWnd, IDC_LIST1));

    // call SetWindowPos to insert control in proper place in tab order
    ::SetWindowPos(m_List1.m_hWnd, ::GetDlgItem(m_hWnd, IDC_STATIC_FRAME1), 
        0,0,0,0, SWP_NOMOVE|SWP_NOSIZE);

    m_List1.SetCount(100000);    // set count of virtual items

Step 3: Add WM_DRAWITEM Handler

In Step 2, the statement SetCount(100000); tells the control that the virtual list contains 100,000 items. When the control needs to draw one of these items, it sends a WM_DRAWITEM message to its parent window. The parent window must handle this WM_DRAWITEM message with code like this:

//=============================================================================
// WM_DRAWITEM is sent by CXHtmlListBox::Draw() when the control is about to
// display text.  The bContainsHtml member of XHTMLLISTBOX_DATA should be set
// to TRUE if the text contains HTML.
void CXHtmlListBoxTestDlg::OnDrawItem(int nIDCtl, 
                                      LPDRAWITEMSTRUCT lpDrawItemStruct)
//=============================================================================
{
    UpdateData(TRUE);
    // create new XHTMLLISTBOX_DATA buffer - this will be deleted by XHtmlListBox
    CXHtmlListBox::XHTMLLISTBOX_DATA *buf = new CXHtmlListBox::XHTMLLISTBOX_DATA;
    buf->bContainsHtml = (lpDrawItemStruct->CtlID != IDC_LIST6) ? TRUE : FALSE;
    CString s = _T("");
    s.Format(_T("Item %d  %*.*s"), lpDrawItemStruct->itemID, 
        m_nTextlen, m_nTextlen, g_Text);
    _tcscpy(buf->text, s);
    lpDrawItemStruct->itemData = (DWORD)buf;
    CBaseDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}

For more details on how to use CXHtmlListBox, see XHtmlListBoxTestDlg.cpp.

Revision History

Version 2.1

  • Initial release.

Buy latest version

Buy XHtmlListBox 2.1 license – $30.00 per developer