Download demo project - 18 Kb
Download source files - 8 Kb
<!-- Article Starts -->
Here I present a class that helps writing Owner Draw Controls with basic -
although cute - text formatting capabilities.
The CTcxOwnerDrawEngine class is not a control class by its own, or even a
CWnd derived class. As a matter of fact, its objects work as aggregateable
members of Owner Draw Controls objects. By instantiating a CTcxOwnerDrawEngine
as a member object, such controls can rely on delegation to let the engine taking
care of the complex draw and format operations.
All we have to do is to redirect the control's DrawItem method to the
CTcxOwnerDrawEngine::DrawItem method, providing it with the draw item data
(DRAWITEMSTRUCT), and the item's tagged text string.
The tagged item's string describes both the text and the format, pretty much as other
tagged syntaxes do. Of course it's not so powerful as HTML or Rich Text Format. But,
for the basic stuff, it's pretty good.
Just to make it clear now, so I won't disappoint you later: at this version, it's
not possible to mix different font typefaces or font sizes. However, you still can
change the font color and any of its bold, italic or underline attributes (in any
For a glance, the figure that opens this article is a snapshot of the TcxOde Demo
Application screen, taken after the following tagged items were entered - you must
enter line by line.
<#1000><0><|><j>Group A<5><|><j>Group B<10><|>
<#1000><0><|><-><j>Col X<1><|><-><j>Col Y<2><|><-><j>Col Z<5><|><-><j>Col W<6><|><-><j>Col T<10><|>
<0><|> <i0> <20> Thales <1><|><j><j><#5>10 <#><2><|><#1><b10><#><5><|>---<6><|>---<10><|>
<0><|> <i0><i1><i2> <20> Ricardo <1><|><j><j><#5>100 <#><2><|><#1><b100><#><5><|>---<6><|>---<10><|>
<0><|> <i0> <20> Pacheco <1><|><j><j><#5>20 <#><2><|><#1><b20><#><5><|>---<6><|>---<10><|>
<0><|> <i0><i1> <20> Eloy <1><|><j><j><#5>50 <#><2><|><#1><b50><#><5><|>---<6><|>---<10><|>
<0><|> <i2> <20> Marcio <1><|><j><j><#5>3 <#><2><|><#1><b3><#><5><|>---<6><|>---<10><|>
<0><-><|><#1000><j><j> Total<#> <1><-><j><j><-><#5>11111 <#><2><|>
The TcxOde Demo Application uses the CTcxOdeListBox, which is a
CTcxOwnerDrawEngine equipped MFC Owner Draw CListBox.
Here is a description of each existing tag.
<#n> - Select Style
This tag selects the style n for the subsequent drawing, where
n is a decimal identifier from 0 up to 4294967294. Each style has a
text color and a font attribute (either bold, italic, underline or any combination
of those). At this version (and probably this is the last one), it's not possible
to change the font size, neither the font typeface.
Styles are independently configurable through the CTcxOwnerDrawEngine::SetStyle
I designed it to use externally configurable styles, rather than putting direct
tags to select font attributes and text colors, because I think styles are more
abstract. Therefore, you can change one style properties and all the affected
items will be changed automatically, without having to be reformatted and
reinserted in the control.
The selected style won't persist from one item to other. Every time the engine starts
drawing a new item, it resets to the default style.
If a style doesn't exist (it was not configured with CTcxOwnerDrawEngine::SetStyle),
the engine keeps using the current style.
You can use sparse style IDs (e.g. 1015 and 3). Internally, the engine keeps them in an
ordered CArray and uses binary search (which, for this particular purpose,
has better performance than the CMap, for both memory and speed).
<#> - Select Default Style
The default style is basically the style of the DC that's provided for
<n> - Tab Stop
This tag aligns the current draw position with the tab stop n, where
n is a decimal identifier from 0 up to 4294967294.
Well, at first it's a normal tab stop, but there're three major differences:
It has an ID. Therefore, you always know to which tab stop you're going to advance
the drawing position. In an ID-less tab stop List Box, for instance, if the drawing
position is already beyond the desired tab stop (the previous text was too long),
it'll advance to the next tab stop - if any -, and the item will show unaligned.
Again, it has an ID. Another advantage of labeled tab stops is they affect only
the items that refer to them. Therefore, you might have different groups of items,
each one using a separate group of tab stops, and one will not get in the way of the
The engine automatically resizes them, so the distance between any two different
tab stops is always enough to fit any text that happen to be between them. It makes
the tag useful to create tables.
<j> or <J> - Justification
This tag changes the justification alignment from left to center, and from center to
right, between two consecutive tab marks.
The engine stars aligning text to the left. If it finds this tag once, the subsequent
output will be aligned to the center, and, if it finds this tag twice or more, the
subsequent output will be aligned to the right.
For instance, the following tagged text items will produce the next figure's output.
Left <j> Center <j> Right
<j> Just Center
Left and... <j><j> Right
Since the output is once aligned to the right, extra Justification tags would have no
effect. However, do note that the Tab Stop tag will reset the alignment back to the
<in> or <In> - Icon Tag
Yes, you can attach a CImageList to the engine
(CTcxOwnerDrawEngine::AttachImageList) and instruct it to draw icons, using
this tag. Just give the icon index.
<-> - Bottom Horizontal Border
This tag instructs the engine to draw a bottom border (a 1 pixel horizontal line)
between the current tab stops, using the current style color.
<|> - Vertical Border
This tag instructs the engine to draw a vertical border (a 1 pixel vertical line)
at the current drawing position.
<bw> or <Bw> - Bar
This tag instructs the engine to draw a bar w pixels wide.
It advances the current drawing position of w pixels to the right.
The bar is painted with the current style's text color.
<< - Less Than Character
This tag draws the '<' character.
>> - Greater Than Character
This tag draws the '>' character.
CTcxOwnerDrawEngine Class Members
CTcxOwnerDrawEngine( void )
Constructs a CTcxOwnerDrawEngine object. You'll generally construct it as a member
of a Owner Draw Control object class (for an example, see demo's CTcxOdeListBox).
~CTcxOwnerDrawEngine( void )
Destructs a CTcxOwnerDrawEngine object. Do note that the destructor is not virtual.
Thus the object is not intended to be used as a base class, but as an aggregated object.
void SetStyle( DWORD dwStyleId, COLORREF clrText, BOOL bBold, BOOL bItalic, BOOL bUnderline )
Sets the attributes of the Style identified by dwStyleId.
CImageList* AttachImageList( CImageList* pImgList )
Attach a Image List object to the CTcxOwnerDrawEngine. The Image List object
must not be destructed until it's either detached or the CTcxOwnerDrawEngine
object is destructed. The CTcxOwnerDrawEngine only refers to the Image List object,
and has no control on its lifetime. The function returns the previous attached Image List
object, or NULL, if there wasn't any previous attatchment.
CImageList* DetachImageList( void )
Detaches the current Image List object. Returns the pointer to the just detached Image
List, or NULL if there's no Image List currently attached.
void DrawItem( LPDRAWITEMSTRUCT pDrw, LPCTSTR pszText )
Draws the tagged text given by pszText in the Owner Draw Item context
given by pDrw.
BOOL InvalidateIsRequired( void )
If the last DrawItem operation has moved any Tab Stop guideline, this
function returns TRUE. It indicates to a multi-item container control
(e.g. List Box) that all the other visible items should be redraw to reflect the new
aligment. The control can do this by invalidating its client area (see demo's
void DeleteFonts( void )
Deletes all cached fonts. It's done automatically when the object is destructed.
Use this to "clean-up" the engine's cache when some styles were modified.
void DeleteTabs( void )
Resets all the Tab Stop guidelines. Use this to "clean-up" the engine's tab stops after
an item is removed.
int MaxWidth( void )
Returns the width of the largest item that has been already drawn by the engine. The
owner control can use this value to resize its horizontal extension or scroll bar,
so all items will be fully visible (see demo's CTcxOdeListBox).
void SetMinimumWidth( int cx )
Sets the minimum item width, in pixels. The engine might draw items wider, but not
narrower than this size. This forces the engine to expand items with center and right
elements, even if the item could be drawn with less space.
TcxOde Demo Application Features
The demo application is just a dialog box with an edit box and a CTcxOdeListBox.
The list box's stamina is a CTcxOwnerDrawEngine object. The edit box is just for
entering tagged items strings in the list box. So, just type the text in the edit box and
press <return>. You can also delete an item by selecting it and pressing
<del>. The dialog box is resizeable.
In the demo application, I use the following Style ID pattern: biuC
Where b, i and u are the flags for font attributes bold, italic
and underline. These flags can be either 1 (on) or 0 (off). And C is a color
index, being 0 black, 1 light red, 2 light green, 3 light blue,
4 dark red, 5 dark green, and 6 dark blue.
Therefore, such a tag as <#1003> will activate the blue bold font style.
But do note that you can program whatever combination you want.
Files to Include
To use the CTcxOwnerDrawEngine, the only files you must include in your project are:
Another class that's ready and easy to use, although it's not the focus of this
article, is the CTcxOdeListBox. You don't need to use it in order to use the engine.
This is a MFC Owner Draw List Box. To use it in a Dialog Box, in the Dialog's template create
an Owner Draw List Box with the following attributes: single selection, owner draw
fixed, has strings. Then, create a CTcxOdeListBox object as a member of the
Dialog's class, and use it to subclass the List Box.
The files you'll need are: