DiskDiff: Decorating the TreeView

The application displays the sizes well, but it’s fairly difficult to tell where the disk space is used based upon the numbers. What you need is a graphical representation of the amount of space used by each directory and file.

The TreeView control can put bitmaps in front of each item. To do this, an ImageList that contains all the bitmaps is attached to the TreeView, and then the index of the desired bitmap is set for each item in the tree.

To get started, you need to add an ImageList to the form and then populate it with images. First, drag an ImageList object from the Toolbox to the form. You can then add images through the Collections property in the property window. (We drew the images for this project using Paint Shop Pro.)

To hook up the ImageList to the TreeView, set the ImageList property in the TreeView control to the name of the ImageList in the form.

The next task is to figure out which image to present for each item in the list. The bitmaps are pie charts representing the percentage of the total space an item used. You can therefore determine the index by multiplying the fraction of space used by the number of images (8) to get the index. That requires you to modify the population function as follows:

public void PopulateTree(TreeNodeCollection treeNodeCollection,

DirectoryNode directoryNode, float fractionUsed)

{

TreeNode treeNode = new TreeNode(directoryNode.NameSize);

treeNode.ImageIndex = FractionToIndex(fractionUsed);

treeNodeCollection.Add(treeNode);

// As we walk though the tree, we need to figure out the

// percentages for each item. We do that based upon the

// full size of this directory. float dirSize = directoryNode.SizeTree;

foreach (DirectoryNode subdir in directoryNode.GetDirectories())

{

PopulateTree(treeNode.Nodes, subdir, subdir.SizeTree / dirSize);

}

foreach (FileNode fileNode in directoryNode.GetFiles())

{

TreeNode treeFileNode = new TreeNode(fileNode.NameSize);

treeFileNode.ImageIndex =

FractionToIndex(fileNode.Size / dirSize);

treeNode.Nodes.Add(treeFileNode);

}

}

The percentage is passed into the PopulateTree() function because each directory creates its own node. The top node is passed in the value 1.0, since it obviously contains all the size in the tree. The fraction of each element is computed based on the size of that element and the size of the directory it’s in, and the index for that fraction is obtained from the FractionToIndex() function.

Figure 36-2 shows how a tree looks with all the nodes expanded.

The program is starting to get useful, but it still has a few problems. One is that populating the TreeView object with all the nodes of the directory tree can take a long time—and use lots of memory—if the directory tree is large.

1. Expand-o-Matic

Instead of populating the whole tree, you’ll populate only the currently visible portion of the tree initially. When a user clicks the plus sign to expand a directory, you’ll populate the newly visible section.

Hooking up to the BeforeExpand event is fairly easy. When this event occurs, a TreeViewCancelEventArgs is passed, and that class contains the node that’s expanding. The event code will merely need to figure out what DirectoryNode object corresponds to the node passed with the event.

When dealing with a tree view control in the MFC framework or directly in Win32, you can store a value with each tree node and use this value to point to the object that corresponds to the tree node.

However, WinForms doesn’t provide access to this, so you’ll need to use another approach. One approach is to change DirectoryNode and FileNode so they’re derived from TreeNode and then store those directly. This works fine but will complicate those classes, since they now depend on the WinForms classes.

Another approach is to define a class derived from TreeNode that has a reference to the DirectoryNode or FileNode object for that node. That class is simple and looks like this:

public class MyTreeNode: TreeNode {

object node; // DirectoryNode or TreeNode

public MyTreeNode(string text, object node): base(text)

{

this.node = node;

}

public object Node {

get

{

return(node);

}

}

}

The MyTreeNode stores the additional information in an object variable. For a little nicer type checking, you could add a base class for both DirectoryNode and TreeNode so that MyTreeNode could store a class rather than just object.

After this node is defined, the form’s PopulateTree() function merely needs to have a few lines changed to create MyTreeNode instances rather than TreeNode instances.

At first glance, you may think there’s an extra object for each node now, but in actuality, you’ve merely replaced the TreeNode instances with MyTreeNode instances, and the added overhead is merely the extra object field.

2. Populate on Demand

The next task is to change the population code so it populates only a single level. To do this, you need to remove the recursion from the Populate() function. This involves a bit of a subtlety; your goal is to get rid of the extra nodes below all the directories, but if you get rid of the extra nodes, there won’t be any plus signs on the directories to click.

You can get around this by putting a single blank node under each directory that has files in it and setting the node of that entry to null. This enables the plus signs on the directory. The populate code now looks like this:

public void PopulateTreeNode(TreeNodeCollection treeNodeCollection,

DirectoryNode directoryNode, float fractionUsed)

{

TreeNode treeNode = new MyTreeNode(directoryNode.NameSize, directoryNode);

treeNode.ImageIndex = FractionToIndex(fractionUsed);

treeNode.SelectedImageIndex = treeNode.ImageIndex;

treeNodeCollection.Add(treeNode);

if (directoryNode.SizeTree != 0)

{

// Add a fake entry to this node so that there will be

// a + sign in front of it.

treeNode.Nodes.Add(new MyTreeNode(“”, null));

}

}

Next, you’ll need to write the function that populates the nodes below a directory when it’s clicked. It looks like this:

public void ExpandTreeNode(MyTreeNode treeNode)

{

// look at the first child of this tree. If the node

// associated with it isn’t null, then we’ve already

// done the expansion before.

MyTreeNode childTreeNode = (MyTreeNode) treeNode.Nodes[0];

if (childTreeNode.Node != null) return;

treeNode.Nodes.Clear();                          // get rid of null entry

DirectoryNode directoryNode = (DirectoryNode) treeNode.Node;

// As we walk through the tree, we need to figure out the

// percentages for each item. We do that based upon the

// full size of this directory.

float dirSize = directoryNode.SizeTree;

foreach (DirectoryNode subdir in directoryNode.GetDirectories())

{

PopulateTreeNode(treeNode.Nodes, subdir, subdir.SizeTree / dirSize);

}

foreach (FileNode fileNode in directoryNode.GetFiles())

{

TreeNode treeFileNode = new MyTreeNode(fileNode.NameSize, fileNode);

treeFileNode.ImageIndex = FractionToIndex(fileNode.Size / dirSize);

treeFileNode.SelectedImageIndex = treeFileNode.ImageIndex;

treeNode.Nodes.Add(treeFileNode);

}

}

The code at the beginning of the function checks to see if the first entry in the node list has a null node. If it isn’t null, then this node has been expanded before, and the tree is already up- to-date. If it is null, you delete the blank node and then add all the nodes for this directory.

Finally, you need to hook this up to the expand event. You add a handler for the BeforeExpand event in the Form Designer and then write the code. It’s really only one line of code:

protected void treeView1_BeforeExpand (

object sender, System.WinForms.TreeViewCancelEventArgs e)

{

ExpandTreeNode((MyTreeNode) e.node);

}

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *