When .NET first came to life, there were two ways to create applications: ASP.NET for web applications, and Windows Forms for Windows applications. Although WPF offers many advantages over Windows Forms, Microsoft realizes that there are a great many Windows Forms applications already up, tested, and working, and that many companies will choose to maintain and extend them.
Our interest in this book is how we can use C# to interact with Windows Forms, and in this chapter we’ll look at building a nontrivial application using this technology. Figure 19-1 shows the application we’re going to build. It is a Windows application for copying files from one or more directories to a target directory, written in Windows Forms and designed to be run on a Windows computer (this application has been tested on Windows XP, 2000, and Vista).
Once again, because this is a C# book and not a Windows programming book, we’re going to make fast work of the UI and focus on the code-behind file and the event handlers—that is, on the C#. Unlike with WPF, however, there is no declarative aspect to Windows Forms; you create the UI by dragging objects onto a form, and then interacting with those objects by clicking on them and setting their properties, either in the Properties window at design time, or programmatically at runtime.
Open Visual Studio 2008, and choose Create → Project. In the New Project window, create a new Visual C# application, and from the Templates window, choose Windows Forms Application. Name it Windows Form File Copier, as shown in Figure 19-2.
Visual Studio responds by creating a Windows Forms application and, best of all, putting you into a design environment and opening a toolbox with controls sorted by the type of work you might want to do. The user interface for FileCopier
consists of the following controls:
Labels (source files and target files)
Buttons (Clear, Copy, Delete, and Cancel)
An “Overwrite if exists” checkbox
A text box displaying the path of the selected target directory
Two large tree-view controls, one for available source directories, and one for available target devices and directories
To create the UI, click on the form and click in the Properties window. Expand the Size property and set the Width to 585 and the Height to 561. Change the Text property to File Copier, change the name to frmFileCopier, and change the AutoSizeMode to GrowOnly. You can leave the remaining properties alone.
Drag two tree-view controls onto the form, placing them as shown in Figure 19-3. Note that the left tree-view control is taller than the right, allowing room for a text box above the right tree-view control. Continue to drag controls onto the form and then name them, as shown in Table 19-1.
Table 19-1 shows the names we assigned to the controls on this form.
Control type | Control name |
Tree view (left) |
|
Tree view (right) |
|
Text box |
|
Label |
|
Label |
|
Label |
|
Button |
|
Button |
|
Button |
|
Button |
|
Checkbox |
|
There are four common ways to create event handlers in Visual Studio. One is to click on a control, and then to name the event handler in the Properties window. You can switch the Properties window into Events mode by clicking on the lightning bolt button. Find the event you want to hook up, click in the box next to the event, and type a name, as shown in Figure 19-4.
When you press the Enter key, Visual Studio 2008 will create an event handler stub and place you in the source code to fill in the details.
The second option is to do the same thing, but rather than typing in a name, just double-click in the space next to the property name in the Properties window. This instructs Visual Studio to create a name for you, which it does by concatenating the name of the control and the name of the event. Thus, were you to have double-clicked in the space next to Click in Figure 19-4, Visual Studio would have created an event handler named btnCancel_Click
, and placed you in the stub of that event handler:
private void btnCancel_Click(object sender, EventArgs e) { }
The third option is to drop down a list of existing event handlers and thus instruct Visual Studio that the Click
event for this button will share an already existing event handling method, as shown in Figure 19-5.
The fourth and fastest way to wire up an event handler is to double-click on a control. Each control has a default event (the most common event for that control). With a button, that event is, of course, Click
, and double-clicking on a button is exactly like navigating to the Click
event and double-clicking in the event name area: an event handler is created for you with the name btnCancel_Click
.
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 multiselection, 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 form’s 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. You’ll need some classes from System.IO
, so add a using System.IO;
statement at the top of Form1.cs. Next, add the method declaration to Form1.cs:
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, get all the logical drives on the system. To do so, 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 process each drive within the foreach
loop.
The very first thing you need to determine is whether the drive is ready. Our hack for that is to get the list of top-level directories from the drive by calling GetDirectories( )
on a DirectoryInfo
object we 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. I cover the DirectoryInfo
class in detail in Chapter 22.
The GetDirectories( )
method returns a list of directories, but actually, this code throws the list away. You are calling it here only to generate an exception if the drive is not ready.
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, 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);
To get the plus (+
) signs right in the TreeView
, you must find at least two levels of directories (so that the TreeView
knows which directories have subdirectories and can write the +
next to them). You don’t want to recurse through all the subdirectories, however, because that would be too slow.
The job of the GetSubDirectoryNodes( )
method is to recurse two levels deep, passing in the root node, the name of the root directory, a flag indicating whether you want files, and the current level (you always start at level 1):
if ( isSource ) { GetSubDirectoryNodes( ndRoot, ndRoot.Text, true,1 ); } else { GetSubDirectoryNodes( ndRoot, ndRoot.Text, false,1 ); }
You are probably wondering why you need to pass in ndRoot.Text
if you’re already passing in ndRoot
. Patience—you will see why this is needed when you recurse back into GetSubDirectoryNodes
. You are now finished with FillDirectoryTree( )
. See Example 19-1, later in this chapter, for a complete listing of this method.
GetSubDirectoryNodes( )
begins by once again calling GetDirectories( )
, this time stashing away the resulting array of DirectoryInfo
objects:
private void GetSubDireoctoryNodes( 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 (DirectoryInfo dirSub in dirSubs) { if ( (dirSub.Attributes & FileAttributes.Hidden) != 0 ) { continue; }
FileAttributes
is an enum; other possible values include Archive, Compressed, Directory, Encrypted, Hidden, Normal, ReadOnly
, and so on.
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 FileAttributes.Hidden
, a bit is set if the file has the hidden
attribute; otherwise, all the bits are cleared. You can check for any hidden bit by testing whether the resulting int
is something other than 0.
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);
Now you check the current level (passed in by the calling method) against a constant defined for the class:
private const int MaxLevel = 2;
so as to recurse only two levels deep:
if ( level < MaxLevel ) { GetSubDirectoryNodes( subNode, dirSub.FullName, getFileNames, level+1 ); }
You pass in the node you just created as the new parent, the full path as the full name of the parent, and the flag you received, along with one greater than the current level (thus, if you started at level 1, this next call will set the level to 2).
The call to the TreeNode
constructor uses the Name
property of the DirectoryInfo
object, whereas the call to GetSubDirectoryNodes( )
uses the FullName
property. If your directory is C:WindowsMediaSounds, the FullName
property returns the full path, and the Name
property returns just Sounds
. Pass in only the name to the node because that is what you want displayed in the tree view. Pass in the full name with the path to the GetSubDirectoryNodes( )
method so that the method can locate all the subdirectories on the disk. This answers the question asked earlier regarding why you need to pass in the root node’s name the first time you call this method. What is passed in isn’t 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, call the GetFiles( )
method on the DirectoryInfo
object. An array of FileInfo
objects is returned:
if (getFileNames) { // Get any files for this node. FileInfo[] files = dir.GetFiles( );
The FileInfo
class 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 don’t 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. See Example 19-1 for a complete listing of this method.
If you found any of this confusing, I highly recommend putting the code into your debugger and stepping through the recursion; you can watch the TreeView
build its nodes.
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
, one of the nodes in the right TreeView
, or one of the plus signs in either view.
Let’s consider the clicks on the TreeView
s 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 the checkbox indicating a file or directory, a number of events are raised. The event you must handle is AfterCheck
.
To do so, implement a custom event handler method you will create and name tvwSource_AfterCheck( )
. Visual Studio will wire this to the event handler, or if you aren’t using the IDE, 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( )
that you’ll also write. The SetCheck
method will recursively set the checkmark for all the contained folders.
To add the AfterCheck
event, select the tvwSource
control, click the Events icon in the Properties window, and then double-click AfterCheck. This will add the event, wire it up, and place you in the code editor where you can add the body of the method:
private 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
). 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 checkmark 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, check to see whether it is a leaf. A node is a leaf if its own Nodes
collection has a count of 0. If it is a leaf, set its check
property to whatever was passed in as a parameter. If it isn’t a leaf, recurse:
private void SetCheck(TreeNode node, bool check) { // find all the child nodes from this node foreach (TreeNode n in node.Nodes) { n.Checked = check; // check the node // if this is a node in the tree, recurse if (n.Nodes.Count != 0) { SetCheck(n,check); } } }
This propagates the checkmark (or clears the checkmark) 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.
Each time you click a +
next to a directory in the source (or in the target), you want to expand that directory. To do so, you’ll need an event handler for the BeforeExpand
event. Because the event handlers will be identical for both the source and the target tree views, you’ll create a shared event handler (assigning the same event handler to both):
private void tvwExpand(object sender,TreeViewCancelEventArgs
e) {TreeView
tvw = (TreeView
) sender; bool getFiles = tvw == tvwSource;TreeNode
currentNode = e.Node; string fullName = currentNode.FullPath; currentNode.Nodes.Clear( ); GetSubDirectoryNodes( currentNode, fullName, getFiles, 1 ); }
The first line of this code casts the object passed in by the delegate from object
to TreeView
, which is safe because you know that only a TreeView
can trigger this event.
Your second task is to determine whether you want to get the files in the directory you are opening, and you do only if the name of the TreeView
that triggered the event is tvwSource
.
You determine which node’s +
was checked by getting the Node
property from the TreeViewCancelEventArgs
that is passed in by the event:
TreeNode
currentNode = e.Node;
Once you have the current node, you get its full pathname (which you will need as a parameter to GetSubDirectoryNodes
), and then you must clear its collection of subnodes because you are going to refill that collection by calling in to GetSubDirectoryNodes
:
currentNode.Nodes.Clear( );
Why do you clear the subnodes and then refill them? Because this time you will go another level deep so that the subnodes know whether they in turn have subnodes, and thus will know whether they should draw a +
next to their subdirectories.
The second event handler for the target TreeView
(in addition to BeforeExpand
) is somewhat trickier. The event itself is AfterSelect
. (Remember that the target TreeView
doesn’t 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:
private 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 0; 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:
private void btnClear_Click( object sender, System.EventArgs e ) { foreach ( TreeNode node in tvwSource.Nodes ) { SetCheck( node, false ); } }
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. This is a perfect job for ArrayList
. Delegate responsibility for filling the list to a method called GetFileList( )
:
private void btnCopy_Click (object sender, System.EventArgs e) {List
<FileInfo
> fileList = GetFileList( );
Let’s pick that method apart before returning to the event handler.
Start by instantiating a new List
object to hold the strings representing the names of all the files selected:
privateList
<FileInfo
> GetFileList( ) {List
<string> fileNames = newList
<string>( );
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, 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, 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, List<string> fileNames )
{if ( node.Nodes.Count == 0 )
{
if ( node.Checked )
{
string fullPath = GetParentString( node );
fileNames.Add( fullPath );
}
}
If the node is not a leaf, recurse down the tree, finding the child nodes:
else { foreach ( TreeNode n in node.Nodes ) { GetCheckedFiles( n, fileNames ); } } }
This returns the List
filled with all the filenames. Back in GetFileList( )
, use this List
of filenames to create a second List
, this time to hold the actual FileInfo
objects:
List<FileInfo> fileList = new List<FileInfo>( );
Notice the use of type-safe List
objects to ensure that the compiler flags any objects added to the collection that aren’t of type FileInfo
.
You can now iterate through the filenames in fileList
, picking out each name and instantiating a FileInfo
object with it. You can detect whether 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 FileInfo(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 FileInfo
objects?
To solve this, you must pass in an IComparer<T>
interface. We’ll create a class called FileComparer
that will implement this generic interface for FileInfo
objects:
public class FileComparer : IComparer<FileInfo> {
This class has only one method, Compare( )
, which takes two FileInfo
objects as arguments:
public int Compare(FileInfo
file1,FileInfo
file2){
The normal approach is to return 1
if the first object (file1
) is larger than the second (file2
), 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.
Because this is the only use of the 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.
To test the length of the FileInfo
object, you must cast the Object
parameters to FileInfo
objects (which is safe because you know this method will never receive anything else):
public int Compare(FileInfo file1, FileInfo file2) { if ( file1.Length > file2.Length ) { return −1; } if ( file1.Length < file2.Length ) { return 1; } return 0; }
Returning to GetFileList( )
, you were about to instantiate the IComparer
reference and pass it to the Sort( )
method of fileList
:
IComparer<FileInfo> comparer = ( IComparer<FileInfo> ) new FileComparer( ); fileList.Sort(comparer);
With 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) {List
<FileInfo
> 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.";
As you go, write the progress to the lblStatus
label and call Application.DoEvents( )
to give the UI an opportunity to redraw. 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, handle all exceptions by popping up a dialog box with the error; you might want to take corrective action in a commercial application.
That’s it; you’ve implemented file copying!
The code to handle the Delete
event is even simpler. The very first thing you do is ask the user whether she is sure she wants to delete the files:
private 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 if ( result == System.Windows.Forms.DialogResult.OK ) { List<FileInfo> fileNames = GetFileList( ); foreach ( FileInfo file in fileNames ) { try { lblStatus.Text = "Deleting " + txtTargetDir.Text + "\" + file.Name + "..."; Application.DoEvents( ); // Danger Will Robinson! file.Delete( ); } catch ( Exception ex ) { MessageBox.Show( ex.Message ); } } lblStatus.Text = "Done."; Application.DoEvents( ); } }
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, as follows:
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 whether the user selected 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.
This code is identical to the copy code, except that the method that is called on the file is Delete( )
.
Example 19-1 provides the commented source code for this example.
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Windows.Forms; /// <remarks> /// File Copier - Windows Forms demonstration program /// (c) Copyright 2007 O'Reilly Media /// </remarks> namespace FileCopier { /// <summary> /// Form demonstrating Windows Forms implementation /// </summary> partial class frmFileCopier : Form { private const int MaxLevel = 2; public frmFileCopier( ) { InitializeComponent( ); FillDirectoryTree( tvwSource, true ); FillDirectoryTree( tvwTarget, false ); } /// <summary> /// nested 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<FileInfo> { public int Compare(FileInfo file1, FileInfo file2) { if ( file1.Length > file2.Length ) { return −1; } if ( file1.Length < file2.Length ) { return 1; } return 0; } public bool Equals(FileInfo x, FileInfo y) { throw new NotImplemented Exception( );} public int GetHashCode(FileInfo x) {throw new NotImplementedException( ); } } 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 ) { 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( ); // force exception if drive not ready 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,1 ); } else { GetSubDirectoryNodes( ndRoot, ndRoot.Text, false,1 ); } } // Catch any errors such as // Drive not ready. catch { } Application.DoEvents( ); } } // close for FillSourceDirectoryTree /// <summary> /// Gets all the subdirectories below the /// passed-in directory node. /// Adds to the directory tree. /// The parameters passed in are 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, int level ) { 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. if ( level < MaxLevel ) { GetSubDirectoryNodes( subNode, dirSub.FullName, getFileNames, level+1 ); } } 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 ); } } } /// <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 List<FileInfo> 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."; } /// <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 topmost node for each drive // and tell it to clear recursively foreach ( TreeNode node in tvwSource.Nodes ) { SetCheck( node, false ); } } /// <summary> /// on cancel, exit /// </summary> private void btnCancel_Click(object sender, EventArgs e) { Application.Exit( ); } /// <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 files checked private void GetCheckedFiles( TreeNode node, List<string> 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 { // if this node is not a leaf 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 List<FileInfo> GetFileList( ) { // create an unsorted array list of the full filenames List<string> fileNames = new List<string>( ); // 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 List<FileInfo> fileList = new List<FileInfo>( ); // 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<FileInfo> comparer = ( IComparer<FileInfo> ) 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> /// 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 List<FileInfo> 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 checkmark 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 checkmarks /// </summary> private void SetCheck( TreeNode node, bool check ) { // find all the child nodes from this node foreach ( TreeNode n in node.Nodes ) { n.Checked = check; // check the node // if this is a node in the tree, recurse if ( n.Nodes.Count != 0 ) { SetCheck( n, check ); } } } private void tvwExpand(object sender, TreeViewCancelEventArgs e) { TreeView tvw = ( TreeView ) sender; bool getFiles = tvw == tvwSource; TreeNode currentNode = e.Node; string fullName = currentNode.FullPath; currentNode.Nodes.Clear( ); GetSubDirectoryNodes( currentNode, fullName, getFiles, 1 ); } } // end class frmFileCopier } // end namespace FileCopier
18.222.121.156