XHtmlListBox
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:
- Listboxes in this column are standard CListBox controls.
- Listboxes in this column are XHtmlListBox controls.
- XHtmlListBox with blue text, image background and HTML text (thanks to Rafal Hyps for the image of his cat).
- XHtmlListBox with blue background and HTML text.
- XHtmlListBox with green background and plain text.
Using Hyperlinks
The HTML dialog shows use of font, and http: and app: hyperlinks:
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
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:
Click to enlarge.You can also use names of the common Windows system colors:
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. |
- 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).
- 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:
- 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., " " TCHAR cCode; // code generated by XHtmlListBox TCHAR cSymbol; // character symbol displayed }; - The table is specified in XHtmlListBox.cpp:
XHtmlListBox_CHAR_ENTITIES CXHtmlListBox::m_aCharEntities[] = { { _T("&"), 0, _T('&') }, // ampersand { _T("•"), 0, _T('\x95') }, // bullet NOT IN MS SANS SERIF { _T("¢"), 0, _T('\xA2') }, // cent sign { _T("©"), 0, _T('\xA9') }, // copyright { _T("°"), 0, _T('\xB0') }, // degree sign { _T("€"), 0, _T('\x80') }, // euro sign { _T("½"), 0, _T('\xBD') }, // fraction one half { _T("¼"), 0, _T('\xBC') }, // fraction one quarter { _T(">"), 0, _T('>') }, // greater than { _T("¿"), 0, _T('\xBF') }, // inverted question mark { _T("<"), 0, _T('<') }, // less than { _T("µ"), 0, _T('\xB5') }, // micro sign { _T("·"), 0, _T('\xB7') }, // middle dot = Georgian comma { _T(" "), 0, _T(' ') }, // non-breaking space { _T("¶"), 0, _T('\xB6') }, // pilcrow sign = paragraph sign { _T("±"), 0, _T('\xB1') }, // plus-minus sign { _T("£"), 0, _T('\xA3') }, // pound sign { _T("""), 0, _T('"') }, // quotation mark { _T("®"), 0, _T('\xAE') }, // registered trademark { _T("§"), 0, _T('\xA7') }, // section sign { _T("¹"), 0, _T('\xB9') }, // superscript one { _T("²"), 0, _T('\xB2') }, // superscript two { _T("³"), 0, _T('\xB3') }, // superscript three { _T("×"), 0, _T('\xD7') }, // multiplication sign { _T("™"), 0, _T('\x99') }, // trademark NOT IN MS SANS SERIF { NULL, 0, 0 } // MUST BE LASTTo 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. - That's it! The XHtmlListBox control will now substitute the display code whenever it sees the entity name.
The following table shows how the character entities will be displayed:
| Entity | Display Symbol | Description |
|---|---|---|
| & | & | ampersand |
| • | • | bullet (not in MS SANS SERIF) |
| ¢ | ¢ | cent sign |
| © | © | copyright |
| ° | ° | degree sign |
| € | € | euro sign |
| ½ | ½ | fraction one half |
| ¼ | ¼ | fraction one quarter |
| > | > | greater than |
| ¿ | ¿ | inverted question mark |
| < | < | less than |
| µ | µ | micro sign |
| · | · | middle dot = Georgian comma |
| | non-breaking space | |
| ¶ | ¶ | pilcrow sign = paragraph sign |
| ± | ± | plus-minus sign |
| £ | £ | pound sign |
| " | " | quotation mark = double quote |
| ® | ® | registered trademark |
| § | § | section sign |
| ¹ | ¹ | superscript one |
| ² | ² | superscript two |
| ³ | ³ | superscript three |
| × | × | multiplication sign |
| ™ | ™ | 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:- Use the style
LBS_USETABSTOPSwhen creating the listbox. - 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 functionGetBaseUnits()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);
_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:
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
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 |