Introduction
There are many places in an application where nice text formatting will give your software a polished look — like in about-boxes, splash screens, and even registration dialogs. When I look into how to do this, I find these alternatives:
- Rich edit control — This is by far the toughest to use, since it involves a very complicated API and custom code — unless you are willing to learn RTF. (I don't know about you, but all those backslashes make me dizzy.) CodeProject has many rich edit control articles here.
- CHtmlCtrl — This is a nice
CHtmlView
-based control written by Paul DiLascia. You can find it in the January 2000 MSJ. OK, so obviously I didn't go this way. Why not? Here's DiLascia's last sentence from his article, which explains how to use CHtmlCtrl in an About box:
"...a word of caution: it does take a little while to load all those Internet Explorer DLLs. If it takes 10 seconds and an hourglass to run the About dialog, users may think your app was written by a bozo."
- QHTM — This is a shareware product, and there is also a "lite" freeware version available here. This initially looked good, but then I discovered the size of the "lite" DLL was 248 Kb. This is way too much code to drag in, when all you want is simple text formatting. And besides, you don't get the source code, even with the shareware version — you have to pay extra for the source.
So with these alternatives, you must choose between complexity and/or code bloat. This was disappointing to me, but also made me think how to display simple text formatting.
First, what to base the new control on? To keep it simple as possible, I picked CStatic
. Second, what formatting to use? This was an easy decision, because it's obvious that HTML is becoming formatting standard of choice. The next decision had to do with what HTML elements to support. Here there is a not-so-clear line between doing something simple and compact, and doing what CHtmlCtrl
does. For example, right now I do not need to display tables, so this eliminated a lot of complexity. If I ever did need tables, I would probably just use CHtmlCtrl
— after all, if you really need the functionality, why not get it straight from Microsoft? The final decision was also easy: the new control would support no interactive elements, except one: hyperlinks.
CXHTMLStatic Features
The following HTML tags are supported by CXHTMLStatic
:
Tag |
Syntax |
Attributes |
A - Anchor |
<A>...</A> |
HREF="http://www.codeproject.com" HREF="mailto:hdietrich@gmail.com" HREF="app:MY_COMMAND_MESSAGE" |
BIG - Big Text |
<BIG>...</BIG> |
|
B - Bold Text |
<B>...</B> |
|
BR - Line Break |
<BR> |
|
CENTER - Center Text |
<CENTER>...</CENTER> |
|
CODE - Software Code Text |
<CODE>...</CODE> |
|
FONT - Font Change |
<FONT>...</FONT> |
COLOR="Color string" BGCOLOR="Color string" SIZE="Size adjustment" FACE="Font face name" |
HR - Horizontal Rule |
<HR> |
SIZE=Line thickness |
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
The FONT COLOR
and BGCOLOR
attributes both take a color string value
, which is a string in one of three forms:
- "color-name" - Example: "red".
- "hex-value" - Example: "#FF0000".
- "rgb-value" - Example: "255,0,0".
The SIZE
attribute for FONT
currently takes only relative size adjustments - plus or minus. For example, SIZE="+4"
or SIZE="-2"
.
Using the APP: Hyperlink
Using an APP: hyperlink in a XHTMLStatic control involves three steps:
- Define the APP: struct - here is a sample table:
#define WM_APP_COMMAND_1 (WM_APP+100)
XHTMLSTATIC_APP_COMMAND CXHTMLStaticTestDlg::m_AppCommands[] =
{
{ m_hWnd, WM_APP_COMMAND_1, 1, _T("WM_APP_COMMAND") }
};
This table only has one entry, but you can add as many entries as you need. Each entry has four elements: the first is the HWND
of the window that is to receive the message; the second is the numeric message number that will be sent to the window via SendMessage()
; the third is user-defined data that is returned in the wParam member; and the fourth is a string that ties the table entry to the HTML code. When the user clicks on the link, XHTMLStatic will scan the table trying to find a match for the string that follows the "app:". Then, it will extract the message number from the table and send the message.
- Pass the table address to the XHTMLStatic control:
m_static1.SetAppCommands(m_AppCommands, 1);
This function passes the table address and the number of entries in the table. The XHTMLStatic control makes its own copy of the table, so the parent need not ensure its persistence.
- Insert the hyperlink in the HTML — in XHTMLStaticTestDlg.cpp, the HTML for the APP: hyperlink (shown here without the font tags) is written as:
_T("<a href=\"app:WM_APP_COMMAND1\">Moby Dick</a>")
The string "WM_APP_COMMAND
" that follows the app: is what ties the hyperlink to the app command table of Step 1. Note that this string may be anything you wish; to improve readability, you can use the "string form" of the actual message command constant.
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 XHTMLStatic.h as
static XHTMLSTATIC_CHAR_ENTITIES m_aCharEntities[];
Each of the table entries is defined as:
struct XHTMLSTATIC_CHAR_ENTITIES
{
TCHAR * pszName;
TCHAR cCode;
TCHAR cSymbol;
};
- The table is specified in XHTMLStatic.cpp:
XHTMLSTATIC_CHAR_ENTITIES CXHTMLStatic::m_aCharEntities[] =
{
{ _T("&"), 0, _T('&') },
{ _T("•"), 0, _T('\x95') },
{ _T("¢"), 0, _T('\xA2') },
{ _T("©"), 0, _T('\xA9') },
{ _T("°"), 0, _T('\xB0') },
{ _T("€"), 0, _T('\x80') },
{ _T("½"), 0, _T('\xBD') },
{ _T("¼"), 0, _T('\xBC') },
{ _T(">"), 0, _T('>') },
{ _T("¿"), 0, _T('\xBF') },
{ _T("<"), 0, _T('<') },
{ _T("µ"), 0, _T('\xB5') },
{ _T("·"), 0, _T('\xB7') },
{ _T(" "), 0, _T(' ') },
{ _T("¶"), 0, _T('\xB6') },
{ _T("±"), 0, _T('\xB1') },
{ _T("£"), 0, _T('\xA3') },
{ _T("""), 0, _T('"') },
{ _T("®"), 0, _T('\xAE') },
{ _T("§"), 0, _T('\xA7') },
{ _T("¹"), 0, _T('\xB9') },
{ _T("²"), 0, _T('\xB2') },
{ _T("×"), 0, _T('\xD7') },
{ _T("™"), 0, _T('\x99') },
{ NULL, 0, 0 }
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.
- That's it! The XHTMLStatic 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 |
& |
& |
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 |
|
|
nonbreaking space |
¶ |
¶ |
pilcrow sign = paragraph sign |
± |
± |
plus-minus sign |
£ |
£ |
pound sign |
" |
" |
quotation mark = double quote |
® |
® |
registered trademark |
§ |
§ |
section sign |
¹ |
¹ |
superscript one |
² |
² |
superscript two |
× |
× |
multiplication sign |
™ |
™ |
trademark (not in MS SANS SERIF) |
CXHTMLStatic Demo
The demo project provides a sample app that shows what the XHTMLStatic control looks like:
The demo displays most of the XHTMLStatic features, including sub- and super-script, font, and http: and app: hyperlinks.
Press the "Click here to see the HTML" button and the HTML is shown in a separate dialog:
When you click on the title, you will see the parent dialog catch the APP:
message:
Current Limitations
- The supported HTML tags are limited to those listed above.
- Currently changing the size of fonts on the same line does not produce the desired result. For anyone who wishes to do so, this can be fixed by keeping track of baseline and adjusting rect for drawing text.
- There are some simplifying assumptions made when parsing the HTML - for example, it is assumed there are no spaces between the < and the beginning of the tag. Another assumption is that the sequence "<a href=" will only have one space between the "a" and the "href", and that there will be no spaces between the "href" and the "=".
How To Use
To integrate this CXHTMLStatic
into your own app, you first need to add the following files to your project:
- XHTMLStatic.cpp
- XHTMLStatic.h
- XNamedColors.cpp
- XNamedColors.h
Next, create some static controls on your dialog, where you want to put the new XHTMLStatic control. Also assign variable name to these controls using ClassWizard. Next, include the header file XHTMLStatic.h in dialog's header file, and replace the CStatic
variables with CXHTMLStatic
. This shows header file XHTMLStaticTestDlg.h from demo:
#include "XHTMLStatic.h"
class CXHTMLStaticTestDlg : public CDialog
{
public:
CXHTMLStaticTestDlg(CWnd* pParent = NULL);
enum { IDD = IDD_XHTMLSTATICTEST_DIALOG };
CXHTMLStatic m_static1;
CXHTMLStatic m_static2;
.
.
.
Final step is to put some text into XHTMLStatic control. Using the variable you added with ClassWizard, you can say
m_static1.SetWindowText(_T("<b><font size=\"+8\">Moby Dick</font></b>"));
For an example of how to use CXHTMLStatic
, see XHTMLStaticTestDlg.cpp.
Revision History
Version 1.4 - 2007 October 19
- Fixed bug where text could be written outside of control's client rect, reported (with fix) by David Pritchard.
- Expanded table of character entities.
- Added VS2005 project.
- Implemented memory DC to improve performance, suggested by conan.ks.
- Switched from WM_TIMER to WM_MOUSEMOVE for display of hand cursor, suggested by rm2 and RichardC. This prevents hand cursor from appearing on overlapping windows.
Version 1.3 - 2006 August 15
- Added transparency support, suggested by Anna.
- Added support for
WM_PRINT
, suggested by beaus07.
- Added support for <center>, requested by several readers.
- Added support for tooltips for hyperlinks.
- Load hand cursor from
IDC_HAND
, suggested by kamnas.
- Fixed font object leak, reported by furbo.
- Fixed problem with
SetWindowText()
reported by Andro67; the background and text colors are no longer reset when SetWindowText()
is called.
- Fixed bug when control is hidden, reported by RichardC.
Version 1.2 - 2004 June 12
- Changed APP: hyperlink to use HWND instead of
GetParent
.
- Added wParam to
XHTMLSTATIC_APP_COMMAND
struct.
- Added function
SetTextColor(LPCTSTR lpszColor)
.
- Added function
SetLogFont(const LOGFONT * pLogFont)
.
- Added function
SetWindowText()
to call Init and RedrawWindow
.
- Fixed bug with
XNamedColors
in handling of "255,0,0" style.
- in
SetColorFromString()
.
- Fixed bug with descenders of large serif fonts.
Version 1.1 - 2004 May 20
- Implemented SUB tag.
- Implemented SUP tag.
- Implemented BIG tag.
- Implemented SMALL tag.
- Implemented CODE tag.
- Implemented HR tag.
- Implemented APP: hyperlink.
- Implemented common character entities.
- Improved parsing performance.
- Bug fixes.
Version 1.0 - 2002 September 16
Usage
This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.