1 Attachment(s)
Re: Loop through folders with shell object.
I have made a little application which helps explore the shell automation and makes use of the shell32.FolderItem and shell32.Folder objects.
A TreeView is filled starting from the Desktop, like an explorer tree would be, although I put in all the non-folders (files/items), too.
Clicking on a node in the tree will display infos in the left TextBox.
It will display all interesting info including those obtained by GetDetailsOf() method of the (parent) folder. A Detail is only displayed, if the returned string is not "".
It appears that some objects like a local drive may return up to 40 DetailsOf.
It is a bit bewildering what comes back from this method.
If the object is a diskdrive, Detail #0 will be literally "Name", Detail #1 will be literally "Size" whereas when you go into the drive and click a file, #0 will be the real name of the file and #1 will be its real size.
I'm still a little confused about using the shell objects, because of the localized DetailOf strings, but it seems they are a rather good alternative to the FSO.
Look how the tree is able to browse all Desktop Namespaces, including Nethood, Control Panel, etc. You cannot do this with FSO.
If you look at the code, a few simple changes can make the tree only display folders like a real explorer tree, so as you could implement a ListView to display the files in that folder.
Well it is just a start-off to play a little with shell32 stuff.
Re: Loop through folders with shell object.
Quote:
Originally Posted by
DataMiser
Apparently the function wants a variant or a pointer as a parameter. If memory serves a variant datatype is in effect a pointer to the memory location of the given data.
Trim() returns a variant where-as Trim$() returns a string This would explain the reason it worked with Trim() but not with a string.
If the function wants a pointer, why dont give it what it wants? Try VarPtr() with a String variable. This way there would be no need of using the Variant DataType.
Re: Loop through folders with shell object.
Dagnarus,
If you had read my posts on the subject, you would have seen that I tried both varptr and strptr and both did not work... and it was also I who found that the trim and the variant solutions...
Re: Loop through folders with shell object.
I don't know if you guys got any further with the shell method, or even care but after a lot of trial and error I just had to share:
The FolderItems3 collection has a filter property: Filter(grfFlags As Long, bstrFileSpec As String)
The FileSpec is the familiar *.??? stuff and you can use multiple specs separated by a ; .
The grfFlags is a combo of the values in the SHCONTF enumeration.
So: if clItems is a FolderItems3
Code:
clItems.Filter SHCONTF_FOLDERS Or SHCONTF_INCLUDEHIDDEN, "*"
For Each oFolderItem In clItems
If oFolderItem.IsFileSystem Then
colFolders.Add oFolderItem.GetFolder
numDirs = numDirs + 1
End If
Next oFolderItem
clItems.Filter SHCONTF_NONFOLDERS Or SHCONTF_INCLUDEHIDDEN, "*"
For Each oFolderItem In clItems
numFiles = numFiles + 1
Next oFolderItem
I am using this in a non-recursive procedure, I am also scanning the entire drive. For some reason the folderitem isfolder test calls zips and cabs folders. The filter function seems to weed those out when done this way. The snippet I posted gives me the same number of files and folders that I get when I select everything in the C:\ folder and choose properties, so I think these are the proper results.
In order to use this method you need to run through folders and files separately. It's not a speed hit though, because each item is still only done once. It also gives you the chance to use the FileSpec argument.
Hope someone finds this helpful.
By the way: SHCONTF_INCLUDEHIDDEN = &H80, SHCONTF_NONFOLDERS = &H40 , SHCONTF_FOLDERS = &H20
Re: Loop through folders with shell object.
Helpful it is. I was already wondering about how to implement pattern matching.
You could possibly do it in one pass:
Code:
clItems.Filter SHCONTF_FOLDERS Or SHCONTF_NONFOLDERS Or SHCONTF_INCLUDEHIDDEN, "*"
For Each oFolderItem In clItems
If oFolderItem.IsFileSystem Then
If oFolderItem.IsFolder Then
colFolders.Add oFolderItem.GetFolder
numDirs = numDirs + 1
Else
numFiles=NumFiles+1
End If
End If
Next oFolderItem
Re: Loop through folders with shell object.
Hi Wof,
I can't remember why I did it in 2 passes. I think there was a reason, but you never know! I'm playing with trying to enumerate all the files on a drive and get as much information as possible without actually having to calculate it myself.
Anyhow, here's the latest thing I've learned:
' if afolder refers to a drive ( root folder ) then
Code:
for i= 0 to 40 :debug.print afolder.GetDetailsOf(afolder.Self,i): next
' prints the names ( column headings ) of the detail
That was obviously run from the immediate window.
I'm not sure the shell object is really going to make much of a difference with all of its quirks. The compressed file/folder issue has already been mentioned. I noticed another issue since I use a database to store my results. The shell seems to treat the Temporary Internet Files folder differently: it tries to put all the files in the folders(?) under it into the same folder so you wind up with files in the Temporary Internet Files folder that have the same name. The other methods keep them separated.
I'll post back later with more.
Len
Re: Loop through folders with shell object.
Hi again,
I have to correct my last post. It turns out that you can get the names for getdetailsof from any folder. As long as the folderitem parameter isn't a folderitem the function will return the detail name. so:
? ofolder.GetDetailsOf("xy",1) prints 'Size'
Secondly, there was a reason I did it separately. Enumerating the files in a single pass produces a resultset that includes zip and cab files as folders and the separate loop method doesn't. Kind of dense of me to forget that since that was the entire reason for the post in the first place.
Anyway, back to the speed issue and the shell object.
Code:
Option Explicit
Dim TotalFiles As Long, TotalFolders As Long
Dim tmStart As Single, tmFin As Single, tmDif As Single
Const SHCONTF_INCLUDEHIDDEN = &H80, SHCONTF_NONFOLDERS = &H40
Const SHCONTF_FOLDERS = &H20
Private Sub Command1_Click()
List1.Clear
TotalFiles = 0: TotalFolders = 0
List1.Visible = False
If Text1 = "" Then Text1 = "c:\"
tmStart = Timer
SFindFiles (Text1)
tmFin = Timer
List1.Visible = True
tmDif = tmFin - tmStart
Label1.Caption = "DIRS: " & TotalFolders & " FILES: " & TotalFiles & " " & tmDif
Debug.Print "DIRS: " & TotalFolders & vbTab & "FILES: " & TotalFiles & " " & tmDif
End Sub
Private Sub SFindFiles(aPath As String)
Dim oShell As New Shell
Dim oFolder As Folder3
Dim oFolderItem As ShellFolderItem
Dim clItems As FolderItems3
Dim ndir As Long, nfiles As Long
Dim DirName As String
Dim dirNames() As String
Dim aLine As String
Dim i As Integer
ndir = 0
Set oFolder = oShell.NameSpace(aPath)
Set clItems = oFolder.Items
clItems.Filter SHCONTF_FOLDERS Or SHCONTF_NONFOLDERS Or SHCONTF_INCLUDEHIDDEN, "*"
For Each oFolderItem In clItems
If oFolderItem.IsFileSystem Then
If oFolderItem.IsFolder Then
ReDim Preserve dirNames(ndir)
dirNames(ndir) = oFolderItem.Path
ndir = ndir + 1
Else
nfiles = nfiles + 1: TotalFiles = TotalFiles + 1
aLine = oFolderItem.Path
List1.AddItem aLine
End If
End If
Next oFolderItem
TotalFolders = TotalFolders + ndir
If ndir > 0 Then
For i = 0 To ndir - 1
SFindFiles (dirNames(i))
Next i
End If
End Sub
Private Sub Command2_Click()
List1.Clear
TotalFiles = 0: TotalFolders = 0
List1.Visible = False
If Text1 = "" Then Text1 = "c:\"
tmStart = Timer
S2FindFiles (Text1)
tmFin = Timer
List1.Visible = True
tmDif = tmFin - tmStart
Label1.Caption = "DIRS: " & TotalFolders & " FILES: " & TotalFiles & " " & tmDif
Debug.Print "DIRS: " & TotalFolders & vbTab & "FILES: " & TotalFiles & " " & tmDif
End Sub
Private Sub S2FindFiles(aPath As String)
Dim oShell As New Shell
Dim oFolder As Folder3
Dim oFolderItem As ShellFolderItem
Dim clItems As FolderItems3
Dim ndir As Long, nfiles As Long
Dim DirName As String
Dim dirNames() As String
Dim aLine As String
Dim i As Integer
Set oFolder = oShell.NameSpace(aPath)
ndir = 0
Set clItems = oFolder.Items
clItems.Filter SHCONTF_FOLDERS Or SHCONTF_INCLUDEHIDDEN, "*"
For Each oFolderItem In clItems
If oFolderItem.IsFileSystem Then
ReDim Preserve dirNames(ndir)
dirNames(ndir) = oFolderItem.Path
ndir = ndir + 1
End If
Next oFolderItem
TotalFolders = TotalFolders + ndir
clItems.Filter SHCONTF_NONFOLDERS Or SHCONTF_INCLUDEHIDDEN, "*"
For Each oFolderItem In clItems
nfiles = nfiles + 1: TotalFiles = TotalFiles + 1
aLine = oFolderItem.Path
List1.AddItem aLine
Next oFolderItem
If ndir > 0 Then
For i = 0 To ndir - 1
S2FindFiles (dirNames(i))
Next i
End If
End Sub
The code is, hopefully, functional. I'm not making any promises, though. Dump the code into a form module. You need to reference shell32.dll. 2 buttons, a textbox, a listbox, and a label.
When run you can enter a path in the listbox and it will show the files and paths in the listbox. The label and immediate window will show total folders and total files as well as time in seconds ( as long as it isn't near midnight). Command1 runs the single pass... you get the idea.
If you don't want to go through all that here are the results for my c:\drive (inactive XP, FAT):
DIRS: 3678 FILES: 17859 2.390625 FindFirst/Next
DIRS: 3712 FILES: 17818 13.03125 SFindFiles (compressed files counted as folders)
DIRS: 3678 FILES: 17856 13.01563 S2FindFiles
I don't know what 3 files are missing from the third method yet. But don't get your hopes up. It appears your results depend on the folder options settings.
DIRS: 3628 FILES: 14315 16.70313
That last one is with the don't show/hide options checked. At least we still get the file extensions whether they're shown or not.
The shell methods might be better if you need the filter functions, especially if you're only looking for normal files in normal places. Since I think the original question was one of speed I have to vote for FindFirst. Maybe I'm missing something, let me know if you can speed it up.
Feel free to use the code. It doesn't do much and it needs some checks, but it works.
Len
Re: Loop through folders with shell object.
How about ATTRIB /? That should return everything
Re: Loop through folders with shell object.
OK. I won't bother coding that though. I'm going to go out on a limb here and guess that method would be way slower, and you only get the path and attribute string. The dir command would at least provide more data.
The whole point of this exercise was to find faster ways of doing things. Using the shell object seems to be slower than the FindFirst functions.
The code I posted was to show that the shell object works differently than the other methods. FindFirst and the FSO find the number of folders and files that you would expect. The shell object produces DIFFERENT results.
I was hoping someone would actually run that code and let me know what the results. I guess I could attach the project so you don't have to build you're own form?
Len
Re: Loop through folders with shell object.
Why not. Please post it.
I had posted a benchmark comparing FSO, API and DIR$() and found API and DIR nearly the same and much faster'n FSO. Only Shell objects could go faster as I found then.
However, as you have stated correctly, the shell objects might produce results you did not expect, like treating zip and cab files as folders. Also it will follow all these user specific paths, i think, like "My Documents", differently.
Maybe we stick to API when scanning for files and folders. :) But I was curious what features the shell objects would provide on top of that. That's why I startetd to research and play a little.
I'd appreciate to look at your code and run it over my test folders to get a timed comparable result.
Re: Loop through folders with shell object.
Hi Wof,
*** PLEASE IGNORE BELOW ***
Sorry, I meant to post this project sooner, but then my brain exploded! Long story short: I didn't save a copy of my FindFirst API loop version, so I decided to steal yours from your earlier post. I thought it was the same as the one I had used in my tests. Then I noticed that you used GetFileAttributes instead of WFD.dwFileAttributes . I added the declare to run yours the way you wrote it. It cataloged my c drive in about 4 seconds. I knew my recursive version was faster so I cut and pasted it in to compare. My version, using dwFileAttributes ran at nearly the same speed yours did. Then I started playing with the attributes call. It seems like one version affects the other. That was when my brain exploded.
I left the commented lines in the two Api subs, try switching them around and see how it affects the timing. It would be really nice if you could post back your observations.
It seems like I'm going to have to do my comparisons in separate projects. I don't see why one function can affect the others performance.
*** PLEASE IGNORE ABOVE ***
*** CORRECTED PROJECT IN NEXT POST ***
About the project: It's not good coding practice, but I wanted the functions to be fairly self-sufficient so they all dim their own variables. The command_click events are identical except for the function call. There is nothing in here to trap errors or keep you from clicking buttons repeatedly, no cursor changing, no object clean-up. In short this is functional code but I wouldn't suggest using it without fixing it up.
You can type a pathname into the textbox if you don't want to do a whole drive. It starts with C:\ as a default and will also use c:\ if the box is empty.
BTW: The results I posted earlier were second run data: The initial run for the API versions is about 12 seconds.
LEN
1 Attachment(s)
Re: Loop through folders with shell object.
Hello again,
Completely Ignore my last post. I'm insane. It helps if you actually call the right function. Here's the corrected project.
At least I figured it out before anyone else pointed out how stupid I am.
Hope you didn't spend too much time on that, sorry.
LEN