3.5. Copying Files Using Tree Views


Let's try something a bit fancier. Add a menu choice to the Welcome form's menu named mnuFilesFileCopier. Set its Text to File Copier. The event handler for that menu choice will open the frmFilesCopier form that you'll create to copy 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.

Although you won't implement every possible feature, you can imagine programming this form so that you can mark dozens of files and have them copied to multiple disks.

Begin by creating the frmFilesCopier form, then extending it to a size of 570,740. Next, drag on three labels, a text box, two tree view controls, four buttons, and a checkbox, as shown in Figure 3-15.

Figure 3-15. File Copier design


Drag a StatusStrip on to the form at the bottom. Click on the status strip's drop down (on the form) and chose StatusLabel. Set the label's name to lblStatus and set its Text to Ready.

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 tvwSource to TRue, and on tvwTarget to false.

Once you've done this, double-click the Cancel button to create its event handler. The entire implementation for this event handler is to close the form without taking further action, as shown in Example 3-21.

Example 3-21. Cancel button Click event handler
 Private Sub btnCancel_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCancel.Click     Me.Close(  ) End Sub 

3.5.1. 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, tvwTarget, lists only directories. Also, although tvwSource will allow multiselect, which is the default for treeView controls, you will enforce single selection for tvwTarget.

Before you begin, please add these three Imports statements to the top of your code file:

 Imports System.Collections.Generic Imports System.Collections Imports System.IO 

Factor the common code for both treeView controls into a shared method FillDirectoryTree , passing in the target tree view and a flag indicating whether to get the files, as shown in Example 3-22.

Example 3-22. FillDirectoryTree helper method
 Private Sub FillDirectoryTree( _ ByVal tvw As TreeView, _ ByVal getFiles As Boolean) End Sub 

You'll call this method from the Form's Load event handler, once for each of the two controls, as shown in Example 3-23.

Example 3-23. FilesCopier form Load event handler
 Private Sub frmFilesCopier_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load       Me.Cursor = Cursors.WaitCursor       Me.FillDirectoryTree(Me.tvwSource, True)       Me.FillDirectoryTree(Me.tvwTarget, False)       Me.Cursor = Cursors.Default  End Sub 

Because filling the Directory Trees will take a few seconds, you change the cursor to the WaitCursor mode until the work is complete.


3.5.1.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. The first thing you'll do in FillDirectoryTree is empty that collection:

 tvw.Nodes.Clear(  ) 

You are ready to fill the treeView's Nodes collection by recursing through the directories of all the drives. You'll implement a method called GetSubDirectoryNodes that does exactly that.

Recursion

A method can call any method. It can also call itself. Thus, the GetSubDirectoryNodes method may, in fact, call GetSubDirectoryNodes. This can be a powerful way to solve a problem, and it can be an effective way to crash your system. The trick is to avoid "infinite recursion " in which you recurse repeatedly and without end.

Each time your method recurses, a section of memory is allocated on the stack to hold the information about each call to the method (complete with the parameters passed in). If you recurse too many times you run out of stack and <poof> your program goes up in (virtual) smoke.

The answer to this problem is to have a terminal condition: a condition under which the method returns without further recursion.

The first time GetSubDirectoryNodes is called, the level parameter is passed in (let's say that value is 1). When you recurse, you increase that level by one:

 GetSubDirectoryNodes(subNode, _  dirsub.FullName, getFileNames, level + 1) 

Processing of the current GetSubDirectoryNodes method stops and the new version runs. The value that is passed in is the original value (1) plus 1 = 2.

Each time through the loop that value is checked against the constant MaxLevel, which you previously set to 2:

 Private Const MaxLevel As Integer = 2 If level < MaxLevel Then 

Since level (2) is now no longer less than MaxLevel, you do not recurse again; you do your remaining work and then you return. What you return to is the original version of GetSubDirectoryNodes, which then completes its run and returns. This is illustrated in Figure 3-16.


3.5.2. Displaying the Directories

Before calling GetSubDirectoryNodes, FillDirectoryTree needs to get all the logical drives on the system. To do so, 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

Figure 3-16. Recursion


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.

 For Each rootDirectoryName As String In strDrives 

You process each drive within the For Each loop.

The very first thing you need to determine is whether the drive is ready. One hack for doing that is to get the list of top-level directories from the drive by calling GeTDirectories on a DirectoryInfo object you create for the root directory, like this:

 Try     Dim dir As DirectoryInfo = 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 throws an exception if the drive is not ready (e.g., the A: drive does not have a floppy in it). Your goal here is just to skip over those drives; you don't actually care about the directories returned.

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.

Continuing in the TRy block (if you're still there, the drive is ready), create a TReeNode to hold the root directory of the drive and add that node to the TReeView control, like this:

 Dim ndRoot As TreeNode = 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 the TReeView knows which directories have subdirectories and can write the plus sign next to them). You do not 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, as shown schematically in Figure 3-16. You pass it:

  • The root node (ndRoot)

  • The name of the root directory (ndRoot.Text)

  • A flag indicating whether you want files (TRue) or just directories (False)

  • The current level (you always start at level 1)

Here's the code for doing these steps:

 If (getFiles = True) Then     GetSubDirectoryNodes(ndRoot, ndRoot.Text, True, 1) Else     GetSubDirectoryNodes(ndRoot, ndRoot.Text, False, 1) End If 

You will see why you need to pass in ndRoot.Text when you recurse back into GetSubDirectoryNodes.

3.5.2.1. 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, _ ByVal level As Int32)     Dim dir As DirectoryInfo = New DirectoryInfo(fullName) 

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 within a try block (forbidden files and directories will throw an exception that you can safely ignore). Here's some code for doing that:

 Try     Dim dirSubs As DirectoryInfo(  ) = dir.GetDirectories(  )     For Each dirsub As DirectoryInfo In dirSubs     ''... Catch ex As Exception     ' ignore exceptions End Try 

Create a treeNode with the directory name and add it to the Nodes collection of the node passed in to the method (parentNode), like this:

 Dim subNode As TreeNode = 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 MaxLevel As Integer = 2 

so as to recurse only two levels deep:

 If level < MaxLevel Then     'recursion     GetSubDirectoryNodes( _         subNode, _         dirsub.FullName, _         getFileNames, _         level + 1) End If 

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 (getFileNames), along with one greater than the current level (thus, if you started at level 1, this next call will set the level to 2).

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:\WinNT\Media\Sounds, the FullName property will return the full path, while the Name property will return 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 path to the GetSubDirectoryNodes method so that the method can locate all the subdirectories on the disk. This is 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!


3.5.2.2. Getting the files in the directory

Once you've recursed through the subdirectories, it's 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 = True 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:

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

That's all it takes to fill the two tree views. Run the program and see how it works so far.

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.


3.5.3. Handling TreeView Events

You must handle a number of events for this page to work properly. For example, the user might click Cancel, Copy, Clear, or Delete. She 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 TReeViews first, as they are the most interesting, and potentially the most challenging.

3.5.3.1. Clicking the source TreeView

There are two TReeView objects , each with its own event handlers. 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 .

Your implementation of AfterCheck will delegate the work to a recursive method named SetCheck that you'll also write. The SetCheck method will recursively set the check mark for all the contained folders.

To add the AfterCheck event, select the tvwSource control, click the Events icon in the Properties window, then double-click AfterCheck. This will add the event, wire it, and place you in the code editor where you can add the body of the method, shown in Example 3-24.

Example 3-24. AfterCheck event handler
 Private Sub tvwSource_AfterCheck( _ ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.TreeViewEventArgs) _ Handles tvwSource.AfterCheck     SetCheck  (e.Node, e.Node.Checked) End Sub 

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 its checked state.

Each node has a Nodes property, which gets a TReeNodeCollection containing all the subnodes. Your SetCheck method 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, set the checked property to the Boolean value passed in. A node is a leaf if its own Nodes collection has a count of zero; if the current node is not a leaf, recurse. Code for the SetCheck method is shown in Example 3-25.

Example 3-25. SetCheck method
 Private Sub SetCheck( _ ByVal node As TreeNode, _ ByVal check As Boolean)     For Each n As TreeNode In node.Nodes         n.Checked = check         If n.Nodes.Count <> 0 Then             SetCheck(n, check)         End If     Next End Sub 

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.

3.5.3.2. Expanding a directory

Each time you click on a plus sign 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. Since 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), as shown in Example 3-26.

Example 3-26. BeforeExpand event handler BeforeExpand event handler
 Private Sub tvwExpand( _ ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) _ Handles tvwSource.BeforeExpand, tvwTarget.BeforeExpand     Dim tvw As TreeView = CType(sender, TreeView)     Dim getFiles As Boolean = (tvw.Name = "tvwSource")     Dim currentNode As TreeNode = e.Node     Dim fullName As String = currentNode.FullPath     currentNode.Nodes.Clear(  )     GetSubDirectoryNodes(currentNode, fullName, getFiles, 1) End Sub 

There are two schools of thought on how terse to make your code. For example, many programmers would argue that the declaration of getFiles should be written as:

 Dim getFiles As Boolean = False If tvw.Name = "tvwSource" Then    getFiles = True End If 

The significant advantage to the longer style is that you can examine the interim values in the debugger if your results are not what you expect.


The first line of tvwExpand casts sender from System.Object to treeView, which is safe since you know that only a treeView can trigger this event.

You must determine whether you want to get the files in the directory you are opening. You want to get the files only if the name of the TReeView that triggered the event is tvwSource.

You determine which node's plus mark was checked by getting the Node property from the TReeViewCancelEventArgs that is passed in as the second argument.

 Dim currentNode As TreeNode = e.Node 

Once you have the current node, you get its full path name (which you will need as a parameter to GetSubDirectoryNodes). You then clear its collection of subnodes; you are going to refill that collection by calling 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 if they, in turn, have subnodes, and thus will know if they should draw a plus mark next to their subdirectories.

3.5.3.3. Clicking the target TreeView

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 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. An event handler for AfterSelect that does all this is shown in Example 3-27.

Example 3-27. AfterSelect event handler AfterSelect event handler
 Private Sub tvwTarget_AfterSelect( _ ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.TreeViewEventArgs) _ Handles tvwTarget.AfterSelect     Dim theFullPath As String = GetParentString  (e.Node) 

(You'll see 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, like this:

 If theFullPath.EndsWith("\") Then     theFullPath = theFullPath.Substring(0, theFullPath.Length - 1) End If Me.txtTarget.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, as shown in Example 3-28.

Example 3-28. GetParentString method
 Private Function GetParentString(ByVal node As TreeNode) As String     If node.Parent Is Nothing Then         Return node.Text     Else         Dim endString As String = String.Empty         If node.Nodes.Count <> 0 Then endString = "\"         Return GetParentString(node.Parent) + node.Text + endString     End If End Function 

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

3.5.3.4. Handling the Clear button event

Given the SetCheck method developed earlier, handling the Clear button's Click event is trivial, as shown in Example 3-29.

Example 3-29. Clear button Click event handler
 Private Sub btnClear_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnClear.Click     For Each node As TreeNode In tvwSource.Nodes         SetCheck(node, False)     Next End Sub 

Just call the SetCheck method on the root nodes and tell them to recursively uncheck all their contained nodes.

3.5.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's Click event. The very first thing you need to do is to get a list of which files were selected. This will be represented as a collection of FileInfo objects. Delegate responsibility for filling the list to a method called GetFileList as the first step executed by the event handler:

 Private Sub btnCopy_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnCopy.Click     Dim fileList As List(Of FileInfo) = GetFileList(  ) 

Let's examine the GetFileList method before returning to the event handler.

3.5.4.1. Getting the selected files

Start by instantiating a new List(Of string) object to hold the strings representing the names of all the files selected:

 Private Function GetFileList(  ) As List(Of FileInfo)     Dim fileNames As List(Of String) = New List(Of String) 

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

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

To see how this works, look at the GetCheckedFiles method, shown in Example 3-30. This method is pretty simple: it examines the node it was handed. If that node has no children, it is a leaf. If that leaf is checked, get the full path (by calling GetParentString on the node) and add it to the List(Of String) passed in as a parameter.

Example 3-30. GetCheckedFiles method
 Private Sub GetCheckedFiles( _ ByVal node As TreeNode, _ ByVal fileNames As List(Of String))     If node.Nodes.Count = 0 Then         If node.Checked Then             fileNames.Add(GetParentString(node))         End If     Else         For Each n As TreeNode In node.Nodes             GetCheckedFiles(n, fileNames)         Next     End If End Sub 

Notice that if the node is not a leaf, you recurse down the tree, finding the child nodes.

This will return the List filled with all the filenames. Back in GetFileList, create a second List, this time to hold the actual FileInfo objects:

 Dim fileList As List(Of FileInfo) = New List(Of FileInfo) 

Notice the use of type-safe List objects to ensure that the compiler will flag any objects added to the collection that are not of type FileInfo.

You can now iterate through the filenames in fileNames, 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 List(Of FileInfo), as shown in the following snippet:

 For Each fileName As String In fileNames     Dim file As FileInfo = New FileInfo(fileName)     If file.Exists Then         fileList.Add(file)     End If Next 

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! At this point, you've returned with a list of FileInfo objects, each representing a file selected in the source TReeView. You can now iterate through the list, copying the files and updating the UI, as shown in the completed Click event handler in Example 3-31.

Example 3-31. Copy button Click event handler
 Private Sub btnCopy_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCopy.Click     Dim fileList As List(Of FileInfo) = GetFileList(  )     For Each file As FileInfo In fileList         Try             lblStatus.Text = "Copying " + txtTarget.Text + "\" + file.Name + "..."             Application.DoEvents(  )             file.CopyTo(txtTarget.Text + "\" + file.Name, cbOverwrite.Checked)         Catch ex As Exception             MessageBox.Show(ex.Message)         End Try     Next     lblStatus.Text = "Done"     Application.DoEvents(  ) End Sub 

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 cbOverwrite checkbox. The Checked property evaluates to 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!

3.5.5. Handling the Delete Button Event

The code to handle the delete event is even simpler. The very first thing you do is make sure the user really wants to delete the files. 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.YesNo

Asks for two buttons: Yes and No


MessageBox.IconExclamation

Indicates that you want to display an exclamation mark icon


MessageBox.DefaultButton.Button2

Sets the second button (No) as the default choice

When the user chooses Yes or No, the result is passed back as a System.Windows.Forms.DialogResult enumerated value. You can test this value to see if the user selected Yes, as shown in the following code snippet:

 Private Sub btnDelete_Click( _    ByVal sender As System.Object, _    ByVal e As System.EventArgs) Handles btnDelete.Click     Dim result As DialogResult = _         MessageBox.Show( _             "Are you quite sure?", _             "Delete Files", _             MessageBoxButtons.YesNo, _             MessageBoxIcon.Exclamation, _             MessageBoxDefaultButton.Button2)     If result = Windows.Forms.DialogResult.Yes Then         Dim fileNames As List(Of FileInfo) = GetFileList(  )         For Each file As FileInfo In fileNames             Try                 lblStatus.Text = "Deleting " + txtTarget.Text + "\" +                 file.Name + "..."                 Application.DoEvents(  )                 file.Delete(  )             Catch ex As Exception                 MessageBox.Show(ex.Message)             End Try         Next         lblStatus.Text = "Done."         Application.DoEvents(  )     End If End Sub 

Assuming the value you get back from the DialogResult is Yes, you get the list of fileNames and iterate through it, deleting each as you go:

The final working version of FilesCopier window is shown in Figure 3-17.

Figure 3-17. Working version of the FilesCopier




Programming Visual Basic 2005
Programming Visual Basic 2005
ISBN: 0596009496
EAN: 2147483647
Year: 2006
Pages: 162
Authors: Jesse Liberty

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net