The TreeView control (which is mapped in Figure 11-16 on the following page) could more accurately be called OutlineView because it certainly isn’t designed to handle what programmers commonly call a tree. The control works fine if you know how many levels of data you’ll have, but it’s difficult to use for recursive data such as directory, registry, or windows trees. I’ve been able to use several different strategies to get around inherent limitations of the control.
The first problem you’ll encounter is initializing your outline data. The design of the TreeView control lends itself to completely filling out the data during
Figure 11-16. A TreeView map.
initialization, but of course that’s not generally what you want to do with a recursive tree. It would take a very long time to fill an Explorer-style program with all the files on a large network disk. Furthermore, the levels could get out of sync if other programs deleted or created files after the control was initialized. You’d also have serious memory problems. A better strategy is to fill each level as it is expanded. The problem is that if you don’t fill in the next branch of a TreeView, that branch won’t have a plus button, and the user won’t have any way to expand it.
This isn’t really a problem with the underlying Windows TreeView in COMCTL32.DLL. It’s a problem introduced by an incomplete implementation in the ActiveX control. The problem occurs at two levels. First, the control fails to support one of the most common TreeView operations—one that TreeViews in common programs, such as Windows Explorer and the registry editor, do all the time. Second, the control doesn’t provide the handles you need to work around the problem with the API. The TreeView control has a handle property that returns the handle of the underlying TreeView window, but the nodes of a TreeView are also controlled through handles. The TreeView control has these node handles (it couldn’t work without them), but it won’t share them with you. ListView and several other common controls containing collections have the same limitation.
Meriwether has to hack to get expandable nodes. First you determine whether the data you’re adding to the TreeView node has children. If it does, you fill the node with a single fake child so that the TreeView will display the node as expandable. When the user expands the node, you delete the fake child and fill in the real children (filling any of the child nodes that have children with fakes). The whole process has to be done in reverse when a node collapses. Fake children have to have a name that is easily recognizable, but illegal for real children. I used “:” as the fake name.
The WinWatch program, which stores the window tree in a TreeView, had a different initialization problem. It initializes the entire tree at once, but it does so in a recursive function that doesn’t know its complete context. You can review the background for this problem in “Doing windows” in Chapter 6. The function IterateChildWindows walks through the windows tree, calling the DoWindow method of the IWindowsHelper interface to handle each window. Chapter 6 showed the simple implementation of IWindowsHelper (CWindowToFile in WFILEHLP.CLS) that writes window information to a log file. The more complicated implementation (CWindowToForm in WFORMHLP.CLS) writes window data to the nodes of a TreeView. The DoWindow method has level and window handle parameters, but when the method is called, you don’t know whether the current window should be added as a sibling of the last node or as a child of some other higher level node. The class maintains a stack of the last node and key used at each level. Check out the implementation of CWindowToForm to get the details of how it efficiently finds the correct tree position for each new node.
If you want data stored in a TreeView to be keyed for easy lookup, you’re in for more trouble. Every key must be unique, but what are your chances of finding a disk with no duplicate directory names? In fact, what are the chances of finding any tree-structured data without duplicates? One of the main reasons to use a tree is so that each node can be independent. A TreeView control would be much easier to manage if it were organized as a collection of collections instead of as one big collection (like CRegNode in Chapter 10). That’s not to say you can’t use keys with TreeViews, but you might need to come up with a complicated scheme to avoid duplicate keys without using too much memory.
Creating unique keys might not be a problem if every item in the tree happens to have a numeric ID. WinWatch, for example, uses the handle of each window as the key. Handles are guaranteed to be unique, but unfortunately, TreeView won’t accept keys with a digit as the first character. Probably this has something to do with how it distinguishes string keys from numeric indexes when doing node lookups. In any case, you have to prefix your numeric keys with a non-digit prefix (such as H).
Images in the ImageList for a TreeView (or a ListView) should be keyed, but you must be careful how you insert the keys. For example, in Meriwether, each file type must have one and only one image with the file type name as the key. For every file, I use error trapping to identify whether the image for the current file type is already in the ImageList. If the key doesn’t exist, I insert it. This can’t be doing much for performance, but I couldn’t figure out another way.