To see how Windows Forms can be used to create a more realistic
Windows application, in this section you’ll build a utility
named FileCopier
that copies all files from a
group of directories selected by the user to a single target
directory or device, such as a floppy or backup hard drive on the
company network. Although you won’t implement every possible
feature, you can imagine programming this application so that you can
mark dozens of files and have them copied to multiple disks; packing
them as tightly as possible. You might even extend the application to
compress the files. The true goal of this example is for you to
exercise many of the C# skills learned in earlier chapters and to
explore the Windows.Forms
namespace.
For the purposes of this example and to keep the code simple, you’ll focus on the user interface and the steps needed to wire up its various controls. The final application UI is shown in Figure 13-7.
The user interface for FileCopier consists of the following controls:
The goal is to allow the user to check files (or entire directories) in the left tree view (source). If the user presses the Copy button, the files checked on the left side will be copied to the Target Directory specified in the right-hand control. If the user presses Delete, the checked files will be deleted.
The rest of this chapter implements a number of
FileCopier
features in order to demonstrate the
fundamental features of Windows Forms.
The
first task is to open a new project
named FileCopier
. The IDE puts you into the
Designer
, where you can drag widgets onto the
form. You can expand the form to the size you want. Drag label,
button, checkbox, and tree view controls from the
Toolbox onto your form until it looks
more or less like the one shown in Figure 13-8.
You want checkboxes next to the directories and files in the source
selection window but not in the target (where only one directory will
be chosen). Set the CheckBoxes
property on the
left TreeView
control,
tvwSource
, to true
and set the
property on the right-hand TreeView
control,
tvwTargetDir
, to false
. To do
so, click each control in turn and adjust the values in the
Properties window.
Once this is done, double-click the Cancel button to create its event handler: when you double-click a control, Visual Studio.NET creates an event handler for that object. One particular event is the target event, and Visual Studio.NET opens that event’s event handler:
protected void btnCancel_Click (object sender, System.EventArgs e) { Application.Exit( ); }
You can set many different events for the TreeView
control. You can set them programmatically by clicking the Events
button in the Properties window. From there you can create new
handlers, just by filling in a new event handler method name. Visual
Studio.NET will register the event handler and open the editor for
the code, where it will create the header and put the cursor in an
empty method body.
So much for the easy part. Visual Studio.NET will generate code to
set up the form and initialize all the controls, but it won’t
fill the TreeView
controls. That you must do by
hand.
The two TreeView
controls work identically, except
that the left control, tvwSource
, lists the
directories and files, whereas the right control,
tvwTargetDir
, lists only directories. The
CheckBoxes
property on
tvwSource
is set to true
, and
on tvwTargetDir
it is set to
false
. Also, although tvwSource
will allow multiselect, which is the default for
TreeView
controls, you will enforce single
selection for tvwTargetDir
.
You’ll factor the common code for both
TreeView
controls into a shared method
FillDirectoryTree
and pass in the control with a
flag indicating whether to get the files. You’ll call this
method from the constructor, once for each of the two controls:
FillDirectoryTree(tvwSource, true); FillDirectoryTree(tvwTargetDir, false);
The FillDirectoryTree
implementation names the
TreeView
parameter tvw
. This
will represent the source TreeView
and the
destination TreeView
in turn.
private void FillDirectoryTree(TreeView tvw, bool isSource)
The TreeView
control has
a property, Nodes
, which gets a
TreeNodeCollection
object. The
TreeNodeCollection
is a collection of
TreeNode
objects, each of which represents a node
in the tree. Start by emptying that collection:
tvw.Nodes.Clear( );
You are ready to fill the TreeView
’s
Nodes
collection by recursing through the
directories of all the drives. First, you get all the logical drives
on the system. To do so you call a static method of the
Environment
object, GetLogicalDrives( ).
The Environment
class provides
information about and access to the current platform environment. You
can use the Environment
object to get the machine
name, OS version, system directory, and so forth, from the computer
on which you are running your program.
string[] strDrives = Environment.GetLogicalDrives( );
GetLogicalDrives( )
returns an array of strings,
each of which represents the root directory of one of the logical
drives. You will iterate over that collection, adding nodes to the
TreeView
control as you go.
foreach (string rootDirectoryName in strDrives) {
You should process each drive within the foreach
loop. The very first thing you need to determine is whether the drive
is ready. My hack for that is to get the list of top-level
directories from the drive by calling GetDirectories( )
on a DirectoryInfo
object I created
for the root directory:
DirectoryInfo dir = new DirectoryInfo(rootDirectoryName); dir.GetDirectories( );
The DirectoryInfo
class exposes instance methods
for creating, moving, and enumerating through directories, their
files, and their subdirectories. The DirectoryInfo
class is covered in detail in Chapter 21.
The GetDirectories( )
method returns a list of
directories, but you’ll throw this list away. You are calling
it here only to generate an exception if the drive is not ready.
You’ll wrap the call in a try block and take no action in the catch block. The effect is that if an exception is thrown, the drive is skipped.
Once you know that the drive is ready, you create a
TreeNode
to hold the root directory of the drive
and add that node to the TreeView
control:
TreeNode ndRoot = new TreeNode(rootDirectoryName); tvw.Nodes.Add(ndRoot);
You now want to recurse through the directories, so you call into a
new routine, GetSubDirectoryNodes( )
, passing in
the root node, the name of the root directory, and the flag
indicating whether you want files:
if (isSource) { GetSubDirectoryNodes(ndRoot, ndRoot.Text, true); } else { GetSubDirectoryNodes(ndRoot, ndRoot.Text, false); }
You’re probably wondering why you need to pass in
ndRoot.Text
if you’re already passing in
ndRoot
. Patience; you’ll see why this is
needed when you recurse back into
GetSubDirectoryNodes
.
GetSubDirectoryNodes( )
begins by once again
calling GetDirectories( )
, this time stashing away
the resulting array of DirectoryInfo
objects:
private void GetSubDirectoryNodes( TreeNode parentNode, string fullName, bool getFileNames) { DirectoryInfo dir = new DirectoryInfo(fullName); DirectoryInfo[] dirSubs = dir.GetDirectories( );
Notice that the node passed in is named
parentNode
. The current level of nodes will be
considered children to the node passed in. This is how you map the
directory structure to the hierarchy of the tree view.
Iterate over each subdirectory, skipping any that are marked
Hidden
:
foreach (Directory dirSub in dirSubs) { if ( (dirSub.Attributes & FileSystemAttributes.Hidden) != 0 ) { continue; }
FileSystemAttributes
is an
enum
; other possible values include
Archive
, Compressed
,
Directory
, Encrypted
,
Hidden
, Normal
,
ReadOnly
, etc.
The property dirSub.Attributes
is the bit pattern
of the current attributes of the directory. If you logically AND that
value with the bit pattern
FileSystemAttributes.Hidden
, a bit is set if the
file has the hidden
attribute; otherwise all the
bits are cleared. You can test for any hidden bit by testing whether
the resulting int
is other than zero.
You create a TreeNode
with the directory name and
add it to the Nodes
collection of the node passed
in to the method (parentNode
):
TreeNode subNode = new TreeNode(dirSub.Name); parentNode.Nodes.Add(subNode);
You now recurse back into the GetSubDirectoryNodes( )
method, passing in the node you just created as the new
parent, the full path as the full name of the parent, and the flag:
GetSubDirectoryNodes(subNode,dirSub.FullName,getFileNames);
Notice that the call to the TreeNode
constructor
uses the Name
property of the
DirectoryInfo
object, while the call to
GetSubDirectoryNodes( )
uses the
FullName
property. If your directory is
c:WinNTMediaSounds
, the
FullName
property will return the full path, while
the Name
property will return just
Sounds
. You pass in only the name to the node
because that is what you want displayed in the tree view. You pass in
the full name with path to the GetSubDirectoryNodes( )
method so that the method can locate all the
subdirectories on the disk. This answers the question asked earlier
as to why you need to pass in the root node’s name the first
time you call this method; what is passed in is not the name of the
node, it is the full path to the directory represented by the node!
Once you’ve recursed through the subdirectories, it is time to
get the files for the directory, if the
getFileNames
flag is true
. To
do so, you call the GetFiles( )
method on the
DirectoryInfo
object. What is returned is an array
of FileInfo
objects:
if (getFileNames) { // Get any files for this node. FileInfo[] files = dir.GetFiles( );
The FileInfo
class (covered in Chapter 21) provides instance methods for manipulating
files.
You can now iterate over this collection, accessing the
Name
property of the FileInfo
object and passing that name to the constructor of a
TreeNode
, which you then add to the parent
node’s Nodes
collection (thus creating a
child node). There is no recursion this time because files do not
have subdirectories:
foreach (FileInfo file in files) { TreeNode fileNode = new TreeNode(file.Name); parentNode.Nodes.Add(fileNode); }
That’s all it takes to fill the two tree views.
You must
handle a number of events in this example. First, the user might
click Cancel, Copy, Clear, or Delete. Second, the user might click
one of the checkboxes in the left TreeView
or one
of the nodes in the right TreeView
.
Let’s consider the clicks on the TreeViews
first, as they are the more interesting, and potentially the more
challenging.
There are two TreeView
objects, each with its own
event handler. Consider the source TreeView
object
first. The user checks the files and directories he wants to copy
from. Each time the user clicks a file or directory, a number of
events are raised. The event you must handle is
AfterCheck
.
To do so, you implement a custom event-handler method you will create
and name tvwSource_AfterCheck( )
. Visual
Studio.NET will wire this to the event handler, or if you are not
using the integrated development environment, you must do so
yourself.
tvwSource.AfterCheck += new System.Windows.Forms.TreeViewEventHandler (this.tvwSource_AfterCheck);
The implementation of AfterCheck( )
delegates the
work to a recursable method named SetCheck( )
,
you’ll also write.
protected void tvwSource_AfterCheck ( object sender, System.Windows.Forms.TreeViewEventArgs e) { SetCheck(e.node,e.node.Checked); }
The event handler passes in the sender
object and
an object of type TreeViewEventArgs
. It turns out
that you can get the node from this
TreeViewEventArgs
object (e
).
You call SetCheck( )
, passing in the node and the
state of whether the node has been checked.
Each node
has a Nodes
property,
which gets a TreeNodeCollection
containing all the
subnodes. SetCheck( )
recurses through the current
node’s Nodes
collection, setting each
subnode’s check mark to match that of the node that was
checked. In other words, when you check a directory, all its files
and subdirectories are checked, recursively, all the way down.
For each TreeNode
in the Nodes
collection, you check to see if it is a leaf. A node is a leaf if its
own Nodes
collection has a count of zero. If so,
you set its check property to whatever was passed in as a parameter.
If it is not a leaf, you recurse.
private void SetCheck(TreeNode node, bool check) { node.Checked = check; foreach (TreeNode n in node.Nodes) { if (node.Nodes.Count == 0) { node.Checked = check; } else { SetCheck(n,check); } } }
This propagates the check mark (or clears the check mark) down through the entire structure. In this way, the user can indicate that he wants to select all the files in all the subdirectories by clicking a single directory.
The event handler for the target TreeView
is
somewhat trickier. The event itself is
AfterSelect
. (Remember that the target
TreeView
does not have checkboxes.) This time, you
want to take the one directory chosen and put its full path into the
text box at the upper-left corner of the form.
To do so, you must work your way up through the nodes, finding the name of each parent directory and building the full path:
protected void tvwTargetDir_AfterSelect ( object sender, System.Windows.Forms.TreeViewEventArgs e) { string theFullPath = GetParentString(e.node);
We’ll look at GetParentString( )
in just a
moment. Once you have the full path, you must lop off the backslash
(if any) on the end and then you can fill the text box:
if (theFullPath.EndsWith("\"))
{
theFullPath =
theFullPath.Substring(0,theFullPath.Length-1);
}
txtTargetDir.Text = theFullPath;
The GetParentString( ) method takes a node and returns a string with the full path. To do so, it recurses upward through the path, adding the backslash after any node that is not a leaf:
private string GetParentString(TreeNode node) { if(node.Parent == null) { return node.Text; } else { return GetParentString(node.Parent) + node.Text + (node.Nodes.Count == 0 ? "" : "\"); } }
The conditional operator (?
) is the only ternary
operator in C# (a ternary operator takes three terms). The logic is:
“test whether node.Nodes.Count
is zero; if
so return the value before the colon (in this case an empty string);
otherwise return the value after the colon (in this case a
backslash).
The recursion stops when there is no parent; that is, when you hit the root directory.
Given the SetCheck( )
method developed earlier, handling the
Clear button’s click event is trivial:
protected void btnClear_Click (object sender, System.EventArgs e) { foreach (TreeNode node in tvwSource.Nodes) { SetCheck(node, false); } }
You just call the SetCheck( )
method on the root
nodes and tell them to recursively uncheck all their
contained nodes.
Now
that you can check the
files and pick the target directory, you’re ready to handle the
Copy button-click event. The very first thing you need to do is to
get a list of which files were selected. What you want is an array of
FileInfo objects, but you have no idea how many objects will be in
the list. That is a perfect job for
ArrayList
. You’ll delegate responsibility for
filling the list to a method called GetFileList( )
:
protected void btnCopy_Click ( object sender, System.EventArgs e) { ArrayList fileList = GetFileList( );
Let’s pick that method apart before returning to the event handler.
You start by instantiating a new ArrayList
object
to hold the strings representing the names of all the files selected:
private ArrayList GetFileList( ) { ArrayList fileNames = new ArrayList( );
To get the selected filenames, you can walk through the source
TreeView
control:
foreach (TreeNode theNode in tvwSource.Nodes) { GetCheckedFiles(theNode, fileNames); }
To see how this works, you want to step into the
GetCheckedFiles( )
method. This method is pretty
simple: it examines the node it was handed. If that node has no
children (node.Nodes.Count == 0
), it is a leaf. If
that leaf is checked, you want to get the full path (by calling
GetParentString( )
on the node) and add it to the
ArrayList
passed in as a parameter:
private void GetCheckedFiles(TreeNode node, ArrayList fileNames) { if (node.Nodes.Count == 0) { if (node.Checked) { string fullPath = GetParentString(node); fileNames.Add(fullPath); } }
If the node is not a leaf, you want to recurse down the tree, finding the child nodes:
else { foreach (TreeNode n in node.Nodes) { GetCheckedFiles(n,fileNames); } } }
This will return the ArrayList
filled with all the
filenames. Back in GetFileList( )
, you’ll
use this ArrayList
of filenames to create a second
ArrayList
, this time to hold the actual
FileInfo
objects:
ArrayList fileList = new ArrayList( );
Notice that once again you do not tell the
ArrayList
constructor what kind of object it will
hold. This is one of the advantages of a rooted type-system; the
collection only needs to know that it has some kind of
Object
; because all types are derived from
Object
, the list can hold
FileInfo
objects as easily as it can hold
string
objects.
You can now iterate through the filenames in
ArrayList
, picking out each name and instantiating
a FileInfo
object with it. You can detect if it is
a file or a directory by calling the Exists
property, which will return false
if the
File
object you created is actually a directory.
If it is a File
, you can add it to the new
ArrayList
:
foreach (string fileName in fileNames) { FileInfo file = new File(fileName); if (file.Exists) { fileList.Add(file); } }
You want to work your way through the list of selected files in large
to small order so that you can pack the target disk as tightly as
possible. You must therefore sort the ArrayList
.
You can call its Sort( )
method, but how will it
know how to sort File
objects? Remember, the
ArrayList
has no special knowledge about its
contents.
To solve this, you must pass in an IComparer
interface. We’ll create a class called
FileComparer
that will implement this interface
and that will know how to sort FileInfo
objects:
public class FileComparer : IComparer {
This class has only one method, Compare( )
, which
takes two objects as arguments:
public int Compare (object f1, object f2) {
The normal approach is to return 1
if the first
object (f1
) is larger than the second
(f2
), to return -1
if the
opposite is true, and to return 0
if they are
equal. In this case, however, you want the list sorted from big to
small, so you should reverse the return values.
Since this is the only use of this compare
method,
it is reasonable to put this special knowledge that the sort is from
big to small right into the compare
method itself.
The alternative is to sort small to big, and have the
calling
method reverse the results, as you saw in
Example 12-1.
To test the length of the FileInfo
object, you
must cast the Object
parameters to
FileInfo
objects (which is safe, as you know this
method will never receive anything else):
FileInfo file1 = (FileInfo) f1; FileInfo file2 = (FileInfo) f2; if (file1.Length > file2.Length) { return -1; } if (file1.Length < file2.Length) { return 1; } return 0; } }
In a production program, you might want to test the type of the object and perhaps handle the exception if the object is not of the expected type.
Returning to GetFileList( )
, you were about to
instantiate the IComparer
reference and pass it to
the Sort( )
method of fileList
:
IComparer comparer = (IComparer) new FileComparer( ); fileList.Sort(comparer);
That done, you can return fileList
to the calling
method:
return fileList;
The calling method was btnCopy_Click
. Remember you
went off to GetFileList( )
in the first line of
the event handler!
protected void btnCopy_Click (object sender, System.EventArgs e) { ArrayList fileList = GetFileList( );
At this point you’ve returned with a sorted list of
File
objects, each representing a file selected in
the source TreeView
.
You can now iterate through the list, copying the files and updating the UI:
foreach (FileInfo file in fileList) { try { lblStatus.Text = "Copying " + txtTargetDir.Text + "\" + file.Name + "..."; Application.DoEvents( ); file.CopyTo(txtTargetDir.Text + "\" + file.Name,chkOverwrite.Checked); } catch (Exception ex) { MessageBox.Show(ex.Message); } } lblStatus.Text = "Done."; Application.DoEvents( );
As you go, you write the progress to the lblStatus
label and call Application.DoEvents( )
to give the
UI an opportunity to redraw. You then call CopyTo( )
on the file, passing in the target directory, obtained
from the text field, and a Boolean flag indicating whether the file
should be overwritten if it already exists.
You’ll notice that the flag you pass in is the value of the
chkOverWrite
checkbox. The
Checked
property evaluates true
if the checkbox is checked and false
if not.
The copy is wrapped in a try
block because you can
anticipate any number of things going wrong when copying files. For
now, you handle all exceptions by popping up a dialog box with the
error, but you might want to take corrective action in a commercial
application.
The code to handle the delete event is even simpler. The very first thing you do is ask the user if she is sure she wants to delete the files:
protected void btnDelete_Click (object sender, System.EventArgs e) { System.Windows.Forms.DialogResult result = MessageBox.Show( "Are you quite sure?", // msg "Delete Files", // caption MessageBoxButtons.OKCancel, // buttons MessageBoxIcon.Exclamation, // icons MessageBoxDefaultButton.Button2); // default button }
You can use the MessageBox
static Show( )
method, passing in the message you want to display, the
title "Delete Files"
as a string, and flags.
MessageBox.OKCancel
asks for two buttons:
OK
and Cancel.
MessageBox.IconExclamation
indicates that you want
to display an exclamation mark icon.
MessageBox.DefaultButton.Button2
sets the second
button (Cancel)
as the default choice.
When the user chooses OK
or
Cancel
, the result is passed back as a
System.Windows.Forms.DialogResult
enumerated
value. You can test this value to see if the user pressed OK:
if (result == System.Windows.Forms.DialogResult.OK) {
If so, you can get the list of fileNames
and
iterate through it, deleting each as you go:
ArrayList fileNames = GetFileList( ); foreach (FileInfo file in fileNames) { try { lblStatus.Text = "Deleting " + txtTargetDir.Text + "\" + file.Name + "..."; Application.DoEvents( ); file.Delete( ); } catch (Exception ex) { MessageBox.Show(ex.Message); } } lblStatus.Text = "Done."; Application.DoEvents( );
This code is identical to the copy code, except that the method that
is called on the file is Delete( )
.
Example 13-3 provides the commented source code for this example.
To save space, this example shows only the custom methods and leaves
out the declarations of the Windows.Forms
objects
as well as the boilerplate code produced by Visual Studio.NET. As
explained in the preface, you can download the complete source code
from my web site, http://www.LibertyAssociates.com.
Example 13-3. File copier source code
using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Windows.Forms; /// <remarks> /// File Copier - WinForms demonstration program /// (c) Copyright 2001 Liberty Associates, Inc. /// </remarks> namespace FileCopier { /// <summary> /// Form demonstrating Windows Forms implementation /// </summary> // < declarations of Windows widgets cut here > public class Form1 : System.Windows.Forms.Form { /// <summary> /// internal class which knows how to compare /// two files we want to sort large to small, /// so reverse the normal return values. /// </summary> public class FileComparer : IComparer { public int Compare (object f1, object f2) { FileInfo file1 = (FileInfo) f1; FileInfo file2 = (FileInfo) f2; if (file1.Length > file2.Length) { return -1; } if (file1.Length < file2.Length) { return 1; } return 0; } } public Form1( ) { // // Required for Windows Form Designer support // InitializeComponent( ); // fill the source and target directory trees FillDirectoryTree(tvwSource, true); FillDirectoryTree(tvwTargetDir, false); } /// <summary> /// Fill the directory tree for either the Source or /// Target TreeView. /// </summary> private void FillDirectoryTree( TreeView tvw, bool isSource) { // Populate tvwSource, the Source TreeView, // with the contents of // the local hard drive. // First clear all the nodes. tvw.Nodes.Clear( ); // Get the logical drives and put them into the // root nodes. Fill an array with all the // logical drives on the machine. string[] strDrives = Environment.GetLogicalDrives( ); // Iterate through the drives, adding them to the tree. // Use a try/catch block, so if a drive is not ready, // e.g. an empty floppy or CD, // it will not be added to the tree. foreach (string rootDirectoryName in strDrives) { if (rootDirectoryName != @"C:") continue; try { // Fill an array with all the first level // subdirectories. If the drive is // not ready, this will throw an exception. DirectoryInfo dir = new DirectoryInfo(rootDirectoryName); dir.GetDirectories( ); TreeNode ndRoot = new TreeNode(rootDirectoryName); // Add a node for each root directory. tvw.Nodes.Add(ndRoot); // Add subdirectory nodes. // If TreeView is the source, // then also get the filenames. if (isSource) { GetSubDirectoryNodes( ndRoot, ndRoot.Text, true); } else { GetSubDirectoryNodes( ndRoot, ndRoot.Text, false); } } catch (Exception e) { // Catch any errors such as // Drive not ready. MessageBox.Show(e.Message); } } } // close for FillSourceDirectoryTree /// <summary> /// Gets all the subdirectories below the /// passed in directory node. /// Adds to the directory tree. /// The parameters passed in at the parent node /// for this subdirectory, /// the full pathname of this subdirectory, /// and a Boolean to indicate /// whether or not to get the files in the subdirectory. /// </summary> private void GetSubDirectoryNodes( TreeNode parentNode, string fullName, bool getFileNames) { DirectoryInfo dir = new DirectoryInfo(fullName); DirectoryInfo[] dirSubs = dir.GetDirectories( ); // Add a child node for each subdirectory. foreach (DirectoryInfo dirSub in dirSubs) { // do not show hidden folders if ( (dirSub.Attributes & FileAttributes.Hidden) != 0 ) { continue; } /// <summary> /// Each directory contains the full path. /// We need to split it on the backslashes, /// and only use /// the last node in the tree. /// Need to double the backslash since it /// is normally /// an escape character /// </summary> TreeNode subNode = new TreeNode(dirSub.Name); parentNode.Nodes.Add(subNode); // Call GetSubDirectoryNodes recursively. GetSubDirectoryNodes( subNode,dirSub.FullName,getFileNames); } if (getFileNames) { // Get any files for this node. FileInfo[] files = dir.GetFiles( ); // After placing the nodes, // now place the files in that subdirectory. foreach (FileInfo file in files) { TreeNode fileNode = new TreeNode(file.Name); parentNode.Nodes.Add(fileNode); } } } // < boilerplate code cut here > /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main( ) { Application.Run(new Form1( )); } /// <summary> /// Create an ordered list of all /// the selected files, copy to the /// target directory /// </summary> private void btnCopy_Click(object sender, System.EventArgs e) { // get the list ArrayList fileList = GetFileList( ); // copy the files foreach (FileInfo file in fileList) { try { // update the label to show progress lblStatus.Text = "Copying " + txtTargetDir.Text + "\" + file.Name + "..."; Application.DoEvents( ); // copy the file to its destination location file.CopyTo(txtTargetDir.Text + "\" + file.Name,chkOverwrite.Checked); } catch // (Exception ex) { // you may want to do more than just show the message // MessageBox.Show(ex.Message); } } lblStatus.Text = "Done."; Application.DoEvents( ); } /// <summary> /// on cancel, exit /// </summary> private void btnCancel_Click(object sender, System.EventArgs e) { Application.Exit( ); } /// <summary> /// Tell the root of each tree to uncheck /// all the nodes below /// </summary> private void btnClear_Click(object sender, System.EventArgs e) { // get the top most node for each drive // and tell it to clear recursively foreach (TreeNode node in tvwSource.Nodes) { SetCheck(node, false); } } /// <summary> /// check that the user does want to delete /// Make a list and delete each in turn /// </summary> private void btnDelete_Click(object sender, System.EventArgs e) { // ask them if they are sure System.Windows.Forms.DialogResult result = MessageBox.Show( "Are you quite sure?", // msg "Delete Files", // caption MessageBoxButtons.OKCancel, // buttons MessageBoxIcon.Exclamation, // icons MessageBoxDefaultButton.Button2); // default button // if they are sure... if (result == System.Windows.Forms.DialogResult.OK) { // iterate through the list and delete them. // get the list of selected files ArrayList fileNames = GetFileList( ); foreach (FileInfo file in fileNames) { try { // update the label to show progress lblStatus.Text = "Deleting " + txtTargetDir.Text + "\" + file.Name + "..."; Application.DoEvents( ); // Danger Will Robinson! file.Delete( ); } catch (Exception ex) { // you may want to do more than // just show the message MessageBox.Show(ex.Message); } } lblStatus.Text = "Done."; Application.DoEvents( ); } } /// <summary> /// Get the full path of the chosen directory /// copy it to txtTargetDir /// </summary> private void tvwTargetDir_AfterSelect( object sender, System.Windows.Forms.TreeViewEventArgs e) { // get the full path for the selected directory string theFullPath = GetParentString(e.Node); // if it is not a leaf, it will end with a backslash // remove the backslash if (theFullPath.EndsWith("\")) { theFullPath = theFullPath.Substring(0,theFullPath.Length-1); } // insert the path in the text box txtTargetDir.Text = theFullPath; } /// <summary> /// Mark each node below the current /// one with the current value of checked /// </summary> private void tvwSource_AfterCheck(object sender, System.Windows.Forms.TreeViewEventArgs e) { // Call a recursible method. // e.node is the node which was checked by the user. // The state of the check mark is already // changed by the time you get here. // Therefore, we want to pass along // the state of e.node.Checked. SetCheck(e.Node,e.Node.Checked); } /// <summary> /// recursively set or clear check marks /// </summary> private void SetCheck(TreeNode node, bool check) { // set this node's check mark node.Checked = check; // find all the child nodes from this node foreach (TreeNode n in node.Nodes) { // if the child is a leaf // just check it (or uncheck it) if (node.Nodes.Count == 0) { node.Checked = check; } // if the child is a node in the tree, recurse else { SetCheck(n,check); } } } /// <summary> /// Given a node and an array list /// fill the list with the names of /// all the checked files /// </summary> // Fill the ArrayList with the full paths of // all the feils checked private void GetCheckedFiles(TreeNode node, ArrayList fileNames) { // if this is a leaf... if (node.Nodes.Count == 0) { // if the node was checked... if (node.Checked) { // get the full path and add it to the arrayList string fullPath = GetParentString(node); fileNames.Add(fullPath); } } else // if this node is not a leaf { // call this for all the subnodes foreach (TreeNode n in node.Nodes) { GetCheckedFiles(n,fileNames); } } } /// <summary> /// Given a node, return the /// full pathname /// </summary> private string GetParentString(TreeNode node) { // if this is the root node (c:) return the text if(node.Parent == null) { return node.Text; } else { // recurse up and get the path then // add this node and a slash // if this node is the leaf, don't add the slash return GetParentString(node.Parent) + node.Text + (node.Nodes.Count == 0 ? "" : "\"); } } /// <summary> /// shared by delete and copy /// creates an ordered list of all /// the selected files /// </summary> private ArrayList GetFileList( ) { // create an unsorted array list of the full filenames ArrayList fileNames = new ArrayList( ); // fill the fileNames ArrayList with the // full path of each file to copy foreach (TreeNode theNode in tvwSource.Nodes) { GetCheckedFiles(theNode, fileNames); } // Create a list to hold the fileInfo objects ArrayList fileList = new ArrayList( ); // for each of the filenames we have in our unsorted list // if the name corresponds to a file (and not a directory) // add it to the file list foreach (string fileName in fileNames) { // create a file with the name FileInfo file = new FileInfo(fileName); // see if it exists on the disk // this fails if it was a directory if (file.Exists) { // both the key and the value are the file // would it be easier to have an empty value? fileList.Add(file); } } // Create an instance of the IComparer interface IComparer comparer = (IComparer) new FileComparer( ); // pass the comparer to the sort method so that the list // is sorted by the compare method of comparer. fileList.Sort(comparer); return fileList; } } } /// <summary> /// Mark each node below the current /// one with the current value of checked /// </summary> protected void tvwSource_AfterCheck ( object sender, System.Windows.Forms.TreeViewEventArgs e) { }
3.144.37.38