Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
(ebook) Visual Studio .NET Mastering Visual Basic.pdf
Скачиваний:
120
Добавлен:
17.08.2013
Размер:
15.38 Mб
Скачать

820 Chapter 18 RECURSIVE PROGRAMMING

I need to mention one important aspect of recursion here. The clr variable is local and maintains its value while the subroutine is interrupted. Visual Basic stores the values of the local variables of the interrupted procedures and recalls them when the procedure gets a chance to complete. This is possible because each new copy of the procedure that starts executing has its own set of local variables. Local variables are part of the procedure’s status.

Scanning Folders Recursively

The examples of recursive functions we have looked at so far probably haven’t entirely convinced you of the usefulness of recursion. The factorial of a number can be easily calculated with a For…Next loop, and the Recurse subroutine is a side effect (basically, a bug). So, what good is recursion?

The answer is the FileScan application, which can’t be implemented non-recursively. I hope that the previous examples helped you understand the principles of recursive programming and that you’re ready for some real recursion. We’ll design an application similar to Windows Explorer, which scans an entire folder, including its subfolders. As the application scans the files and subfolders of a folder or an entire volume, it can locate files by name, size, and date. It can also move files around and, in general, perform all the operations of Windows Explorer plus any other custom operation you might require. Much of the functionality of this application is provided by Windows Explorer already, but as you’ll see shortly, this application is highly customizable. It can serve as your starting point for many file operations that Windows Explorer doesn’t provide. For example, your custom Explorer could expand all the subfolders each time you open a folder, display the full pathname of each folder, or even process files of a certain type (resize image files, encrypt documents, and so on). Later in the chapter, you’ll see an application that generates a list of all the files in a folder, including its subfolders, organized by folder.

A file-scanning application is ideal for implementing with a recursive function because its operation is defined recursively. Suppose you want to scan all the entries of a folder and locate the files whose size exceeds 1 MB or count the files. If an entry is another folder, the same process must be repeated for that subfolder. If the subfolder contains one or more subfolder(s) of its own, the process must be repeated for each subfolder. This application calls for a recursive function, because every time it runs into a subfolder, it must interrupt the scanning of the current folder and start scanning the subfolder by calling itself. If you spend some time thinking about the implementation of this algorithm, you’ll conclude that there’s no simple way to do it without recursion. Actually, once you’ve established the recursive nature of a process, you’ll know you must code it as a recursive procedure.

Describing a Recursive Procedure

When you’re about to write a recursive procedure, it’s helpful to start with a general written description of the procedure. For the FileScan application, we need a subroutine (since it’s not going to return a result) that scans the contents of a folder: let’s call it ScanFolder(). The ScanFolder() subroutine must scan all the entries of the initial folder and process the files in it. If the current entry is a file, it must act upon the file, depending on its name, size, or any of its attributes. If the current entry is a folder, it must scan the contents of this folder. In other words, it must interrupt the

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SCANNING FOLDERS RECURSIVELY 821

scanning of the current folder and start scanning the subfolder. And the most efficient way to scan a subfolder is to have it to call itself. Here’s the ScanFolder() function in pseudocode:

Sub ScanFolder(current_folder)

Process files in current_folder

If current_folder contains subfolders

For each subfolder

ScanFolder(subfolder)

Next

End If

Translating the Description to Code

Now, let’s translate this description to actual code. Because we need access to each folder’s files and subfolders, we need three controls to display the drives, the folders of the selected drive, and the files in the selected folder, as shown in Figure 18.5. This is the FileScan project, which you can find in this chapter’s folder on the CD. The drives are displayed on a ComboBox control. When the user selects a drive on this ComboBox, the drive’s folders are displayed on the ListBox control under it. When the user double-clicks a folder’s name, the ListBox control is populated with the subfolders of the selected folder. Finally, when the user clicks the Scan Now button, the code scans the selected folder, including its subfolders. This application is similar to the CustomExplorer application you developed in Chapter 13. I will repeat the process for the benefit of readers who are not familiar with recursive coding, and in the following section we’ll add a unique feature to the application. Where the FileScan application displays the files under the current folder only, we’ll add a button to display all the files in the selected folder, as well as the files in all subfolders under the selected one.

Figure 18.5

The FileScan application is the core of a custom Explorer.

Displaying Drives and Folders

The Drives ComboBox control is populated when the application starts from within the Form_Load event handler with the following statements, shown in Listing 18.3.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

822 Chapter 18 RECURSIVE PROGRAMMING

Listing 18.3: Populating the Drives ComboBox Control

Private Sub Form1_Load(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles MyBase.Load Dim drives() As String

drives = System.IO.Directory.GetLogicalDrives Dim iDrive As Integer DrivesList.Items.Clear()

On Error Resume Next

For iDrive = 0 To drives.GetUpperBound(0) DrivesList.Items.Add(drives(iDrive))

Next DrivesList.SelectedIndex = 1

End Sub

Notice the error-trapping code, which consists of the On Error Resume Next statement. This is the simplest type of error trapping, but that’s all we need. VB.NET will throw an exception if you attempt to access a drive that’s not ready. This will happen if you have two floppy drives, A: and B:, and there’s no disk in drive B: when you select the second item in the list (on most systems, the second item will be the hard drive C:). The On Error Resume Next statement tells VB to ignore the error and continue. It will add the names of drives A: and B: to the Drives ComboBox control even if it can’t access the selected drive.

When a drive is selected in the Drives ComboBox, the root folders on this drive are displayed in the Folders ListBox control with the following statements, which are executed from within the SelectedIndexChanged event handler of the Drives ListBox control. First, the code retrieves the names of the folders in the root folder with the GetDirectories method and adds them to the FoldersList control. Then it retrieves the files of the root folder with the GetFiles method and displays their names on the FilesList control. Both FoldersList and FilesList are ListBox controls. The code shown in Listing 18.4 is executed when the user selects a drive in the ComboBox control.

Listing 18.4: Displaying a Drive’s Root Folders

Private Sub DrivesList_SelectedIndexChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles DrivesList.SelectedIndexChanged

Dim directories() As String Try

directories = System.IO.Directory.GetDirectories(DrivesList.Text) Catch drvException As Exception

MsgBox(drvException.Message) Exit Sub

End Try

Dim dir As String FoldersList.Items.Clear() For Each dir In directories

FoldersList.Items.Add(dir) Next

FilesList.Items.Clear() End Sub

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SCANNING FOLDERS RECURSIVELY 823

The error handler catches any runtime exceptions, displays the appropriate message, and aborts the execution of the application. An exception will be thrown if the user attempts to display the contents of a drive that’s not ready (a CD drive that’s empty, for example).

Finally, we must add some code to the DoubleClick event handler of the FoldersList ListBox control. This action signals the user’s intention to switch to one of the displayed folders and view its subfolders. Listing 18.5 shows the code behind the DoubleClick event handler.

Listing 18.5: Displaying the Selected Folder’s Subfolders and Files

Private Sub FoldersList_DoubleClick(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles FoldersList.DoubleClick Dim selDir As String

selDir = FoldersList.Text Dim dirs(), files() As String If selDir = “..” Then

dirs = Directory.GetDirectories(parentDir) files = Directory.GetFiles(parentDir)

Try

parentDir = Directory.GetParent(parentDir).FullName Catch exc As Exception

parentDir = Nothing End Try

Else

dirs = Directory.GetDirectories(selDir) files = Directory.GetFiles(selDir)

Try

parentDir = Directory.GetParent(selDir).FullName Catch exc As Exception

parentDir = Nothing End Try

End If

Dim dir As String FoldersList.Items.Clear()

If Not parentDir Is Nothing Then FoldersList.Items.Add(“..”)

End If

For Each dir In dirs FoldersList.Items.Add(dir)

Next End Sub

The code in this event handler displays the subfolders of the selected folder in the same ListBox control, replacing its current contents. The very first item added to the Folders ListBox control is the symbol for the parent directory, which moves you to the parent folder. The code examines the selected item and, if it’s “..”, it displays the subfolders of the parent folder. Otherwise, it displays the

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

824 Chapter 18 RECURSIVE PROGRAMMING

subfolders of the selected folder. The parentDir variable is declared on the Form level, and it’s used for storing the parent folder every time the user switches to a subfolder. It’s used to navigate back to the parent folder when the user clicks the parent folder symbol.

The error handler is actually part of the application’s logic. The code always attempts to retrieve the parent folder. If an error occurs, which means that the current folder has no parent folder, the parentDir variable is set to Nothing.

When the button on the form is clicked, the program starts scanning the selected folder recur- sively—it scans the files of the selected folder and then the files of all folders under the selected one. As it proceeds, it displays the name of the selected folder and the number of files and subfolders scanned so far on the form’s title bar. It also displays the path name of the folder being scanned on a Label control, above the FilesList control. Listing 18.6 is the code behind the Scan Selected Folder button, which initiates the recursive scanning.

Listing 18.6: Initiating the Recursive Scanning of a Folder

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click FilesList.Items.Clear()

If Button1.Text = “Scan Selected Folder” Then Button1.Text = “Stop Scanning”

interrupt = False Else

Button1.Text = “Scan Selected Folder” interrupt = True

Me.Text = “INTERRUPTED” End If

Try

If FoldersList.Text = “” Then Scanfolder(DrivesList.Text)

Else Scanfolder(FoldersList.Text)

End If

Catch scanException As Exception MsgBox(scanException.Message)

End Try

Button1.Text = “Scan Selected Folder” End Sub

First, this code clears the contents of the ListBox, where the files will be displayed. Then it changes the caption of the button to Stop Scanning, so that users will have a chance to interrupt the scan of an entire volume. This code demonstrates a technique for interrupting a recursive process. We set the interrupt variable to False when the scanning of the folder starts. If the user clicks the button while the scan is in progress, the interrupt variable is set to True. This variable is examined from within the recursive procedure, and you’ll see shortly how the program uses it to interrupt the process.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SCANNING FOLDERS RECURSIVELY 825

The last If…End If clause initiates the scanning of the selected folder. If a folder name was selected in the FoldersList control, this is the folder that’s scanned recursively. If not, it attempts to scan recursively the selected drive. The appropriate error handler will display a message, should the user attempt to scan a drive that’s not ready or a folder that can’t be scanned successfully.

The recursive part of the application is the ScanFolder() subroutine, which is shown in Listing 18.7.

Listing 18.7: The ScanFolder() Subroutine

Sub ScanFolder(ByVal currDir As String) If interrupt Then Exit Sub

Dim Dir As String

Dim File As String

Dim FI As FileInfo

Label1.Text = “scanning “ & currDir

For Each File In Directory.GetFiles(currDir) FI = New FileInfo(File)

FilesList.Items.Add(FI.Name & vbTab & FI.Length & vbTab & _ FI.CreationTime)

Next

countFiles += Directory.GetFiles(currDir).Length

Me.Text = “Scanning “ & FoldersList.Text & “ — Processed “ & countFiles & _ “ files in “ & countFolders & “ folders...”

For Each Dir In Directory.GetDirectories(currDir) countFolders += 1

Application.DoEvents()

ScanFolder(Dir) Next

End Sub

The ScanFolder() subroutine examines the value of the interrupt variable. If it’s True, it terminates. Because ScanFolder() is recursive, all pending instances of the subroutine must terminate. As soon as the current instance terminates, the most recently interrupted instance of the subroutine kicks in and completes its execution. Then the next instance kicks in, until all pending instances of the subroutine have terminated. Depending on the complexity of the calculations performed by the recursive procedure, it may take a few seconds until the process is terminated.

The code that fills the FilesFolder ListBox control is straightforward: First, the subroutine adds the names of the files in the folder passed as argument (this is the selected folder on the FoldersList control). In addition to the name, it displays the file’s size and its creation date. After scanning all the files in the current folder, the program goes through each subfolder of the selected folder. Each one of these folders must also be scanned in the same manner, so the subroutine calls itself, passing the name of a different folder each time. This process is repeated for all the subfolders of the selected folder, at any depth.

The ScanFolder application is an interesting example of recursive programming, but why duplicate functionality that’s already available for free? One reason is that the FileScan application is highly customizable. In the previous section, you learned how to count all the files of a given folder,

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

826 Chapter 18 RECURSIVE PROGRAMMING

including those in its subfolders. You can add many more useful features to the FileScan application that aren’t available through Windows Explorer. For example, you can implement a version of the Find utility that locates files and/or folders based on criteria that aren’t available through the Find utility. A limitation of the Find utility is that you can’t specify exclusion criteria. For instance, you can’t ask it to find all the files whose size exceeds 1 MB that aren’t system files (e.g., EXE, DLL) or images (e.g., BMP, TIF, JPG). But with FileScan, you can modify the application to handle all types of file selection or rejection criteria by designing the proper user interface. In the following section, we’ll modify the FileScan project a little, so that it maps a folder on a RichTextBox control.

VB.NET at Work: The FolderMap Project

Here’s another customization idea for the FileScan application: Have you ever had to prepare a hard copy of your hard disk’s structure? (If you ever have to submit the contents and structure of an entire CD to a publisher, this utility will save you a good deal of work.) As far as I know, there is no simple way to do it. However, you can easily modify the FileScan application so that it prints the contents of a folder, including its subfolders, to a text box. Figure 18.6 shows the FolderMap application, which does exactly that. The structure of a user-specified folder is printed on a RichTextBox control so that folder names can be displayed in bold and stand out. The contents of the text box can be copied and pasted in any other document or used in a mail message. In this example, I’m using the RichTextBox control to format the folder and filenames differently. The FolderMap project is also an interesting demonstration of creating formatted text from within your application with the help of the RichTextBox control.

Figure 18.6

The FolderMap application generates a text file with the structure of any given folder.

The code behind the Map Selected Folder button is identical to the code of the Scan Now button of the FileScan project, and it’s shown in Listing 18.8. It calls the ScanFolder() subroutine once, passing the name of the folder to be mapped.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

SCANNING FOLDERS RECURSIVELY 827

Listing 18.8: The Map Selected Folder Button

Private Sub bttnMapFolder_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles bttnMapFolder.Click RichTextBox1.Clear()

Me.Cursor = System.Windows.Forms.Cursors.WaitCursor countFiles = 0

countFolders = 1 Try

ScanFolder(FoldersList.SelectedItem) Catch scanException As Exception

MsgBox(scanException.Message) End Try

Me.Cursor = System.Windows.Forms.Cursors.Default End Sub

This code isn’t new to you. Let’s examine the code of the ScanFolder() subroutine, which is shown in Listing 18.9. It’s based on the ScanFolder() subroutine of the previous example, with the exception of the lines that format and display the filenames on the RichTextBox control at the bottom of the form. The subroutine prints the name of the current folder in bold. Then, it goes through the files in the current folder first and prints them on the RichTextBox control. All filenames are indented by a few spaces to the right, and they’re printed in regular font. The last For Each…Next loop in the subroutine goes through the subfolders of the current folder and calls the ScanFolder() subroutine for each one.

Listing 18.9: The Revised ScanFolder() Subroutine

Sub ScanFolder(ByVal currDir As String) Dim Dir As String

Dim File As String RichTextBox1.SelectionFont = boldFont RichTextBox1.AppendText(currDir & vbCrLf)

For Each File In System.IO.Directory.GetFiles(currDir) RichTextBox1.SelectionFont = textFont RichTextBox1.AppendText(“ “ & File & vbCrLf)

Next

countFiles += System.IO.Directory.GetFiles(currDir).Length

Me.Text = “scanned “ & countFiles & “ files in “ & countFolders & _ “ folders...”

For Each Dir In System.IO.Directory.GetDirectories(currDir) countFolders += 1

Application.DoEvents()

ScanFolder(Dir) Next

End Sub

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

828 Chapter 18 RECURSIVE PROGRAMMING

At the beginning of the program, the variables boldFont and textFont are declared as follows:

Dim boldFont As New Font(“Verdana”, 11, System.Drawing.FontStyle.Bold)

Dim textFont As New Font(“Verdana”, 9, System.Drawing.FontStyle.Regular)

The code switches the RichTextBox control to bold font for printing folder names and back to regular font for printing filenames. The actual folder and filenames are appended to the existing contents with the control’s AppendText method.

While the application scans the selected folder, the pointer’s icon is switched to an hourglass, indicating that the process will take a while. Depending on the total number of files under the folder you’re mapping, the program may take a while to complete. Normally, you will create a printout of a folder with a few dozen, or even a few hundred, files. If you attempt to map the Program Files folder after the installation of Visual Studio, you will have to wait for a few minutes. Scanning the folders isn’t a slow process, but as the number of text lines in the RichTextBox increases, it takes more and more time to add new lines to the control.

An alternative is to append the filenames to a disk file rather than a memory variable, then open the file and read its contents into the RichTextBox control. Or, if you don’t care about displaying folder names in bold, you could abandon the RichTextBox control and use a TextBox or a ListBox control. You could also use a StringBuilder variable to store all the folder and filenames in a really long variable and then display this string on the TextBox control. I think the benefit of richly formatting the folder structure offsets the less-than-optimal execution speed, as long as the size of the folder you want to map is reasonable.

Further Customization

Another customization idea is to process selected files with a specific application. Suppose your DownLoad folder is full of ZIP files you have downloaded from various sources. Unzipping these files into the DownLoad folder would be a disaster. Ideally, you should create a separate folder for each ZIP file, copy a single ZIP file there, and then unzip it. You can do this manually or you can let a variation of the FileScan application do it for you. All you need is a small program that creates the folder, moves the ZIP file there, and then unzips it with PK_UNZIP (of course, any zipping/unzipping utility will work in a similar manner.) You could even write a DOS batch file to process the ZIP files with the following statements:

md c:\Shareware\%2

copy %1 c:\Shareware\%2\ del %1

pkunzip c:\Shareware\%1

Tip A batch file is a program that can be started with the Shell function. To start the PK_UNZIP application from within Visual Basic, use a statement like Shell(“pkunzip c:\zipfiles\*.ZIP”).

If this batch file is named MVFiles.bat, you can call it with two arguments:

MVFILES CuteUtility.zip CuteUtility

The first argument is the name of the ZIP file to be moved and unzipped, and the second argument is the name of the folder where the ZIP file will be moved and unzipped. You can modify the FileScan

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com