微軟風格的Explore
發表于:2007-07-14來源:作者:點擊數:
標簽:
盡管Visual Basic標準控件DriveListBox、DirListBox和FileListBox在一些環境中能很好使用,但若想顯示類似 Windows Explorer的文件夾結構就顯得力不從心了。這時就必須使用TreeView控件,因為它為文件、文件夾和驅動器顯示提供了完善的 解決方案 ,并且系統
盡管Visual Basic標準控件DriveListBox、DirListBox和FileListBox在一些環境中能很好使用,但若想顯示類似
Windows Explorer的文件夾結構就顯得力不從心了。這時就必須使用TreeView控件,因為它為文件、文件夾和驅動器顯示提供了完善的
解決方案,并且系統的FileSystemObject對象能有效地獲得主機的目錄信息。
本文將介紹如何使用FileSystemObject將指定的目錄或全部的驅動器與TreeView控件相結合,以及如何消除當驅動器全部文件列表填充TreeView時所遇到的
缺陷。作為技巧,文中還討論如何用SendMessage()的API函數來對TreeView內容的快速清除。事實上,這是一種最快捷的方法,它也可用于其他控件中。
關于FileSystemObject對象庫
眾所周知,FileSystemObject對象庫是在Visual Basic Scripting Edition 2.
中發布的,它包括一系列用于驅動器、文件夾和文件的操作。為了能在Visual Basic中使用FileSystemObject,必須引用SCRRUN.DLL文件以便能使用它的屬性、方法和事件。在
VB工程中添加SCRRUN.DLL的引用,是通過選擇"Project"(工程)->"References"(引用)命令,然后選定"Microsoft Scripting Runtime"項來進行的。一旦引用后,就可用FileSystemObject方法獲得文件夾名稱以及每個文件夾下的文件名。
FileSystemObject允許用戶在當前磁盤中進行文件操作,如復制、刪除以及其他文件夾、文件操作等。本文著重討論與文件夾結構相關的方法,即如何獲得文件夾和文件名稱。
創建示例
示例創建的過程是這樣的,首先在默認的表單中添加必要的控件;然后添加代碼,用來將目錄內容添加在TreeView中;最后,利用SendMessageLong() API函數添加一個過程,用來清除TreeView中的內容。
創建用于文件和文件夾的表單
首先,創建一個新的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調用該過程時,它處理被選中的節點,理論上包含能建立其他文件結構的目錄?,F在我們在這里提供相應的代碼,用來添加到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中。
快速清除TreeView控件中的內容
在這個示例中,用戶可以通過單擊命令按鈕在TreeView控件中添加文件夾和文件列表。在實際的應用程序中,我們應該可以讓用戶通過相應的控件選擇不同的開始目錄。因此,在用新項目填充TreeView控件之前,還必須刪除該控件中的原有內容。通常,我們可以使用遞歸的方式來遍歷TreeView控件中的所有節點,然后刪除相應的項目,但是卻要花費不少時間,尤其是當TreeView控件含有更多的項目和嵌套時。一個最簡單快捷的方法,是使用SendMessage() API函數,在一次循環后就可將TreeView控件的內容刪除。
為了說明它的工作原理,我們返回到默認表單的設計視圖中,再在表單中添加一個命令按鈕,打開表單的代碼窗口,在聲明部分添加如Listing C所示的API聲明。
Listing C:
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()
?。emove 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
?。urn off redrawing first
SendMessageLong tvHwnd, WM_SETREDRAW, False,
?。emove 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,
End Sub
原文轉自:http://www.anti-gravitydesign.com