Previous section   Next section

13.2 Creating a Windows Forms Application

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 Visual Basic .NET 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-9.

Figure 13-9. The FileCopier user interface
figs/pvn2_1309.gif

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 complete code listing is shown in Example 13-3.

13.2.1 Creating the Basic UI Form

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. Create the controls as shown in Table 13-1 so that your form looks like Figure 13-10.

Table 13-1. Controls for FileCopier

Type

Name

Text

Notes

Label

lblSource

Source Files

Bold, underlined, 12 point font

Label

lblTarget

Target Directory

Bold, underlined, 12 point font

Label

lblStatus

<none>

Blank label, multi-line

Button

btnClear

Clear

Clears the checkboxes

Button

btnCopy

Copy

Copies from source to destination

Button

btnDelete

Delete

Deletes the selected files

Button

btnCancel

Cancel

Exits the application

TextBox

txtTargetDirectory

<none>

Appears above treeview for Target directory

CheckBox

chkOverwrite

Overwrite if exists

 

TreeView

tvwSource

 

Set CheckBoxes property = True

TreeView

tvwTargetDir

 

Set CheckBoxes property = False

Figure 13-10. Creating the FileCopier form
figs/pvn2_1310.gif

Once your form is created, 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 and places you at the correct location within the code editor. One particular event is the button click event, and Visual Studio .NET opens that event's event handler:

Private Sub btnCancelClick( _
    ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles btnCancel.Click
    Application.Exit( )
End Sub 'btnCancelClick

You can set many different events for the TreeView control 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.

13.2.2 Populating the TreeView Controls

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 Sub FillDirectoryTree( _
    ByVal tvw As TreeView, _
    ByVal isSource As Boolean)
13.2.2.1 TreeNode objects

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 shared 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:

Dim strDrives As String( ) = 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:

Dim rootDirectoryName As String
For Each rootDirectoryName In strDrives

You should process each drive within the For Each 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:

Dim dir As New DirectoryInfo(rootDirectoryName)
dir.GetDirectories( )

The DirectoryInfo class exposes instance methods for creating, moving, and enumerating through directories, their files, and their subdirectories. 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.

In the code shown in Example 13-3, the catch block displays a message box with the exception. This is used for debugging purposes only.

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:

Dim ndRoot As 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:

GetSubDirectoryNodes(ndRoot, ndRoot.Text, isSource)

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.

13.2.2.2 Recursing through the subdirectories

GetSubDirectoryNodes( ) begins by once again calling GetDirectories( ), this time stashing away the resulting array of DirectoryInfo objects:

Private Sub GetSubDirectoryNodes( _
    ByVal parentNode As TreeNode, _
    ByVal fullName As String, _
    ByVal getFileNames As Boolean)

    Dim dir As New DirectoryInfo(fullName)
    Dim dirSubs As DirectoryInfo( ) = 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:

Dim dirSub As DirectoryInfo
For Each dirSub In dirSubs

    If (dirSub.Attributes And FileAttributes.Hidden) = 0 Then

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 integer 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):

Dim subNode As New TreeNode(dirSub.Name)
parentNode.Nodes.Add(subNode)

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 GetSubDirec-toryNodes( ) uses the FullName property. If your directory is c:\WinNT\Media\Sounds, 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!

13.2.2.3 Getting the files in the directory

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 Then
    Dim files As FileInfo( ) = 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 do not have subdirectories:

Dim file As FileInfo
For Each file In files
    Dim fileNode As New TreeNode(file.Name)
    parentNode.Nodes.Add(fileNode)
Next file

That's all it takes to fill the two tree views.

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.

13.2.2.4 Begin and EndUpdate

You might be adding many nodes to the TreeView control. To enhance the performance of the program, you'll call BeginUpdate( ) on the TreeView before you begin looping through the entries, and EndUpdate( ) when you are done. The BeginUpdate( ) method prevents the control from painting until the EndUpdate( ) method is called, which greatly enhances its performance.

13.2.3 Handling TreeView Events

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 more interesting, and potentially more challenging.

13.2.3.1 Clicking the source TreeView

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( ). The implementation of AfterCheck( ) delegates the work to a recursable method named SetCheck( ), which you'll also write:

Private Sub tvwSourceAfterCheck( _
ByVal sender As Object, _
ByVal e As System.Windows.Forms.TreeViewEventArgs) _
  Handles tvwSource.AfterCheck
    SetCheck(e.Node, e.Node.Checked)
End Sub 'tvwSourceAfterCheck

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 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.

It's Turtles, All the Way Down

Steven Hawking tells one of my favorites stories on recursion in A Brief History of Time. A well-known scientist was lecturing on astronomy. He described the Earth's orbit around the sun, and the sun's orbit around the galactic center.

An old lady interrupted to say "Rubbish. The world is really a flat plate supported on the back of a giant tortoise."

The scientist smiled and asked "What is the tortoise standing on?"

"You're very clever, young man, very clever," the old lady replied, "but it's turtles 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 Sub SetCheck( _
 ByVal node As TreeNode, ByVal check As Boolean)
     Dim n As TreeNode
     For Each n In node.Nodes
         n.Checked = check 
         If n.Nodes.Count <> 0 Then
             SetCheck(n, check)
         End If
     Next n
 End Sub 

This propagates the checkmark (or clears the checkmark) down through the entire structure. In this way, the user can indicate that she wants to select all the files in all the subdirectories by clicking a single directory.

13.2.3.2 Clicking the target TreeView

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 above the tree view.

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 Sub tvwTargetDirAfterSelect( _
ByVal sender As Object, _
ByVal e As System.Windows.Forms.TreeViewEventArgs) _
  Handles tvwTargetDir.AfterSelect

    Dim theFullPath As String = 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("\") Then
    theFullPath = _
    theFullPath.Substring( _
       0, theFullPath.Length - 1)
End If
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 Function GetParentString( _
    ByVal node As TreeNode) As String
    If node.Parent Is Nothing Then
        Return node.Text
    Else
        Dim suffix As String
        If node.Nodes.Count = 0 Then
            suffix = ""
        Else
            suffix = "\"
        End If
        Return GetParentString(node.Parent) _
            + node.Text + suffix
    End If
End Function 'GetParentString

The recursion stops when there is no parent; that is, when you hit the root directory.

13.2.3.3 Handling the Clear button event

Given the SetCheck( )method developed earlier, handling the Clear button's click event is trivial:

Private Sub btnClearClick( _
ByVal sender As Object, ByVal e As System.EventArgs) _
  Handles btnClear.Click

    Dim node As TreeNode
    For Each node In tvwSource.Nodes
        SetCheck(node, False)
    Next node

End Sub 'btnClearClick

You just call the SetCheck( ) method on the root nodes and tell them to recursively uncheck all their contained nodes.

13.2.4 Implementing the Copy Button Event

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( ):

Private Sub btnCopyClick( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles btnCopy.Click

    Dim fileList As ArrayList = GetFileList( )

Let's pick that method apart before returning to the event handler.

13.2.4.1 Getting the selected files

You start by instantiating a new ArrayList object to hold the strings representing the names of all the files selected:

Private Function GetFileList( ) As ArrayList
     Dim fileNames As New ArrayList( )

To get the selected filenames, you can walk through the source TreeView control:

Dim theNode As TreeNode
For Each theNode In tvwSource.Nodes
    GetCheckedFiles(theNode, fileNames)
Next theNode

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 Sub GetCheckedFiles( _
    ByVal node As TreeNode, _
    ByVal fileNames As ArrayList)

    If node.Nodes.Count = 0 Then
        If node.Checked Then
            Dim fullPath As String = _
                GetParentString(node)
            fileNames.Add(fullPath)
        End If

If the node is not a leaf, you want to recurse down the tree, finding the child nodes:

    Else
        Dim n As TreeNode
        For Each n In node.Nodes
            GetCheckedFiles(n, fileNames)
        Next n
    End If
End Sub 'GetCheckedFiles

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:

Dim fileList As 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:

Dim fileName As String
For Each fileName In fileNames
    Dim file As New FileInfo(fileName)
    If file.Exists Then
        fileList.Add(file)
    End If
13.2.4.2 Sorting the list of selected files

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
    Implements IComparer

This class has only one method, Compare( ), which takes two objects as arguments:

Public Function Compare( _
    ByVal f1 As Object, _
    ByVal f2 As Object) _
    As Integer _
    Implements IComparer.Compare

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 13-3.

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):

        Dim file1 As FileInfo = CType(f1, FileInfo)
        Dim file2 As FileInfo = CType(f2, FileInfo)
        If file1.Length > file2.Length Then
            Return -1
        End If
        If file1.Length < file2.Length Then
            Return 1
        End If
        Return 0
    End Function 'Compare
End Class 'FileComparer

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:

Dim comparer As IComparer = _
    CType(New FileComparer( ), IComparer)
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!

Private Sub btnCopyClick( _
    ByVal sender As Object, _
    ByVal e As System.EventArgs) _
    Handles btnCopy.Click
    ' get the list
    Dim fileList As ArrayList = 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:

    Dim file As FileInfo
    For Each file In fileList
        Try
            lblStatus.Text = _
              "Copying " + txtTargetDir.Text + _
              "\" + file.Name + "..."
            Application.DoEvents( )

            file.CopyTo( _
               txtTargetDir.Text + _
               "\" + file.Name, _
               chkOverwrite.Checked)

        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    Next file
    lblStatus.Text = "Done."
    Application.DoEvents( )
End Sub 'btnCopyClick

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.

That's it; you've implemented file copying!

13.2.5 Handling the Delete Button Event

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:

Private Sub btnDeleteClick( _
ByVal sender As Object, ByVal e As System.EventArgs) _
  Handles btnDelete.Click
    ' ask them if they are sure
    Dim result As System.Windows.Forms.DialogResult = _
        MessageBox.Show( _
        "Are you quite sure?", _
           "Delete Files", _
        MessageBoxButtons.OKCancel, _
            MessageBoxIcon.Exclamation, _
            MessageBoxDefaultButton.Button2)

You can use the MessageBox static Show( ) method, passing:

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 Then

If so, you can get the list of fileNames and iterate through it, deleting each as you go:

Dim fileNames As ArrayList = GetFileList( )

Dim file As FileInfo
For Each 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 ex As Exception
        ' you may want to do more than 
        ' just show the message
        MessageBox.Show(ex.Message)
    End Try
Next file
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.

Example 13-3. Complete FileCopier code
Imports System
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Data
Imports System.IO

Namespace FileCopier

    ' Form demonstrating Windows Forms implementation
    Public Class Form1
        Inherits System.Windows.Forms.Form
        'Tree view of source directories
        'includes check boxes for checking
        'chosen files or directories
        Private WithEvents tvwSource As System.Windows.Forms.TreeView

        'Tree view of potential target directories
        Private WithEvents tvwTargetDir As System.Windows.Forms.TreeView

        'When pressed, sets all check boxes
        'in source tree view to clear
        Private WithEvents btnClear As System.Windows.Forms.Button

        'If checked, when copying we'll
        'overwrite existing files
        Private WithEvents chkOverwrite As System.Windows.Forms.CheckBox

        'Shuts the application
        Private WithEvents btnCancel As System.Windows.Forms.Button

        'Copies the selected files
        'to the target directory
        Private WithEvents btnCopy As System.Windows.Forms.Button

        'Label displays progress when
        'copying or deleting files
        Private lblStatus As System.Windows.Forms.Label

        'Deletes the selected files
        Private WithEvents btnDelete As System.Windows.Forms.Button

        'Currently selected target directory
        Private WithEvents txtTargetDir As System.Windows.Forms.TextBox

        Private lblSource As System.Windows.Forms.Label
        Private lblTarget As System.Windows.Forms.Label

        ' Required designer variable.
        Private components As System.ComponentModel.Container = Nothing

        'internal class which knows how to compare 
        'two files we want to sort large to small, 
        'so reverse the normal return values.
        Public Class FileComparer
            Implements IComparer

            Public Function Compare( _
                ByVal f1 As Object, _
                ByVal f2 As Object) _
                As Integer _
                Implements IComparer.Compare

                Dim file1 As FileInfo = CType(f1, FileInfo)
                Dim file2 As FileInfo = CType(f2, FileInfo)
                If file1.Length > file2.Length Then
                    Return -1
                End If
                If file1.Length < file2.Length Then
                    Return 1
                End If
                Return 0
            End Function 'Compare

            Public Sub New( )

            End Sub
        End Class 'FileComparer

        Public Sub New( )
            '
            ' Required for Windows Form Designer support
            '
            InitializeComponent( )

            ' fill the source and target directory trees
            FillDirectoryTree(tvwSource, True)
            FillDirectoryTree(tvwTargetDir, False)
        End Sub 'New

        ' Fill the directory tree for either the Source or 
        ' Target TreeView.
        Private Sub FillDirectoryTree( _
            ByVal tvw As TreeView, _
            ByVal isSource As Boolean)

            ' 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.
            Dim strDrives As String( ) = Environment.GetLogicalDrives( )

            tvw.BeginUpdate( )

            ' 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.
            Dim rootDirectoryName As String
            For Each rootDirectoryName In strDrives
                If rootDirectoryName = "Z:\" Then
                    Try

                        ' Fill an array with all the first level 
                        ' subdirectories. If the drive is
                        ' not ready, this will throw an exception.
                        Dim dir As New DirectoryInfo(rootDirectoryName)
                        dir.GetDirectories( )

                        Dim ndRoot As 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.
                        GetSubDirectoryNodes(ndRoot, ndRoot.Text, isSource)

                        '  Catch any errors such as 
                        ' Drive not ready.
                    Catch e As Exception
                        MessageBox.Show(e.Message)
                    End Try
                End If
            Next rootDirectoryName

            tvw.EndUpdate( )
        End Sub 'FillDirectoryTree

        ' close for FillSourceDirectoryTree
        ' 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 path name of this subdirectory, 
        ' and a Boolean to indicate
        ' whether or not to get the files in the subdirectory.
        Private Sub GetSubDirectoryNodes( _
            ByVal parentNode As TreeNode, _
            ByVal fullName As String, _
            ByVal getFileNames As Boolean)

            Dim dir As New DirectoryInfo(fullName)
            Dim dirSubs As DirectoryInfo( ) = dir.GetDirectories( )

            '  Add a child node for each subdirectory.
            Dim dirSub As DirectoryInfo
            For Each dirSub In dirSubs

                ' do not show hidden folders
                If (dirSub.Attributes And FileAttributes.Hidden) = 0 Then

                    ' 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
                    Dim subNode As New TreeNode(dirSub.Name)
                    parentNode.Nodes.Add(subNode)

                    ' Call GetSubDirectoryNodes recursively.
                  GetSubDirectoryNodes( _
                        subNode, _
                        dirSub.FullName, _
                        getFileNames)
                End If ' not hidden files
            Next dirSub

            If getFileNames Then
                ' Get any files for this node.
                Dim files As FileInfo( ) = dir.GetFiles( )

                ' After placing the nodes, 
                ' now place the files in that subdirectory.
                Dim file As FileInfo
                For Each file In files
                    Dim fileNode As New TreeNode(file.Name)
                    parentNode.Nodes.Add(fileNode)
                Next file
            End If
        End Sub 'GetSubDirectoryNodes

        ' Clean up any resources being used.
        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If disposing Then
                If Not (components Is Nothing) Then
                    components.Dispose( )
                End If
            End If
            MyBase.Dispose(disposing)
        End Sub 'Dispose

        Private Sub InitializeComponent( )
           ' contents elided to save space in the book
        End Sub 'InitializeComponent



        ' Create an ordered list of all 
        ' the selected files, copy to the
        ' target directory
        Private Sub btnCopyClick( _
            ByVal sender As Object, _
            ByVal e As System.EventArgs) _
            Handles btnCopy.Click
            ' get the list
            Dim fileList As ArrayList = GetFileList( )

            ' copy the files
            Dim file As FileInfo
            For Each 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 ex As Exception
                    ' you may want to do more than 
                    ' just show the message
                    MessageBox.Show(ex.Message)
                End Try
            Next file
            lblStatus.Text = "Done."
            Application.DoEvents( )
        End Sub 'btnCopyClick

        ' on cancel, exit
        Private Sub btnCancelClick( _
            ByVal sender As Object, ByVal e As System.EventArgs) _
            Handles btnCancel.Click
            Application.Exit( )
        End Sub 'btnCancelClick

        ' Tell the root of each tree to uncheck
        ' all the nodes below
        Private Sub btnClearClick( _
        ByVal sender As Object, ByVal e As System.EventArgs) _
          Handles btnClear.Click
            ' get the top most node for each drive
            ' and tell it to clear recursively
            Dim node As TreeNode
            For Each node In tvwSource.Nodes
                SetCheck(node, False)
            Next node
        End Sub 'btnClearClick

        ' check that the user does want to delete
        ' Make a list and delete each in turn
        Private Sub btnDeleteClick( _
        ByVal sender As Object, ByVal e As System.EventArgs) _
          Handles btnDelete.Click
            ' ask them if they are sure
            Dim result As System.Windows.Forms.DialogResult = _
                MessageBox.Show("Are you quite sure?", _
                    "Delete Files", MessageBoxButtons.OKCancel, _
                    MessageBoxIcon.Exclamation, _
                    MessageBoxDefaultButton.Button2)

            If result = System.Windows.Forms.DialogResult.OK Then
                ' iterate through the list and delete them.
                ' get the list of selected files
                Dim fileNames As ArrayList = GetFileList( )

                Dim file As FileInfo
                For Each 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 ex As Exception
                        ' you may want to do more than 
                        ' just show the message
                        MessageBox.Show(ex.Message)
                    End Try
                Next file
                lblStatus.Text = "Done."
                Application.DoEvents( )
            End If
        End Sub 'btnDeleteClick


        ' Get the full path of the chosen directory
        ' copy it to txtTargetDir
        Private Sub tvwTargetDirAfterSelect( _
        ByVal sender As Object, _
        ByVal e As System.Windows.Forms.TreeViewEventArgs) _
          Handles tvwTargetDir.AfterSelect
            ' get the full path for the selected directory
            Dim theFullPath As String = GetParentString(e.Node)

            ' if it is not a leaf, it will end with a back slash
            ' remove the backslash
            If theFullPath.EndsWith("\") Then
                theFullPath = _
                theFullPath.Substring( _
                   0, theFullPath.Length - 1)
            End If
            ' insert the path in the text box
            txtTargetDir.Text = theFullPath
        End Sub 'tvwTargetDirAfterSelect


        ' Mark each node below the current
        ' one with the current value of checked
        Private Sub tvwSourceAfterCheck( _
            ByVal sender As Object, _
            ByVal e As System.Windows.Forms.TreeViewEventArgs) _
              Handles tvwSource.AfterCheck
            ' 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)
        End Sub 'tvwSourceAfterCheck

        ' recursively set or clear check marks
        Private Sub SetCheck( _
        ByVal node As TreeNode, ByVal check As Boolean)
            ' find all the child nodes from this node
            Dim n As TreeNode
            For Each n In node.Nodes
                n.Checked = check ' check the node
                ' if this is a node in the tree, recurse
                If n.Nodes.Count <> 0 Then
                    SetCheck(n, check)
                End If
            Next n
        End Sub 'SetCheck

        ' Given a node and an array list
        ' fill the list with the names of
        ' all the checked files
        ' Fill the ArrayList with the full paths of 
        ' all the files checked
        Private Sub GetCheckedFiles( _
            ByVal node As TreeNode, _
            ByVal fileNames As ArrayList)
            ' if this is a leaf...
            If node.Nodes.Count = 0 Then
                ' if the node was checked...
                If node.Checked Then
                    ' get the full path and add it 
                    ' to the arrayList
                    Dim fullPath As String = _
                        GetParentString(node)
                    fileNames.Add(fullPath)
                End If
                ' if this node is not a leaf
            Else
                ' if this node is not a leaf
                Dim n As TreeNode
                For Each n In node.Nodes
                    GetCheckedFiles(n, fileNames)
                Next n
            End If
        End Sub 'GetCheckedFiles

        ' Given a node, return the full path name
        Private Function GetParentString( _
            ByVal node As TreeNode) As String
            ' if this is the root node (c:\) return the text
            If node.Parent Is Nothing Then
                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
                Dim suffix As String
                If node.Nodes.Count = 0 Then
                    suffix = ""
                Else
                    suffix = "\"
                End If
                Return GetParentString(node.Parent) _
                    + node.Text + suffix
            End If
        End Function 'GetParentString


        ' shared by delete and copy
        ' creates an ordered list of all 
        ' the selected files
        Private Function GetFileList( ) As ArrayList
            ' create an unsorted array list of the full file names
            Dim fileNames As New ArrayList( )

            ' fill the fileNames ArrayList with the 
            ' full path of each file to copy
            Dim theNode As TreeNode
            For Each theNode In tvwSource.Nodes
                GetCheckedFiles(theNode, fileNames)
            Next theNode

            ' Create a list to hold the FileInfo objects
            Dim fileList As New ArrayList( )

            ' for each of the file names we have in our 
            ' unsorted(list)if the name corresponds to 
            ' a file (and not a directory)
            ' add it to the file list
            Dim fileName As String
            For Each fileName In fileNames
                ' create a file with the name
                Dim file As New FileInfo(fileName)

                ' see if it exists on the disk
                ' this fails if it was a directory
                If file.Exists Then
                    ' both the key and the value are the file
                    ' would it be easier to have an empty value?
                    fileList.Add(file)
                End If
            Next fileName

            ' Create an instance of the IComparer interface
            Dim comparer As IComparer = _
                CType(New FileComparer( ), IComparer)

            ' pass the comparer to the sort method so that the list
            ' is sorted by the compare method of comparer.
            fileList.Sort(comparer)
            Return fileList
        End Function 'GetFileList 

    End Class 'Form1
End Namespace 'FileCopier 

  Previous section   Next section
Top