创建用于文件和文件夹的表单
首先,创建一个新的Visual Basic工程;然后,将默认的表单标题设置为"Exploring the Windows' directory",再在表单上添置一个TreeView控件和一个命令按钮控件,保留默认名称。最后,将命令按钮的标题设置为"Fill TreeView"。
声明两个重要的变量
在这一步中,我们需要为表单添加一些代码。首先,需要安装SCRRUN.DLL的引用,从菜单栏上选择"Project"(工程)->"References"(引用)命令,在弹出的对话框中,选中"Microsoft Scripting Runtime"项。单击[OK]按钮关闭对话框。然后,在表单中右击鼠标,从弹出的快捷菜单中选择"View Code"命令,在声明部分添加下列语句:
Const DEFAULT_DRIVE = "C:\"
Dim FSO As New Scripting.FileSystemObject
这里为简单起见,我们用一个常量来指定建立的目录树的默认驱动器。当然,最好能让用户对这个值作出选择。
用户或许想知道为什么在表单层中声明一个FSO变量,而不是在实际读取文件信息的过程中声明。这完全是一次偶然机会的心得,因为在许多好的程序中,通常允许用户用多种方式处理选择的文件。在表单层范围内定义一个FSO变量,其目的是可以处理所有其他FileSystemObject成员,而不必每次都有定义一个对象变量。
基于这种考虑,在表单被调用时应对FSO变量进行初始化,而当表单关闭后,该变量应被清除。这项工作的最终完成是通过下面的代码:
Private Sub Form_Load()
Set FSO = New FileSystemObject
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set FSO = Nothing
End Sub
添加命令按钮Click()的事件
下面来添加代码用于TreeView控件的填充,这个过程是通过命令按钮的Click()事件来处理的,具体的代码如Listing A所示。代码中,一开始是定义几个变量,然后判断TreeView控件是否已经包含默认驱动器下的节点内容。若是,则不需要重新填充TreeView控件。若不是,则根据已知的节点创建新的节点变量,并连同TreeView控件一起调用PopulateTreeView()过程。显然,这个定制的子程序需要两个参数,一个是要填充的TreeView对象,另一个是要创建的目录树的父节点。PopulateTreeView()过程将在后面进行详细讨论。
Listing A:
Private Sub Command1_Click()
Dim strRootDir As String
Dim ndRoot As Node
strRootDir = UCase(DEFAULT_DRIVE)
On Error Resume Next
Set ndRoot = TreeView1.Nodes(strRootDir)
On Error GoTo
If ndRoot Is Nothing Then
Set ndRoot = TreeView1.Nodes.Add(, , strRootDir, _
strRootDir)
ndRoot.Sorted = True
PopulateTreeview TreeView1, ndRoot
End If
Set ndRoot = Nothing
End Sub
消除长时间等待的缺陷
在这一步中,我们需要考虑到定制的过程必须能从默认的目录中读取完整的文件信息并能填充到TreeView控件中。在磁盘大小越来越大的今天,这种方式却是非常不明智的。为了能得到目录的文件列表,Visual Basic必须在给定的文件夹下不断循环搜索所有的文件和子文件夹,当发现一个另外的子文件夹时,又要开始同样的循环,直到最后一个文件被找到为止。用这种方式读取全部的目录信息必然使程序挂起许多分钟。如果假设读取的磁盘,不仅仅是主机上的,而且还有网络上的,那么Visual Basic可能要耗上几个小时的时间。
作为技巧,我们在获取文件信息采用"即需即用"的原则。也就是说,只将在用户选定的文件夹的目录树的信息填充到TreeView控件中。是啊,一个从来就没有被选定的内容又怎么能去填充呢?
也就是说,在TreeView控件中,只有当用户展开一个文件夹项时,代码才填充该文件夹下的目录信息。基于这种思想,在TreeView控件的Expand()事件中必须调用定制的PopulateTreeView()过程,如下面的代码:
Private Sub TreeView1_Expand(ByVal Node _ As MSComctlLib.Node)
PopulateTreeview TreeView1, Node
End Sub
这样,当Visual Basic调用该过程时,它处理被选中的节点,理论上包含能建立其他文件结构的目录。现在我们在这里提供相应的代码,用来添加到PopulateTreeView()过程中。
增加PopulateTreeView()程序
这一步,我们准备添加定制的PopulateTreeView()过程。前面已提及,我们的这个子程序是通过其中一个父节点参数创建相应的目录结构。但是,我们每次向TreeView添加的目录应该包含多少层呢?如果代码在所有的嵌套子文件夹中不断循环,这必然有潜在的缺陷,因为获取所有的硬盘文件结构信息必然需要大量的时间。另一方面,我们的代码也不能简单地在一个过程查找当前子文件夹下的文件信息。
为了能让TreeView控件显示子目录节点旁的"+"、"-"号,我们的代码必须填充其子项内容。但实际上,尽管用户可能仅仅选择显示C驱动器的目录结构,但我们的子程序还必须能添加所有可见的子目录内容。幸运的是,这第二层的内容仍然可以用代码去填充到TreeView控件中去。Listing B 是定制的PopulateTreeView()过程的全部代码:
Listing B:
Sub PopulateTreeview(trvw As TreeView, ndParent As Node)
Dim fldrParent As Folder
Dim fldrChildren As Folders
Dim FoundFile As Files
Dim FoundDir As Folder
Dim FoundFiles As Files
Dim iDir As Integer, iFile As Integer
Dim ndChild As Node
Dim blnHasChildren As Boolean
Set fldrParent = FSO.GetFolder(ndParent.Key)
Set fldrChildren = fldrParent.SubFolders
Set FoundFiles = fldrParent.Files
blnHasChildren = CBool(ndParent.Children)
With trvw
For Each FoundDir In fldrChildren
If blnHasChildren Then
Set ndChild = .Nodes(FoundDir.Path)
Else
Set ndChild = .Nodes.Add(ndParent.Key, _ tvwChild, FoundDir.Path, FoundDir.Name)
ndChild.Sorted = True
End If
If ndParent.Expanded Then
PopulateTreeview trvw, ndChild
End If
Next FoundDir
For Each FoundFile In FoundFiles
On Error Resume Next
Set ndChild = .Nodes(FoundFile.Path)
On Error GoTo
If ndChild Is Nothing Then
.Nodes.Add FoundFile.ParentFolder.Path, _tvwChild, FoundFile.Path, FoundFile.Name
End If
Next FoundFile
End With
Set ndChild = Nothing
Set fldrParent = Nothing
Set fldrChildren = Nothing
Set FoundFiles = Nothing
End Sub
在不工作中填充TreeView的子项
要查看这个定制过程的结果,按[F5]来运行这个工程。当Visual Basic显示默认的表单时,单击[Fill TreeView]按钮。此时,产生Click()事件,并运行PopulateTreeView()过程,处理TreeView1和ndRoot节点(此时是C:)。然后,该过程得到一个基于ndParent参数的文件夹对象以及该文件夹下的子文件夹。这时,代码循环所有的子文件夹。如果代码查找到父节点存在相应的子项内容,则代码为该父节点添加子文件夹,然后该过程为TreeView项目添加一个子节点。如果当前父节点在TreeView中没有子项,但是与父节点相关的父 文件夹包含子文件夹,则代码向TreeView添加新的文件夹名。
然后,该过程判断父节点是否展开。若是,则代码添加相应的子文件夹内容,这样在TreeView层次上出现"+"、"-"号。为了达到这一点,后面调用的PopulateTreeView()就是用子节点来处理的。在第二次调用这个过程时,由于子节点没有展开,因此PopulateTreeView()过程不会再被调用。最后,该过程在该文件夹下循环所有的文件,并将其填充到TreeView中。
Const TV_FIRST As Long = &H11
Const TVM_GETNEXTITEM As Long = (TV_FIRST + 1)
Const TVM_DELETEITEM As Long = (TV_FIRST + 1)
Const TVGN_ROOT As Long = &H
Const WM_SETREDRAW As Long = &HB
Private Declare Function SendMessageLong Lib "user32" _
Alias "SendMessageA" (ByVal hWnd As Long, ByVal msg _
As Long, ByVal wParam As Long, ByVal lParam As Long) _
As Long
然后,添加Command2的Click()事件,并增加下列代码:
Private Sub Command2_Click()
'Remove all items from the Treeview
ClearTreeView TreeView1.hWnd
End Sub
最后,建立一个ClearTreeView()子程序,如Listing D所示。
Listing D:
Private Sub ClearTreeView(ByVal tvHwnd As Long)
Dim lNodeHandle As Long
'Turn off redrawing first
SendMessageLong tvHwnd, WM_SETREDRAW, False,
'Remove each node in the treeview
Do
lNodeHandle = SendMessageLong(tvHwnd, _
TVM_GETNEXTITEM, TVGN_ROOT,
)
If lNodeHandle Then
SendMessageLong tvHwnd, TVM_DELETEITEM,, _lNodeHandle
Else
Exit Do
End If
Loop
SendMessageLong tvHwnd, WM_SETREDRAW, True,
FSO是一个描述目录/文件的对象模型结构。使用方法见下面的例子。
'定义一个FSO对象,两个目录对象
Dim fso As FileSystemObject
Dim fodWindow As Folder
Dim fodSubWindow As Folder
'生成一个新的FSO对象
Set fso = New FileSystemObject
'生成指向WINDOWS目录的目录对象
Set fodWindow = fso.GetFolder(("C:\WINDOWS"))
'显示windows目录下的所有子目录的名称
For Each fodSubWindow In fodWindow.SubFolders
Debug.Print fodSubWindow.Path
Next