Kyle Marsh
Microsoft Developer Network Technology Group
Created: December 22, 1992
Click to open or copy the files in the HierList sample application for this technical article.
This article explains how to create a hierarchical list box that includes icon bitmaps and connecting lines. It describes what an application must do to display the hierarchy and discusses some helper functions I developed specifically to make the process easier for the application developer. HierList, the sample application included with this article, implements a hierarchical list box with these helper functions.
Many applications need to display information to a user in a hierarchical, or outline, format. In the Microsoft® Windows™ operating system, the best-known example is the list of directories in the File Manager. The directory structure of a disk is a hierarchical data structure. Databases often contain hierarchical data as well; for example, the relationship between managers and the people they manage is a hierarchical relationship.
A common way to display a hierarchy is with a tree diagram, as in Figure 1.
Figure 1. A tree diagram
Unfortunately this display requires a lot of work to program, especially when a level changes. Each box, line, and text string must be placed and drawn. When a change occurs to the data (for example, a new vice president is added), the entire display must be recalculated and displayed. Tree diagrams also take up a lot of screen space and, when there is a lot of data to display, the individual boxes become so small that the writing inside becomes unreadable.
An alternate way to display the data is to turn the tree diagram on its side, as in Figure 2.
Figure 2. A turned tree diagram
This technique requires less space. It is a little easier to program than the tree diagram, but it still requires quite a bit of development effort. Text and lines must still be placed and drawn, but calculating the positions is simpler because you don't have to worry about centering the boxes and lines as in Figure 1.
Another option is to display the data in a list, as in Figure 3.
Figure 3. A hierarchical list
This version is not quite as easy to read as the tree diagram, or even the tree diagram turned on its side, but it has one big advantage: It can be programmed using an owner-drawn list box. If you capitalize on some easily used features of Windows-based programming, you can implement a hierarchical data structure that looks like the one in Figure 4.
Figure 4. A list box displaying a hierarchical list
With the addition of the icons, lines, and color, the list box display becomes almost as visually appealing as the tree diagram, but it requires a lot less programming effort. The display uses a standard list box control, so it can be easily added to a dialog box or other window—an additional advantage.
Let's go through the steps involved in displaying a hierarchy in a list box. To make things easier I wrote some helper functions that any application can use. These functions are described in "The Helper Functions" section later in this article.
Here are the steps needed to display a hierarchy using the helper functions:
An application developer must first design the appearance of the hierarchy. The developer determines what types of items are in the hierarchy and how the icons that represent these items should look. A hierarchical display usually needs at least two icons:
The display may use additional icons, and they may vary. The File Manager can display plain open and closed folders or, at the user's option, it can indicate which directories have subdirectories by displaying a closed folder with a "+" on it. The color of the icon can also vary. The Microsoft Developer Network CD's index window uses color to add visual variety to the index display. When choosing color combinations for a hierarchy, be sure to consider that many laptop and palmtop computers do not have color capability. Pick combinations that look good in monochrome and shades of gray.
Once a developer knows which types of items will be displayed and establishes the corresponding icons and their variations, he or she can create a bitmap that contains the icons. Using one bitmap that contains all the icons (an icon bitmap) improves performance of the owner-drawn functions. The icons should be arranged in rows and columns within the bitmap; therefore, all icons must be the same size.
There should be a column for each icon type and a row for each variation. This is not a hard and fast rule, however, because the application will designate the row and column numbers to determine which icon the drawing helper function will use to draw a particular item. Figure 5 shows the bitmap used in the sample application.
Figure 5. A sample icon bitmap
The helper functions require the following:
Once the bitmap is assembled, it must be linked into the application as a bitmap resource. For example, you could include the following line in the application's .RC file:
IDR_HIERICONS bitmap Hiericon.bmp
The application must create a list box with the LBS_OWNERDRAWFIXED style and without the LBS_HASSTRINGS and LBS_SORT styles, either by using a dialog box or by calling CreateWindow (or CreateWindowEx) directly. For example, you could use the following statement in a .DLG file:
LISTBOX IDLIST, 14, 10, 165, 195, LBS_OWNERDRAWFIXED |
LBS_WANTKEYBOARDINPUT | WS_VSCROLL
Or you could use this call to CreateWindow:
CreateWindow("LISTBOX", NULL,
LBS_OWNERDRAWFIXED | LBS_WANTKEYBOARDINPUT |
WS_VSCROLL,
14, 10, 165, 195,
hwndParent, NULL, _hInstance, NULL);
If you use the dialog box method, the application calls the HierDraw_DrawInit helper function to initialize the list box. A dialog procedure's WM_INITDIALOG handler cannot call this function because Windows sends a WM_MEASUREITEM message to a list box with the LBS_OWNERDRAWFIXED style before the dialog manager sends a WM_INITDIALOG message to the dialog procedure. The item height, which must be returned in response to the WM_MEASUREITEM message, is either the height of the text or the height of the bitmap, whichever is greater. Calling the initialization function before creating the dialog box provides a value (the bitmap height) that the list box can return with the WM_MEASUREITEM message. If the list box uses an alternate font, the dialog procedure can send a WM_MEASUREITEM message to the list box to adjust the item height when the dialog manager sends a WM_SETFONT message to the dialog procedure.
The application must tell HierDraw_DrawInit the ID of the bitmap resource and how many rows and columns of icons there are in the bitmap. The helper functions also need the following information from the application:
The following code shows how you can use HierDraw_DrawInit and other helper functions to initialize a list box within a dialog box:
HIERDRAWSTRUCT HierDrawStruct;
int HLDialog_Do(HWND hwndOwner)
{
int result;
DLGPROC lpfndp;
//
// Initialize the HierDraw stuff.
// Need to do this here so we have a value for WM_MEASUREITEM,
// which is sent before the WM_INITDIALOG message.
//
HierDraw_DrawInit(_hInstance, // Instance.
IDR_LISTICONS, // Bitmap resource ID.
4, // Rows in the bitmap.
3, // Columns in the bitmap.
TRUE, // Draw connecting lines
// between items?
&HierDrawStruct, // HierDrawStruct.
TRUE // Initialize the list box.
);
lpfndp = (DLGPROC)MakeProcInstance((FARPROC)HLDialog_DlgProc,
_hInstance);
result = DialogBox(_hInstance,
MAKEINTRESOURCE(IDHIERDLG),
hwndOwner, lpfndp);
FreeProcInstance((FARPROC)lpfndp);
HierDraw_DrawTerm(&HierDrawStruct);
return result;
}
The application must store or access the hierarchical data and identify an item in the hierarchy with a 32-bit data value. This value can be a pointer to where the item is stored in memory, a record identifier for a database, an offset in a file, or any other value that the application can use to uniquely identify an item. The 32-bit data value is added to the list box with an LB_INSERTSTRING message. Use the application-supplied 32-bit data value in the lParam parameter in the SendMessage call instead of a far pointer to a string.
The HierDraw_OnDrawItem helper function does the drawing work for the application. The application calls this function whenever Windows sends the application a WM_DRAWITEM message. The application must pass these arguments to HierDraw_OnDrawItem:
Connecting lines link siblings. If a given item is the last child of its parent, it does not connect to a sibling below itself. For example, item 3129 above is the last child of item 3120 and therefore does not connect to the item below (item 3130).
If an item's parent is a last child, this item does not have a connecting line for the parent level. This continues until the top of the hierarchy is reached.
To indicate that an item needs a connecting line at a particular level, the application sets a bit in the DWORD argument. For children of level 0 items (items at level 1), bit 0 is set. For children of level 1 (items at level 2), bit 1 is set. This continues to the current item's level. The DWORD argument for item 3129 is 0x00000003, while the DWORD argument for item 3128 is 0x00000007.
Here is the procedure that handles drawing an item for the HierList sample application, which displays the hierarchy of numbers shown in the above figure:
void HLDialog_OnDrawItem(HWND hwnd, DRAWITEMSTRUCT FAR* lpDrawItem)
{
char szText[128];
DWORD dwData;
int nLevel; // The level of the item.
int nTempLevel;
int nRow; // The row in the bitmap
// for the item's icon.
int nColumn; // The column in the bitmap
// for the item's icon.
DWORD dwConnectLevel = 0L;
DWORD dwMask;
DWORD dwLevelMask;
DWORD dwLevelAdd;
dwData = lpDrawItem->itemData;
wsprintf(szText,"Item Number: %ld",dwData);
//
// Select the correct icon, open folder, closed folder, or
// document.
//
// Can this item be opened?
//
if ( dwData % 10 == 0 ) {
//
// Is it open?
//
if ( HierDraw_IsOpened(&HierDrawStruct, dwData) ) {
nColumn = 1;
}
else {
nColumn = 0;
}
}
else {
nColumn = 2;
}
//
// Select the correct level/color.
// This sample changes the row of the bitmap (color)
// just because it can.
//
if ( dwData % 1000 == 0 ) {
nLevel = 0;
nRow = 0;
}
else if ( dwData % 100 == 0 ) {
nLevel = 1;
nRow = 1;
}
else if ( dwData % 10 == 0 ) {
nLevel = 2;
nRow = 2;
}
else {
nLevel = 3;
nRow = 3;
}
//
// Now build dwConnectLevel, which tells the helper function
// the levels for this item that need connecting lines.
// If this item is the last child or one of its parents
// is a last child, it does not need a connector at that level.
//
if ( nLevel == 0 ) {
//
// Level 0 items never have connectors.
dwConnectLevel = 0L;
}
else {
//
// Check parents (grand, great, ...) to see it they are last
// children.
//
// Start at parent (one level up from here).
//
nTempLevel = nLevel-1;
//
// First bit to set (or not).
//
dwMask = (DWORD) pow(2,nLevel-1);
dwLevelMask = (DWORD) pow(10,4-nLevel);
dwLevelAdd = dwLevelMask / 10;
//
// While we are not at level 0.
//
while ( nTempLevel >= 0 ) {
//
// Last child at this level?
//
if ( ((dwData+dwLevelAdd)%dwLevelMask) == 0 ) {
//
// Last kid, so no connection at this level.
//
dwConnectLevel &= ~dwMask;
}
else {
//
// NOT last kid, so connection at this level.
//
dwConnectLevel |= dwMask;
}
//
// Stuff for this sample, figure out next parent.
dwLevelAdd *= 10;
dwLevelMask *= 10;
dwData = dwData-(dwData%dwLevelAdd);
//
// Move mask bit over.
//
dwMask /= 2;
//
// Move up one level.
//
nTempLevel--;
}
}
//
// All set to call drawing function.
//
HierDraw_OnDrawItem(hwnd,
lpDrawItem,
nLevel,
dwConnectLevel,
szText,
nRow,
nColumn,
&HierDrawStruct);
return;
}
The helper functions change the background of the icon bitmaps to the current window background color (COLOR_WINDOW). If the user changes the color of the window background, the icon bitmap background colors must also change. The application calls the HierDraw_DrawInit function with the last argument set to FALSE to change the icon bitmap backgrounds to the new window background color. For example, the application's main window procedure could handle the WM_SYSCOLORCHANGE message as follows:
case WM_SYSCOLORCHANGE:
HierDraw_DrawInit(_hInstance, // Instance.
IDR_LISTICONS, // Bitmap ID.
ROWS, // Rows.
COLS, // Columns.
TRUE, // Loins.
&HierDrawStruct, // HierDrawStruct.
FALSE // DON'T initialize open
// folders.
);
return TRUE;
break;
If the list box or dialog box receives a WM_SETFONT message specifying the font for drawing text, the application must call the HierDraw_DrawSetTextHeight helper function:
case WM_SETFONT:
{
//
// Set the text height.
//
HierDraw_DrawSetTextHeight(hwndDlg,
IDLIST,
(HFONT) wParam,
&HierDrawStruct);
}
break;
The usual behavior for a hierarchical list box is:
Four helper functions aid an application in opening and closing items:
Here is the HierList sample application's open item and close item function:
void ActionItem(HWND hWndList, DWORD dwData, WORD wItemNum)
{
DWORD dwAddItem;
DWORD dwIncr;
WORD wCount;
WORD wItem;
//
// Is this an item or a folder?
//
if ( (dwData % 10) ) {
//
// Not a folder, just pop a box.
//
MessageBox(hWndList, "Do something with this",
"Simon Says", MB_OK);
}
else {
//
// It's a folder, set up some junk for this sample app.
//
if ( dwData % 1000 == 0 ) {
dwIncr = 100;
}
else if ( dwData % 100 == 0 ) {
dwIncr = 10;
}
else if ( dwData % 10 == 0 ) {
dwIncr = 1;
}
//
// Is it open?
//
if ( HierDraw_IsOpened(&HierDrawStruct, dwData) ) {
//
// It's open ... close it.
//
HierDraw_CloseItem(&HierDrawStruct, dwData);
//
// Remove the child items. Close any children that are
// open on the way.
//
// No need for recursion. We just close everything along
// the way and remove items until we reach the next sibling
// to the current item.
//
wItemNum++;
dwIncr = SendMessage(hWndList, LB_GETITEMDATA,
wItemNum, 0L);
while ( dwIncr < dwAddItem ) {
if ( (dwIncr % 10) == 0 &&
HierDraw_IsOpened(&HierDrawStruct, dwIncr)) {
HierDraw_CloseItem(&HierDrawStruct, dwIncr);
}
SendMessage(hWndList, LB_DELETESTRING, wItemNum, 0L);
dwIncr = SendMessage(hWndList, LB_GETITEMDATA,
wItemNum, 0L);
}
}
else {
//
// It's closed ... open it.
//
HierDraw_OpenItem(&HierDrawStruct, dwData);
//
// Disable redrawing.
//
SendMessage(hWndList, WM_SETREDRAW, FALSE, 0L);
for ( wItem = wItemNum, dwAddItem = dwData+dwIncr,
wCount = 0, wItem++;
wCount < 9; wItem++, wCount++, dwAddItem+=dwIncr ) {
SendMessage(hWndList, LB_INSERTSTRING,
wItem, dwAddItem);
}
//
// Make sure as many child items as possible are showing.
//
HierDraw_ShowKids(&HierDrawStruct, hWndList, wItemNum, 9 );
//
// Enable, and force, redrawing.
//
SendMessage(hWndList, WM_SETREDRAW, TRUE, 0L);
InvalidateRect(hWndList, NULL, TRUE);
}
}
}
This section describes the HierDraw structure and the helper functions that make it easier to display a hierarchy.
An application needs a structure that communicates information between calls to the helper functions. HIERDRAW.H, included in the HierList sample application, defines the HierDraw structure, which communicates this information. The application needs to declare a static or global HIERDRAWSTRUCT variable.
typedef struct _HierDrawStruct {
HDC hdcMem;
HBITMAP hbmIcons;
HBITMAP hbmMem;
int nBitmapHeight;
int nBitmapWidth;
int nTextHeight;
int nLineHeight;
BOOL bLines;
int NumOpened;
DWORD FAR *Opened;
} HIERDRAWSTRUCT;
typedef HIERDRAWSTRUCT FAR * LPHIERDRAWSTRUCT ;
hdcMem
Handle to the memory device context (DC) that contains the icon bitmaps.
hbmIcons
Handle to the device-independent icon bitmap.
hbmMem
Handle to the memory icon bitmap.
nBitmapHeight
Height of an icon in the icon bitmap.
nBitmapWidth
Width of an icon in the icon bitmap.
nTextHeight
Height of the text used in the list box.
NLineHeight
Height of the item lines in the list box. This is either the bitmap height (nBitmapHeight) or the text height (nTextHeight), whichever is greater.
bLines
Flag that designates whether the helper functions should draw connecting lines between items in the list box.
NumOpened
Number of open items in the list box.
Opened
Pointer to a list of open items.
This function is responsible for:
The application must call HierDraw_DrawInit either before the application creates the dialog box that contains the list box or before it creates a child list box.
BOOL HierDraw_DrawInit(HINSTANCE hInstance,
int nBitmap,
int nRows,
int nColumns,
BOOL bLines,
LPHIERDRAWSTRUCT lpHierDrawStruct,
BOOL bInit);
hInstance
Application's instance handle.
nBitmap
Integer identifier for the bitmap resource that contains the icon bitmap.
nRows
Number of rows in the icon bitmap.
nColumns
Number of columns in the icon bitmap.
bLines
Flag that specifies whether the list box should have connecting lines between items. If this parameter is TRUE, the list box will have connecting lines. If this parameter is FALSE, no lines will be drawn.
lpHierDrawStruct
Pointer to the HierDraw structure for this list box, which is filled in by HierDraw_DrawInit.
bInit
Flag that indicates whether to reload the icon bitmap or to initialize the HierDraw structure's data fields. If this parameter is TRUE, the data fields are initialized; otherwise, they are not. HierDraw_DrawInit should also be called with the bInit parameter set to FALSE in response to a WM_SYSCOLORCHANGE message.
This function returns TRUE if successful. Otherwise, it returns FALSE.
This function cleans up any "leftovers" from the helper functions. It should be called when the list box or dialog box is destroyed.
VOID HierDraw_DrawTerm(LPHIERDRAWSTRUCT lpHierDrawStruct);
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
This function sets the text height of the items in the list box. If necessary, HierDraw_DrawSetTextHeight will adjust the height of the items in the list box by sending a WM_MEASUREITEM message to the list box.
VOID HierDraw_DrawSetTextHeight (HWND hwnd,
int nTextHeight,
LPHIERDRAWSTRUCT lpHierDrawStruct);
hwnd
Window handle to the list box.
nTextHeight
Height of the text used in the list box.
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
This function does the actual drawing of an item in the list box. The application calls HierDraw_OnDrawItem when Windows sends it a WM_DRAWITEM message.
VOID HierDraw_OnDrawItem(HWND hwnd,
const DRAWITEMSTRUCT FAR* lpDrawItem,
int nLevel,
DWORD dwConnectLevel,
char *szText,
int nRow,
int nColumn,
LPHIERDRAWSTRUCT lpHierDrawStruct);
hwnd
Window handle to the list box.
lpDrawItem
Pointer to a DRAWITEMSTRUCT that the application received from Windows.
nLevel
Level of the current item.
dwConnectLevel
Contains bits that indicate the levels for which the current item needs connecting lines. Bit 0 designates whether the current item requires a connecting line for the children of level 0; bit 1 designates whether the current item requires a connecting line for the children of level 1; and so on.
szText
Text for the current item.
nRow
Row number for the current item's icon in the icon bitmap.
nColumn
Column number for the current item's icon in the icon bitmap.
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
This function processes a WM_MEASUREITEM message for the list box. The application calls HierDraw_OnMeasureItem when Windows sends it a WM_MEASUREITEM message.
VOID HierDraw_OnMeasureItem(HWND hwnd,
MEASUREITEMSTRUCT FAR* lpMeasureItem,
LPHIERDRAWSTRUCT lpHierDrawStruct);
hwnd
Window handle to the list box.
lpMeasureItem
Pointer to a MEASUREITEMSTRUCT that Windows sent to the application.
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
The application calls this function to determine if the specified item is in the list of open items.
BOOL HierDraw_IsOpened(LPHIERDRAWSTRUCT lpHierDrawStruct,
DWORD dwData);
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
dwData
32-bit data value of the item.
This function returns TRUE if the item is in the open item list. Otherwise, it returns FALSE.
The application calls this function to add a value to the list of open item values.
VOID HierDraw_OpenItem(LPHIERDRAWSTRUCT lpHierDrawStruct,
DWORD dwData);
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
dwData
32-bit data value to be added.
The application calls this function to remove a value from the list of open item values.
VOID HierDraw_CloseItem(LPHIERDRAWSTRUCT lpHierDrawStruct,
DWORD dwData);
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
dwData
32-bit data value to be removed.
The application calls this function when it opens an item to ensure that as many children as possible are visible.
VOID HierDraw_ShowKids(LPHIERDRAWSTRUCT lpHierDrawStruct,
HWND hwndList,
WORD wCurrentSelection,
WORD wKids);
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
hwndList
Window handle to the list box.
wCurrentSelection
Index number of the current item.
wKids
Number of children of the current item.
The application calls this function to remove all the values from the list of open items.
VOID HierDraw_DrawCloseAll(LPHIERDRAWSTRUCT lpHierDrawStruct );
lpHierDrawStruct
Pointer to the HierDraw structure for this list box.
The HierDraw_DrawInit, HierDraw_OnDrawItem, and FastRect functions contain the interesting part of displaying a hierarchy (most of the other code is boring). Let's take a look at these functions.
The HierDraw_DrawInit function creates a memory DC that is compatible with the screen to hold the list's bitmap. It loads the bitmap using the FindResource, LoadResource, and LockResource functions, and receives a pointer to the BITMAPINFOHEADER structure for the bitmap. HierDraw_DrawInit then adjusts the background color of the bitmap by reading the color code for the bitmap's lower-left pixel and adjusting the bitmap's color table entry for that pixel to the current window background color. The lower-left pixel is located right after the bitmap's color table, which comes right after the bitmap's BITMAPINFOHEADER structure. HierDraw_DrawInit assumes that the bitmap is a 16-color bitmap, so the lower-left pixel is easily determined.
Here is the source for HierDraw_DrawInit:
BOOL HierDraw_DrawInit(HINSTANCE hInstance,
int nBitmap,
int nRows,
int nColumns,
BOOL bLines,
LPHIERDRAWSTRUCT lpHierDrawStruct,
BOOL bInit)
{
HANDLE hRes;
HANDLE hResMem;
LPBITMAPINFOHEADER lpbi;
DWORD FAR * lpColorTable;
LPSTR lpBits;
int bc;
HDC hDC;
if ( bInit ) {
lpHierDrawStruct->NumOpened = 0;
lpHierDrawStruct->Opened = NULL;
lpHierDrawStruct->bLines = bLines;
}
//
// If the memory DC is not created yet, do that first.
//
if (!lpHierDrawStruct->hdcMem) {
//
// Get a screen DC.
//
hDC = GetDC(NULL);
//
// Create a memory DC compatible with the screen.
//
lpHierDrawStruct->hdcMem = CreateCompatibleDC(hDC);
//
// Release the screen DC.
ReleaseDC(NULL,hDC);
if (!lpHierDrawStruct->hdcMem)
return FALSE;
lpHierDrawStruct->hbmMem = NULL;
}
//
// (Re)Load the bitmap (original from disk).
//
// Use the FindResource, LoadResource, and LockResource
// functions since it makes it easy to get the pointer to the
// BITMAPINFOHEADER we need.
//
//
hRes = FindResource(hInstance, MAKEINTRESOURCE(nBitmap),
RT_BITMAP);
if (!hRes)
return FALSE;
hResMem = LoadResource(hInstance, hRes);
if (!hResMem)
return FALSE;
//
// Now figure out the bitmap's background color.
// This code assumes these are 16-color bitmaps
// and that the lower-left corner is a bit in the background
// color.
//
//
//
lpbi = (LPBITMAPINFOHEADER)LockResource(hResMem);
if (!lpbi)
return FALSE;
lpColorTable = (DWORD FAR *)(lpbi + 1);
lpBits = (LPSTR)(lpColorTable + 16); // Assumes 16 color.
bc = (lpBits[0] & 0xF0) >> 4; // Assumes 16 color.
// Also assumes lower-
// left corner is
// background color!!!
lpColorTable[bc] = RGB2BGR(GetSysColor(COLOR_WINDOW));
hDC = GetDC(NULL);
lpHierDrawStruct->hbmIcons =
CreateDIBitmap(hDC,lpbi,(DWORD)CBM_INIT,lpBits,
(LPBITMAPINFO)lpbi,DIB_RGB_COLORS);
ReleaseDC(NULL,hDC);
lpHierDrawStruct->nBitmapHeight = (WORD)lpbi->biHeight / nRows;
lpHierDrawStruct->nBitmapWidth = (WORD)lpbi->biWidth / nColumns;
lpHierDrawStruct->nLineHeight =
max(lpHierDrawStruct->nBitmapHeight,
lpHierDrawStruct->nTextHeight);
UnlockResource(hResMem);
FreeResource(hResMem);
if (!lpHierDrawStruct->hbmIcons)
return FALSE;
lpHierDrawStruct->hbmMem =
SelectObject(lpHierDrawStruct->hdcMem,
lpHierDrawStruct->hbmIcons);
if (!lpHierDrawStruct->hbmMem)
return FALSE;
return TRUE;
}
The HierDraw_OnDrawItem function is a straightforward drawing routine for an owner-drawn list box. It calculates the offsets within the item's rectangle, which Windows supplies in DRAWITEMSTRUCT, for the item's indent, bitmap, and text, based on the level of the item.
If the hierarchy wants lines drawn to connect items, HierDraw_OnDrawItem checks the corresponding bit in the dwConnectLevel field for the item's level and each of its parent levels. If the corresponding bit is set in dwConnectLevel, HierDraw_OnDrawItem draws a vertical connecting line. At the item's level and for each parent level, HierDraw_OnDrawItem draws a horizontal line connecting the vertical line to the item's text.
HierDraw_OnDrawItem draws an icon for each item by calling BitBlt to copy the bitmap from the memory device context to the list box's device context.
If the item is selected, HierDraw_OnDrawItem draws the text with the current highlight colors; otherwise, it uses the current window and text colors to draw the text.
If the item has the focus and is not selected, HierDraw_OnDrawItem draws a focus rectangle by calling DrawFocusRect.
Here is the source for HierDraw_OnDrawItem:
VOID HierDraw_OnDrawItem(HWND hwnd,
const DRAWITEMSTRUCT FAR* lpDrawItem,
int nLevel,
DWORD dwConnectLevel,
char *szText,
int nRow,
int nColumn,
LPHIERDRAWSTRUCT lpHierDrawStruct)
{
HDC hDC;
WORD wIndent, wTopBitmap, wTopText;
RECT rcTemp;
if ( lpDrawItem->itemID == (UINT)-1 )
return ;
hDC = lpDrawItem->hDC;
CopyRect(&rcTemp, &lpDrawItem->rcItem);
wIndent = rcTemp.left + ((int)(nLevel)
* lpHierDrawStruct->nBitmapWidth) + XBMPOFFSET;
rcTemp.left = wIndent + lpHierDrawStruct->nBitmapWidth;
wTopText = rcTemp.top + ((rcTemp.bottom - rcTemp.top) / 2)
- (lpHierDrawStruct->nTextHeight / 2);
wTopBitmap = rcTemp.top + ((rcTemp.bottom - rcTemp.top) / 2)
- (lpHierDrawStruct->nBitmapHeight / 2);
if (lpDrawItem->itemAction == ODA_FOCUS)
goto DealWithFocus;
else if (lpDrawItem->itemAction == ODA_SELECT)
goto DealWithSelection;
//
// Draw some lions, if we like lions.
//
if (lpHierDrawStruct->bLines && nLevel)
{
DWORD dwMask = 1;
int nTempLevel;
int x,y;
// Draw lines in text color.
SetBkColor(hDC,GetSysColor(COLOR_WINDOWTEXT));
//
// Draw a series of vertical lines (|) for outer levels.
//
x = lpHierDrawStruct->nBitmapWidth/2 + XBMPOFFSET;
for ( nTempLevel = 0; nTempLevel < nLevel ; nTempLevel++)
{
if ( dwConnectLevel & dwMask )
FastRect(hDC,x,rcTemp.top,1,
rcTemp.bottom - rcTemp.top);
x += lpHierDrawStruct->nBitmapWidth;
dwMask *= 2;
}
//
// Draw the short vertical line up toward the parent.
//
nTempLevel = nLevel-1;
dwMask *= 2;
x = nTempLevel * lpHierDrawStruct->nBitmapWidth
+ lpHierDrawStruct->nBitmapWidth / 2 + XBMPOFFSET;
if ( dwConnectLevel & dwMask )
y = rcTemp.bottom;
else
y = rcTemp.bottom - lpHierDrawStruct->nLineHeight / 2;
FastRect(hDC,x,rcTemp.top,1,y-rcTemp.top);
//
// Draw a short horizontal bar to the right.
//
FastRect(hDC,x,rcTemp.bottom-lpHierDrawStruct->nLineHeight/2
,lpHierDrawStruct->nBitmapWidth/2,1);
}
//
// Draw the selected bitmap.
//
BitBlt(hDC,
wIndent,wTopBitmap,
lpHierDrawStruct->nBitmapWidth,
lpHierDrawStruct->nBitmapHeight,
lpHierDrawStruct->hdcMem,
nColumn*lpHierDrawStruct->nBitmapWidth,
nRow*lpHierDrawStruct->nBitmapHeight,
SRCCOPY);
DealWithSelection:
if (lpDrawItem->itemState & ODS_SELECTED)
{
SetBkColor(hDC,GetSysColor(COLOR_HIGHLIGHT));
SetTextColor(hDC,GetSysColor(COLOR_HIGHLIGHTTEXT));
}
else
{
SetBkColor(hDC,GetSysColor(COLOR_WINDOW));
SetTextColor(hDC,GetSysColor(COLOR_WINDOWTEXT));
}
ExtTextOut(hDC, rcTemp.left + 1, wTopText,
ETO_CLIPPED|ETO_OPAQUE,&rcTemp,
szText,lstrlen(szText), NULL);
if (lpDrawItem->itemState & ODS_FOCUS &&
lpDrawItem->itemAction != ODA_SELECT) {
DealWithFocus:
DrawFocusRect(hDC, &rcTemp);
}
}
So what's the fastest way to draw a rectangle? Use ExtTextOut without a text string to improve drawing performance.
static VOID near FastRect(HDC hDC, int x, int y, int cx, int cy)
{
RECT rc;
rc.left = x;
rc.right = x+cx;
rc.top = y;
rc.bottom = y+cy;
ExtTextOut(hDC,x,y,ETO_OPAQUE,&rc,NULL,0,NULL);
}
FastRect, an internal function in the helper functions, is just a little tidbit to improve drawing performance. And using the helper functions is an easy way to add hierarchical list boxes to your application.