Windows Forms in C#: Tracking Your Progress

The first step is to indicate to the user that something is happening. One option is to add a ProgressBar control to the program. The problem with doing this is that you don’t know how many files you’ll need to process, so it’s difficult to know what the endpoint of the process should mean.

This means that either the progress bar will complete many times or it will never complete, which may be misleading to the user.

Another option is to indicate some directory or file information in the status bar. To start, drag a StatusBar control from the Toolbox to the form, and set the initial text of the status bar to an empty string so there isn’t any text displayed to start.

The next thing to do is to figure out how to update the StatusBar control when the directories are being scanned. You can do this in two ways. The first is to change the DirectoryNode.Populate() member so it takes a StatusBar as a parameter and have it change the text in the StatusBar directly.

That works fine, but it means the DirectoryNode class needs to know how the program wants to handle updating information. It’s not a very general implementation.

The second option is to have the DirectoryNode class support an event that’s fired whenever a file or directory is scanned. Since that’s a bit nicer, that’s what we’ll show how to do for this example. A downside of this approach is that you’ll need two event fields in each DirectoryNode object, even though only the root will ever have events hooked to it.

The first step is to define a class that will carry the event information:

public class ScannedItemEventArgs: EventArgs {

string name;

public ScannedItemEventArgs(string name)

{

this.name = name;

}

public string Name {

get

{

return name;

}

}

}

By convention, EventArgs is used as a base class for any class passing information for an event.

The next step is to add a delegate to DirectoryNode:

public delegate void ScannedEventHandler(object sender, ScannedItemEventArgs e);

Then, you’ll declare two events: one for a directory being scanned and one for a file being scanned:

public event ScannedEventHandler DirectoryScanned;

public event ScannedEventHandler FileScanned;

Finally, you’ll add functions to fire the events. Having separate functions for these isn’t strictly necessary, but they make the class a bit easier to use and easier to derive from.

The event framework is now ready. The next step is to modify the code in Populate() so it fires the events. An interesting subtlety exists here. The class that uses the events will hook up to the events declared in the root directory node, but since the Populate() function is recursive, it can’t fire the events using its own events. Instead, it needs to use the root’s events.

You do this by writing a private version of Populate() that takes a DirectoryNode as a parameter and by passing the root object as a parameter during recursive calls. You then modify the public version to call the private version.

To hook up the events, you need to declare the functions to be called when the event is fired, as part of the main form class:

void myFileScanned(object sender, ScannedItemEventArgs e)

{

statusBar1.Text = “File: ” + e.Name;

}

void myDirectoryScanned(object sender, ScannedItemEventArgs e)

{

statusBar1.Text = “Directory: ” + e.Name;

}

These functions merely update the status bar with the name of the file. You’ll also need to hook those to the events in the DirectoryNode object. Here’s the relevant code:

try

{

DirectoryNode newNode = new DirectoryNode(rootDirectory);

directoryNode = newNode;

directoryNode.DirectoryScanned +=

new DirectoryNode.ScannedEventHandler(myDirectoryScanned);

directoryNode.FileScanned +=

new DirectoryNode.ScannedEventHandler(myFileScanned);

directoryNode.Populate(null);

treeView1.Nodes.Clear();

PopulateTree(treeView1.Nodes, directoryNode);

}

catch (DirectoryNotFoundException e)

{

// don’t do anything; happens on some system directories.

}

The try block in this code handles an invalid entry for the root directory; in that case, the catch block will execute, and the tree will still be valid.

After the new DirectoryNode instance is created, the event handlers are added to it, the directory is populated, and then the tree view is refreshed with the new data.

When this code runs, it will update the status bar as it walks through the specified directory tree. It’s a bit distracting to see an update on every file (not to mention a bit wasteful to fire an event for every file), so you’ll need to figure out a way to update less often. Our initial idea was to merely update the status bar for directories, but this meant there was no change for a

few seconds with really big directories and too many updates for small directories. What you need is an update based on time rather than on the item that’s being scanned.

Add two items to the DirectoryNode class:

TimeSpan scanUpdateMinimum = new TimeSpan(0, 0, 0, 0, 200);

DateTime lastUpdateTime;

The scanUpdateMinimum field defines the time period for which there should be no update. The constructor call sets this period to 200 milliseconds. The lastUpdateTime field stores the time of the last update.

Then add two lines of code to the event-firing functions OnDirectoryScanned and OnFileScanned:

if ((DateTime.Now – this.lastUpdateTime) < scanUpdateMinimum)

return;

This code calculates how long it has been since the last update was sent and doesn’t send the update if there was a recent update.

It’s now obvious that the application isn’t hanging, but there’s no way to interrupt the application in the middle of a scan. You’ll tackle that in Chapter 38.

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 *