|
Winner August 2007 Monthly Competition |
Everyone is familiar with tree controls: every time you open Windows Explorer, you see file system hierarchy as a tree. Another kind of tree is used by some programs, such as installers, to allow you to select options to install. When all options of a particular subtree are selected, you will see checkbox with a check mark (); when no options in a subtree are selected, the checkbox next to the subtree root displays an unchecked checkbox (); and when only some of the options in a subtree are selected, you will see what is known as tri-state checkbox ().
Surprisingly, there is no direct support for tri-state checkboxes in MFC's CTreeCtrl. The closest you can get is to create a tri-state image list for your tree. While this will work, it does not give you the complete visual effect of XP themed checkboxes, such as hot-state appearance () when the mouse is hovering.
While I was researching tree controls and how to add tri-state checkboxes, I came to understand that the tree control - like the list control - can be enhanced with many useful features and nice UI effects by mechanism of custom draw. This led me to take next step: adding HTML support to tree items. It was easy to see how this would be possible, since I had just completed my XHtmlDraw article.
Before long I sketched out requirements of my new tree control:
Checkboxes with theme support - I wanted notification messages sent to parent when checkbox was toggled, and full theme support; this means hover (hot) support, with checkboxes drawn exactly like themed XP checkboxes. Of course, on Vista, checkboxes should have Vista appearance:
|
|
Smart Checkboxes - this is a term I started using (but did not invent) to describe how checking parent item will also checkmark the children of that item - and, conversely, unchecking a child item will display parent with tri-state checkbox, if any other children are still checkmarked. Here is quick demo of how it works: Of course, Smart Checkboxes are optional - you can have checkboxes without using Smart Checkboxes. |
|
HTML - I wanted to include XHtmlDraw functionality, including support for web links and APP: links. | |
Enhanced tree navigation and status - reading Zafir Anjum's articles gave me ideas for improving the programmatic interface of the tree control, and I also wanted to implement better management features, like determining how many items are checked, how many children an item has, etc., and sending notification message to parent when item is expanded. | |
Loading and saving tree data using XML - this is a common requirement as XML is adopted in more applications. |
To begin with, let me show you the demo app:
This dialog allows you to choose source of data for tree. It can be text file or XML file (files are stored as resources, see my XResFile article for details). There are two choices for each file type - one is complete list, the second is partial list with ten items in each main node, to make debugging easy.
When you click on button, here is what you see:
Here are main features:
|
To refresh list, just click button again.
This dialog uses built-in CXHtmlTree::FindItem()
function to search for item text. An option on dialog lets you checkmark all matching items, which you can observe using Show Checked dialog.
Note that background color will be set as background of the entire tree control (via SetBkColor()
). However, individual items can have their backgrounds set independently of this, either by using SetItemTextBkColor()
, or by using HTML:
GetItemPath()
function and XBreadCrumbBar. When you click on active breadcrumb, that tree node will be selected. TVN_SELCHANGED
, WM_XHTMLTREE_CHECKBOX_CLICKED
, and WM_XHTMLTREE_ITEM_EXPANDED
) are received from control. You can enable or disable the Log with checkbox. To see full list of supported HTML tags, including how to use web links and APP: links, please refer to my XHtmlDraw article.
The following functions support item and tree colors and text attributes:
Function | Applies To | Description |
---|---|---|
COLORREF GetBkColor() | tree control | Get current background color for tree |
BOOL GetItemBold(HTREEITEM hItem) | item | Get item bold state (TRUE = item is bold) |
COLORREF GetItemTextBkColor(HTREEITEM hItem) | item | Get item background color for text |
COLORREF GetItemTextColor(HTREEITEM hItem) | item | Get item text color |
LOGFONT * GetLogfont() | tree control | Get pointer to current LOGFONT |
COLORREF GetTextColor() | tree control | Get current text color for tree |
BOOL GetUseLogfont() | tree control | Get current state of LOGFONT (TRUE = use specified LOGFONT |
COLORREF SetBkColor(COLORREF rgb) | tree control | Set tree background color |
BOOL SetItemBold(HTREEITEM hItem, BOOL bBold) | item | Set bold state for item |
COLORREF SetItemTextBkColor(HTREEITEM hItem, COLORREF rgb) | item | Set background color for item text |
COLORREF SetItemTextColor(HTREEITEM hItem, COLORREF rgb) | item | Set text color for item |
CXHtmlTree& SetLogfont(LOGFONT * pLogFont) | tree control | Set new LOGFONT for tree |
COLORREF SetTextColor(COLORREF rgb) | tree control | Set text color for tree |
CXHtmlTree& SetUseLogfont(BOOL bFlag) | tree control | Set flag to indicate whether LOGFONT struct should be used (TRUE = use LOGFONT) |
You can enable checkboxes for tree (using SetHasCheckBoxes()
) independently of enabling Smart Checkboxes (using SetSmartCheckBox()
). When checkboxes are enabled, an internal state image list is created for various checkbox states. This creation is performed in CreateCheckboxImageList()
(see CreateCheckboxImageList.cpp). For each possible state, a bitmap is created (using David Zhao's excellent CVisualStylesXP class), for both cold and hot states. Obviously this implies modal processing, but since I can use CTreeCtrl::SetItemState()
, keeping track of item state is not too complicated. Furthermore, I have designed image list so that common state transitions are simple. In following table, you can see that going from "cold" state to "hot" state involves OR'ing with 8, and going from "normal" state to "disabled" state involves OR'ing with 4.
XHtmlTree Checkbox States | ||||
---|---|---|---|---|
|
Cold | Hot | ||
Normal | Disabled | Normal | Disabled | |
Unchecked | 0001 | 0101 | 1001 | 1101 |
Checked | 0010 | 0110 | 1010 | 1110 |
Tri-state | 0011 | 0111 | 1011 | 1111 |
Here are functions implemented to support XHtmlTree checkboxes:
Function | Description |
---|---|
void CheckAll(BOOL bCheck) | Sets the checkbox for all items to the bCheck state |
BOOL GetCheck(HTREEITEM hItem) | Returns TRUE if item is checked. Returns FALSE if item is not checked, or is in tri-state. |
int GetCheckedCount() | Returns total count of checked items in tree |
int GetChildrenCheckedCount(HTREEITEM hItem) | Returns checked count for children |
HTREEITEM GetFirstCheckedItem() | Returns first checked item, or NULL |
BOOL GetHasCheckBoxes() | Returns TRUE if tree has checkboxes |
HTREEITEM GetNextCheckedItem(HTREEITEM hItem) | Returns next checked item, or NULL |
HTREEITEM GetPrevCheckedItem(HTREEITEM hItem) | Returns previous checked item, or NULL |
BOOL GetSelectFollowsCheck() | Returns TRUE if checking an item will also cause it to be selected |
BOOL GetSmartCheckBox() | Return TRUE if Smart Checkboxes are enabled |
BOOL IsChecked(HTREEITEM hItem) | Returns TRUE if item is checked. Returns FALSE if item is not checked, or is in tri-state. |
CXHtmlTree& SetCheck(HTREEITEM hItem, BOOL fCheck = TRUE) | Sets item checked state to fCheck |
CXHtmlTree& SetCheckChildren(HTREEITEM hItem, BOOL fCheck) | Sets checked state of children to fCheck |
CXHtmlTree& SetHasCheckBoxes(BOOL bHasCheckBoxes) | Enable checkboxes for tree if bHasCheckBoxes is TRUE |
CXHtmlTree& SetSelectFollowsCheck(BOOL bFlag) | If bFlag is TRUE, checking an item will also cause it to be selected |
CXHtmlTree& SetSmartCheckBox(BOOL bFlag) | Enable Smart Checkboxes if bFlag is TRUE |
XHtmlTree supports standard Windows keyboard navigation techniques:
Key | Description |
---|---|
Down and Up Arrow Keys | Moves selection to next/previous item; no expand |
Right Arrow Key | Expands the current selected item (if it is collapsed), selects the first child item |
Left Arrow Key | Collapses the current selected item (if it is expanded), selects the parent item |
Page Down | Moves selection to last item in tree window; pages items as necessary |
Page Up | Moves selection to first item in tree window; pages items as necessary |
Ctrl+Page Down | Scrolls to last item in tree window; pages items as necessary; selection is not changed |
Ctrl+Page Up | Scrolls to first item in tree window; pages items as necessary; selection is not changed |
Backspace | Moves selection to parent item |
End | Moves selection to last item; no expand |
Home | Moves selection to first item; no expand |
Ctrl+End | Scrolls to last; selection is not changed, no expand |
Ctrl+Home | Scrolls to first; selection is not changed, no expand |
Asterisk (*) on numeric keypad | Expands all items under the selected item |
Plus Sign (+) on numeric keypad | Expands the selected item |
Minus Sign (-) on numeric keypad | Collapses the selected item |
Here are functions implemented to support XHtmlTree navigation:
Function | Description |
---|---|
HTREEITEM GetLastItem(HTREEITEM hItem) | Retrieve the last item in the tree |
HTREEITEM GetNextCheckedItem(HTREEITEM hItem) | Retrieve the next item that is checked |
HTREEITEM GetNextItem(HTREEITEM hItem) | Retrieve the next item in the tree. This traverses the tree top-to-bottom, just like the tree appears visually when it is fully expanded. |
HTREEITEM GetNextItem(HTREEITEM hItem, UINT nCode) | Retrieve the item that has the specified relationship, indicated by the nCode parameter, to hItem |
HTREEITEM GetPrevCheckedItem(HTREEITEM hItem) | Retrieve the previous item that is checked |
HTREEITEM GetPrevItem(HTREEITEM hItem) | Retrieve the previous item in the tree. This traverses the tree top-to-bottom, just like the tree appears visually when it is fully expanded. |
It is easy to initialize XHtmlTree in one statement:
m_Tree.Initialize(m_bCheckBoxes, TRUE)
.SetSmartCheckBox(m_bSmartCheckBoxes)
.SetHtml(m_bHtml)
.SetStripHtml(m_bStripHtml)
.SetImages(m_bImages);
XHtmlTree supports standard tooltips via CToolTipCtrl, and also supports HTML tooltips, displayed with Eugene Pustovoyt's excellent CPPToolTip class. Here is example of how HTML tooltips can be used:
Here is HTML used for this tooltip:
The Golden Retriever is a popular breed of dog, originally developed to retrieve game during hunting. It is one of the most common family dogs as it is naturally very friendly and amenable to training. |
This is all standard HTML, except for the
OK, so where is HTML coming from, since it is obviously not the item text? XHtmlTree provides a new SetItemNote()
function, which attaches optional note text to an item. The note text (when it exists) is used instead of item text for tooltips. This is true for both standard tooltips and HTML tooltips. However, keep in mind that standard tooltips have hard limit of 80 characters.
#define XHTMLTOOLTIPS
If you include HTML tooltips, you must include additional files in your project - see How To Use section below for details.
One final point about tooltips: you have option of dynamically changing a tooltip when it is about to be displayed. A notification message (WM_XHTMLTREE_DISPLAY_TOOLTIP
- see Notification Messages below) gives you chance to use SetItemNote()
right before tooltip is displayed. To give example, in XHtmlTreeTestDlg.cpp, you will see this code (condensed here):
LRESULT CXHtmlTreeTestDlg::OnDisplayTreeToolTip(WPARAM wParam, LPARAM)
{
XHTMLTREEMSGDATA *pData = (XHTMLTREEMSGDATA *)wParam;
HTREEITEM hItem = pData->hItem;
CString strText = m_Tree.GetItemText(hItem);
// check if this is 'Galgo Español (Spanish Greyhound)'
if (_tcsncmp(strText, _T("Galgo"), 5) == 0)
{
// set new note for this item --
// zero tip width will use default width
m_Tree.SetItemNote(hItem,
_T("This is alternate text. ")
_T("For standard tooltip, it is limited to 80 characters."),
0);
}
return 0; // return 0 to allow tooltip to be displayed,
// 1 to prevent display
}
In the demo app, this is what you will see:
For all notification messages, the wParam
parameter is pointer to XHTMLTREEMSGDATA
struct:
struct XHTMLTREEMSGDATA
{
HWND hCtrl; // hwnd of XHtmlTree
UINT nCtrlId; // id of XHtmlTree
HTREEITEM hItem; // current item
};
The lParam
parameter depends on specific message. The following messages are sent to XHtmlTree parent:
Message | Description | lParam |
WM_XHTMLTREE_CHECKBOX_CLICKED | Sent when a checkbox is clicked | New checkbox state (TRUE = checked) |
WM_XHTMLTREE_DISPLAY_TOOLTIP | Sent when a tooltip is about to be displayed | Pointer to tooltip control (CToolTipCtrl or CPPToolTip ) |
WM_XHTMLTREE_INIT_TOOLTIP | Sent when tooltip control is being initialized | Pointer to tooltip control (CToolTipCtrl or CPPToolTip ) |
WM_XHTMLTREE_ITEM_EXPANDED | Sent when an item has been expanded or collapsed | New item state (TRUE = expanded) |
Starting with version 1.4, XHtmlTree supports drag & drop. The following drag & drop facilities and operations are implemented:
#define XHTMLDRAGDROP
Defining (or not defining) this symbol also takes care of setting TVS_DISABLEDRAGDROP
style correctly. CTreeCtrl::SetInsertMark()
API. This is what is used, for example, for FireFox bookmarks. As an item is being dragged, what you will see is a solid thin bar - called the insert mark - that is displayed between items in the tree. The insert mark indicates where item will be inserted into the tree if it is dropped. SetDropCursors()
function) three custom cursors: the no-drop cursor, the drop move cursor, and the drop copy cursor. The demo app includes examples of each:
Cursor | Demo Image | Description |
---|---|---|
No drop | Used to indicate a no-drop zone; either an area outside the tree control, or an item that is not an allowed drop target. | |
Drop Move | Used to indicate the dragged item will be moved when left mouse button is released. | |
Drop Copy | Used to indicate the dragged item will be copied when left mouse button is released. |
The user can use Ctrl key to toggle between copy and move, before or during drag. | |
The application can control whether Ctrl key is recognized by setting/resetting XHTMLTREE_DO_CTRL_KEY drag operations flag. |
|
The application can change default behavior of Ctrl key by setting/resetting XHTMLTREE_DO_COPY_DRAG drag operations flag. |
WM_XHTMLTREE_BEGIN_DRAG - This message is sent when a drag is initiated, and includes the dragged item and the state of the XHTMLTREE_DO_COPY_DRAG flag bit. Note that in demo app, attempting to drag Longdog in Sight Hounds group will be rejected (see demo Log). |
|
WM_XHTMLTREE_END_DRAG - This message is sent when a drag is terminated, either by a drop or by other user action (such as hitting the ESC key, right-clicking the mouse, dropping an item back onto itself, or dropping outside tree control). If the drag ends because of a drop on a valid tree item (other than itself), this message will include the proposed drop target. If the drop terminates for any other reason, a value of 0 will be sent as lParam parameter. Note that in demo app, dropping on Longdog in Sight Hounds group will be rejected (see demo Log). |
|
WM_XHTMLTREE_DROP_HOVER - This message is sent when the cursor is over a tree item, and includes the tree item that could be a drop target, Note that in demo app, hovering over Longdog in Sight Hounds group will display a "no-drop" cursor. |
For all above messages, a pointer to XHTMLTREEDRAGMSGDATA
struct is sent as lParam
parameter (this might be NULL in the case of WM_XHTMLTREE_END_DRAG
message).
struct XHTMLTREEDRAGMSGDATA
{
HTREEITEM hItem; // item being dragged
HTREEITEM hNewParent; // proposed new parent
HTREEITEM hAfter; // drop target - item being dragged will
// either sequentially follow this item,
// or hAfter specifies the relationship
// (TVI_FIRST, TVI_LAST, etc.) the
// dragged item will have with hNewParent.
// Note that TVI_xxxx constants are all
// defined as 0xFFFFnnnn, with the 16
// high-order bits set.
BOOL bCopyDrag; // TRUE = dropped item will be copied;
// FALSE = dropped item will be moved
};
The application may respond to these messages with either FALSE
(indicating that proposed action may continue), or TRUE
, indicating that proposed action is not permitted. When sent in response to WM_XHTMLTREE_BEGIN_DRAG
and WM_XHTMLTREE_END_DRAG
messages, a return code of TRUE
will terminate drag. When sent in response to WM_XHTMLTREE_DROP_HOVER
message, a return code of TRUE
will cause the no-drop cursor to be displayed.
Since the default return code - in the absence of a message handler - is 0 or FALSE
, it is not necessary to implement handlers for any of these messages in order to enable drag & drop.
XHTMLTREE_DO_SCROLL_NORMAL
and XHTMLTREE_DO_SCROLL_FAST
. If both of these flag bits are 0, no scrolling will occur. XHTMLTREE_DO_AUTO_EXPAND
. SetInsertMark()
API in conjunction with auto-expand, the user may drop an item (or branch) after any existing node. However, these two mechanisms do not allow for drop-under, where the drop results in creation of a child item. To accommodate this, the user may use Shift key before or during drag. Holding down Shift key causes drop to become drop-under: the insert mark is removed, and instead the drop target (the proposed new parent) is highlighted. This behavior may be disabled with drag operations flag bit XHTMLTREE_DO_SHIFT_KEY
. WM_XHTMLTREE_BEGIN_DRAG
message. See XHtmlTreeTestDlg.cpp for example.
Separators |
To try out separators yourself, you can use right-click menu in demo app:
The following separator facilities and operations are implemented:
InsertSeparator()
inserts a separator after the specified item. There is no limit on the number of separators. IsSeparator()
allows you to test whether a tree item is separator. SetItemTextColor()
, or you can use SetSeparatorColor()
to change the color of all separators. The default separator color is that returned by GetSysColor(COLOR_GRAYTEXT)
. TVN_BEGINLABELEDIT
. To integrate XHtmlTree into your own app, you first need to add following files to your project:
†
†
†
†
Files marked with †
must be set in Visual Studio to Not Using Precompiled headers.
If you want to use HTML tooltips, then you must also include these files:
And finally, if you want to include XML functions, then you must include these two files:
Then declare variable for CTreeCtrl
object, and change its type to CXHtmlTree
:
CXHtmlTree m_Tree;
Now you are ready to use CXHtmlTree
in your project.
There are three compile-time options that control what XHtmlTree features are included. These options may be selected by editing XHtmlTree.h, by including them as #define
statements (for example, in stdafx.h), or by defining them via IDE (in VS2005, go to Project | Properties | Configuration Properties | C/C++ | Preprocessor | Preprocessor Definitions; be sure to do this for All Configurations).
XHTMLHTML
- when defined, this option will enable use of HTML in tree items. XHTMLTOOLTIPS
- when defined, this option will enable use of HTML tooltips. XHTMLXML
- when defined, this option will enable loading/saving XML data. XHTMLDRAGDROP
- when defined, this option will enable drag & drop. The following table shows what source modules need to be included for each compile-time option (including the case where no options are selected, which is what is used in MinDialog demo):
XHtmlTree Compile-time Options | ||||
---|---|---|---|---|
Source Module | None | XHTMLHTML | XHTMLTOOLTIPS | XHTMLXML |
CeXDib.cpp | ||||
CeXDib.h | ||||
CreateCheckboxImageList.cpp | ||||
CreateCheckboxImageList.h | ||||
PPDrawManager.cpp | ||||
PPDrawManager.h | ||||
PPHtmlDrawer.cpp | ||||
PPHtmlDrawer.h | ||||
PPTooltip.cpp | ||||
PPTooltip.h | ||||
VisualStylesXP.cpp | ||||
VisualStylesXP.h | ||||
XHtmlDraw.cpp † |
||||
XHtmlDraw.h | ||||
XHtmlDrawLink.cpp † |
||||
XHtmlDrawLink.h | ||||
XHtmlTree.cpp | ||||
XHtmlTree.h | ||||
XmlDocument.cpp | ||||
XmlDocument.h | ||||
XNamedColors.cpp † |
||||
XNamedColors.h | ||||
XString.cpp † |
||||
XString.h |
†
must be set in Visual Studio to Not Using Precompiled headers.
The files needed for drag & drop (XHTMLDRAGDROP
) are the same ones listed under None.
Here are attribute names expected by LoadXmlFromXXXX
functions:
SetImageList()
to set the image list. XHTMLTOOLTIPS
has been defined (see XHtmlTree.h). You can load XML from a resource, a file, or a memory buffer, and you can save XML to a file.
Here are functions implemented to support XML:
Function | Description |
---|---|
BOOL ConvertBuffer(const BYTE * inbuf, DWORD inlen, BYTE ** outbuf, DWORD& outlen, ConvertAction eConvertAction = NoConvertAction) | Convert XML buffer to/from Unicode |
CString GetXmlText(HTREEITEM hItem, LPCTSTR lpszElem) | Retrieve the XML for item |
BOOL LoadXmlFromBuffer(const BYTE * pBuf, DWORD len, ConvertAction eConvertAction) | Loads XML from memory buffer |
BOOL LoadXmlFromFile(LPCTSTR lpszFile, ConvertAction eConvertAction) | Loads XML from file |
BOOL LoadXmlFromResource(HINSTANCE hInstance, LPCTSTR lpszResId, LPCTSTR lpszResType, ConvertAction eConvertAction) | Loads XML from resource |
BOOL SaveXml(HTREEITEM hItem, LPCTSTR lpszFileName, BOOL bSaveAsUTF16) | Saves XML to file |
Here are links I have mentioned in this article. I have also included links to my articles on CodeProject, which I have used in demo app.
CXmlDocument
class that I use in XHtmlTree. WM_CONTEXTMENU
to demo app. CXHtmlTree::EnableBranch(HTREEITEM hItem, BOOL bEnabled)
CVisualStylesXP::UseVisualStyles()
, reported by Graham Shanks. CheckAll()
to handle multiple root nodes, reported by Rolando E. Cruz-Marshall. SetCheck()
, reported by Graham Shanks. Get/SetReadOnly()
functions, suggested by Graham Shanks. The SetReadOnly()
function toggles all checkboxes between active (read/write) and inactive (read-only), and also allows/prevents in-place editing. When set to read-only, there is no automatic visual indication that the tree is read-only. You can use the SetBkColor()
function to set the background of read-only tree to indicate read-only state. Also added option to demo app to set read-only state. 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.
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)