File sorting in explorer windows

The place for programming-related topics.
Post Reply
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

File sorting in explorer windows

Post by WoF »

Hi out there,

I could do with some help or info with the following problem:
Assume, a folder contains the following files: img.1.tif, img.9.tif, img.10.tif, img.99.tif and the sorting option is by name.
With former Windows (98, NT) you get the listed order:
img.1.tif
img.10.tif
img.9.tif
img.99.tif

This is because the sorting is done alphabetically and the digit 1 is lower than 9.
In a Windows XP explorer window you get a sorted list like:
img.1.tif
img.9.tif
img.10.tif
img.99.tif

It seems, that the sorting mechanism recognizes that there is a number as part of the name, computes it's value and sorts with ascending values (not alphabetically). This certainly is preferable and would also come in handy in my VisualBasic project, but the problem is, it doesn't:

I'm using the ExTvw Tree to browse and determine the full path of a folder.
Then I have a FileSystemObject to read out the names of image files to fill a normal ListView control. I'm doing that like

Dim myFile as File, myFolder as Folder
For Each myFile in myFolder.Files
If IsAnImage(myFile.Name) then myListView.ListItems.Add myFile.Name
next

The sort property of the ListView is false, so as to keep the sequence in which the names arrive provided by the FileSystemObject. But the 'for each' loop gets the files always in an alpha-order that is 1,10,9,99 even if the explorer window shows 1,9,10,99. If I needed alpha-sort, I could switch on the sort property of the ListView but I would rather have that more intelligent sorting.

I'm at my wits end with that one. Can anybody help or give a hint?

Thanks for reading
WoF
User avatar
TiKu
Administrator
Administrator
Posts: 832
Joined: 28 Sep 2004, 21:10
Location: München
Contact:

Post by TiKu »

Hi,

do you know the shell interfaces like IShellFolder, IEnumIDList and so on? That's what I'm using in ExplorerTreeView and what Windows Explorer is using. It's less comfortable than FileSystemObject, but is far more powerful.

Each shell item (files, folders, virtual items and so on) has an associated pIDL. It's the memory address of a list of SHITEMID structs. PIDLs work like paths: There are relative ones and absolute ones (starting at the Desktop) and each segment (i. e. each SHITEMID struct) identifies one of the item's parent items. The SHITEMID struct stores things like the item's name, size and so on.

Additionally, each folder (including virtual ones) implements the IShellFolder interface. Using IEnumIDList you can iterate an IShellFolder's content. So, using pIDLs, IShellFolder and IEnumIDList you can browse the whole shell.
IShellFolder also has a method 'CompareIDs()' for pIDL-comparison. In this method's first argument, you can specify the sorting rules (sort by filesize, by name, by filetype, ...). And this method sorts exactly like Windows Explorer, i. e. it detects numeric values.

I don't know whether you can squeeze this sorting style out of FileSystemObject (apart from implementing your own sort algorithm which would parse strings for numbers). Let me know if I shall write a small sample project demonstrating how to fill a listview using IShellFolder and pIDLs and how to sort this listview.

TiKu
Crunching for Fab36_Folding-Division at Folding@Home. Join Fab36/Fab30! - Folding@Home and BOINC
Boycott DRM! Boycott HDCP!
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

Post by WoF »

Hi TiKu,
erstmal vielen Dank fuer Deine prompte Reaktion :mrgreen: , aber vielleicht sollte ich in Englisch fortfahren.

Yes, I had a look on these shell stuff like IShellFolder, SH(ell)ITEMID and the pIDL and got somewhat confused by some sample code which really was very un-VB like and had to use MoveMemory routines (gulp). I found these rather complicated to use in VB.

So I looked for a replacement for the FileSystemObject, trying the shell32.Folder and FolderItem. I seem to reach a solution now, because the shell32.Folder provides its FolderItems in the desired sorting sequence. These objects are not quite so easy to use as the FileSystemObject, but it turns out ok.

I'd like to know more about the IShellFolder interface and how to use it in VB. Maybe it's even better than the way I'm going now. Thanks for your great offer to show me a piece of sample code using the pIDLs. If it's not to much work for you, I'd like to see how to fill a listview with the contents of a folder.

As I said before, I'm using your great ExplorerTreeView (phantastic piece of work, really) to navigate through the desktop items. Is there a short and easy way of getting the shell32.FolderItem which belongs to the currently selected item of the ExTVw? For the moment I wrote a rather sad recursive routine which has to propagate back to the desktop to get the first available FolderItem and then drop back, looping through the folders. I should think, there is an association from the IShellFolder to the FolderItem...

I'm starting now with the routine to fill the listview looping through the FolderItems in the Folder of the current FolderItem, but would be glad to continue correspondence.

Many thanks so far :D

WoF
User avatar
TiKu
Administrator
Administrator
Posts: 832
Joined: 28 Sep 2004, 21:10
Location: München
Contact:

Post by TiKu »

WoF wrote:Yes, I had a look on these shell stuff like IShellFolder, SH(ell)ITEMID and the pIDL and got somewhat confused by some sample code which really was very un-VB like and had to use MoveMemory routines (gulp). I found these rather complicated to use in VB.
It looks more complicated than it actually is.
WoF wrote:I'd like to know more about the IShellFolder interface and how to use it in VB. Maybe it's even better than the way I'm going now. Thanks for your great offer to show me a piece of sample code using the pIDLs. If it's not to much work for you, I'd like to see how to fill a listview with the contents of a folder.
I've attached a sample project. It uses ExplorerListView Beta 1 (mainly because this allowed me to copy most code from an existing project) and ExplorerTreeView 1.10.0. The binary of ExplorerListView is located in the sample's bin folder. The tlb folder contains the typelibrary (with sourcecode) that declares IShellFolder and all the other interfaces.
Implemented features:
  • Icons view
  • Small Icons view
  • List view
  • overlay icons (using IShellIconOverlay)
  • context menus (using IContextMenu/IContextMenu2/IContextMenu3)
  • info tips (using IQueryInfo)
  • colorizing of encrypted/compressed items
  • item renaming
  • item sorting by name
Missing features:
  • Details view (using IShellDetails/IShellFolder2)
  • Tiles view (using IQueryAssociations, IPropertyUI and IShellFolder2)
  • Thumbnails view (using IExtractImage/IExtractImage2 and optionally IShellImageStore)
  • Thumbstrip view (using IExtractImage/IExtractImage2 and optionally IShellImageStore)
  • item grouping (using ICategoryProvider and ICategorizer)
  • drag'n'drop
  • automatic update on changes (like file deletions)
I use callbacks for things like item text, icon, overlay and so on. So the FillListView method looks somewhat strange. The real work is done in lvwFiles_ItemGetDisplayInfo().
The code should work on Windows 2000 and above. However, I tested it on Windows XP SP2 only. Making the code compatible with Windows 9x/ME and NT4 should be relative easy.
I tend to write code without comments, so feel free to ask me if you don't understand the code or parts of it.
WoF wrote:As I said before, I'm using your great ExplorerTreeView (phantastic piece of work, really) to navigate through the desktop items. Is there a short and easy way of getting the shell32.FolderItem which belongs to the currently selected item of the ExTVw?
I hoped that shell32.FolderItem would be nothing more than a wrapper around IShellFolder, but looks like it's not that simple. I don't know how to initialize a shell32.FolderItem object with a given pIDL.

TiKu
Attachments
New ExplorerClone.zip
A very simple shell browser using pIDLs and the shell interfaces.
(389.83 KiB) Downloaded 641 times
Crunching for Fab36_Folding-Division at Folding@Home. Join Fab36/Fab30! - Folding@Home and BOINC
Boycott DRM! Boycott HDCP!
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

Post by WoF »

Hallo TiKu,

thankyouverymuch for the listing of your explorer clone. :D I didn't expect it to be such complex.
In time I will study it carefully to learn all about the IShellFolder interface. It's very interresting stuff but not trivial. I've not much knowledge about all that interface declaration language stuff and I'm always a bit concerned about putting code in my Programs, which I actually do not fully understand.

For my problem, now I thought I worked out a satisfying conventional and very short solution which may also be of interest for you, even if I was wrong with the sorting.
I wrote a function which will return the shell32.Folder for a given ExplorerTreeView Item. Unfortunately, I was erring with the sorting order: The collection of FolderItems in the Folder is presented in the same alfa-style like the FileSystemObject does.

Description:
By following a given Item's ParentItem, the function recursively propagates up to the "Desktop".
The Desktop's shell32.Folder is provided by the Shell object's NameSpace property.
On the way back it goes through the FolderItems by the name of the ExplorerTreeView-Items it has passed.

Code: Select all

Private Function GetTreeItemFolder(XTV As ExplorerTreeView, TreeItem As Long) As Shell32.Folder
' get the shell32.folder belonging to an ExplorerTreeView Item

  Dim par As Shell32.Folder       'parent folder
  Dim fit As Shell32.FolderItem   'a FolderItem for looping
  Dim pin as long                 'parent item number
  Dim nam As String               'item display name

' dim SHL As New Shell is done public in a module to produce a global shell reference

  pin = XTV.ItemGetParentItem(TreeItem)
  nam = XTV.ItemHandleToDisplayName(TreeItem)

  If pin < 0 Then
     Set GetTreeItemFolder = SHL.NameSpace(ssfDESKTOP)
  Else
     Set par = GetTreeItemFolder(XTV, pin) 'call recursively
     If Not par Is Nothing Then
        For Each fit In par.Items
            If LTrim(fit.Name) = nam Then _
               Set GetTreeItemFolder = fit.GetFolder: Exit For
        Next
     End If
  End If
End Function
It could be a nice and comfortable addition to ExplorerTreeView like XTV.ItemGetShellFolder(TreeItem) ;)

So my problem still persists.
I also thought about an own sorting routine, but since my program is supposed to handle large amounts of files (up to 30000) a VB routine could be somewhat slow, because the comparison based on numeric values within strings is also not so simple.

BTW: Does ExplorerTreeView use a Nodes object like the VB TreeView implies?
If so, is it possible to obtain access to it?
Let's keep in touch.

Thanks again for your effort :mrgreen:

WoF
User avatar
TiKu
Administrator
Administrator
Posts: 832
Joined: 28 Sep 2004, 21:10
Location: München
Contact:

Post by TiKu »

WoF wrote:It could be a nice and comfortable addition to ExplorerTreeView like XTV.ItemGetShellFolder(TreeItem) ;)
I'll think about it.
WoF wrote:So my problem still persists.
I also thought about an own sorting routine, but since my program is supposed to handle large amounts of files (up to 30000) a VB routine could be somewhat slow, because the comparison based on numeric values within strings is also not so simple.
If you're using a listview control that supports item sorting callbacks (i. e. the control doesn't compare 2 items on its own, but lets your app compare them instead), there may be a compromise: You could go on using the shell32 objects to fill the listview and use IShellFolder::CompareIDs to sort the items afterwards. Unfortunately you'd have to retrieve both items pIDL on each comparison which would probably slow down your app noticeably.
WoF wrote:BTW: Does ExplorerTreeView use a Nodes object like the VB TreeView implies?
No. It uses an API-created SysTreeView32 window. SysTreeView32 is Windows' native treeview class and it uses item handles instead of objects. Using objects always means more overhead and VB6 even isn't fully object oriented, so I decided to NOT write a wrapper class and use the item handles directly instead.

ExplorerTreeView 2.0 is written in C++ and wraps the item handles with several classes. But ExplorerTreeView 2.0 has no shell browser features yet.

Kind regards
TiKu
Crunching for Fab36_Folding-Division at Folding@Home. Join Fab36/Fab30! - Folding@Home and BOINC
Boycott DRM! Boycott HDCP!
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

Post by WoF »

Hallo nochmal TiKu,

fuer einen Zwischenbericht: Das binary 'Explorer Clone' laeuft fehlerlos. Lade ich den Source in meine IDE, kann ich das Programm zwar starten, kriege aber beim ersten Click auf ein TreeItem den folgenden Absturz:
Laufzeitfehler: 80004005
Methode 'ItemData' fuer das Objekt 'IlistViewItem' ist fehlgeschlagen.
Hab' ich was falsch gemacht? :?

Viele Gruesse
WoF
User avatar
TiKu
Administrator
Administrator
Posts: 832
Joined: 28 Sep 2004, 21:10
Location: München
Contact:

Post by TiKu »

(This guy complains that the compiled ExplorerClone sample runs fine, but crashes with error 80004005 (method ItemData of object IListViewItem failed) if started from the VB6 IDE.)

No, probably you didn't anything wrong. I've just noticed that the ItemData property doesn't work as expected if the underlying comctl32.dll is older than version 6.0.:angry: My VB6 IDE is themed, so I didn't notice the bug before. Delete the ExplorerClone.exe.manifest file and the sample's binary will crash, too.

Looks like I won't have much spare time during semester break...

/edit: As a workaround, you could theme your VB6. Copy the ExplorerClone.exe.manifest file into the directory containing your VB6.exe and rename it to VB6.exe.manifest. Restart VB6 then.
Crunching for Fab36_Folding-Division at Folding@Home. Join Fab36/Fab30! - Folding@Home and BOINC
Boycott DRM! Boycott HDCP!
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

Post by WoF »

Hi TiKu,

goin' back to english, so you needn't explain everything. ;)
Works fine now. What did I do? What's that manifest files anyway and what are they for? (Everyday seems to present something new).

Still more questions:
What would I really have to include in my application to make it use the FillListView routine from your sample? Do I really need all that code and definitions?
(Maybe I should replace my ListView by your ExplorerListView too.)

If I make a setup-package from my application, do I have to worry about the new typelib definitions, which are not present at the target computer?

Best Regards
WoF
User avatar
TiKu
Administrator
Administrator
Posts: 832
Joined: 28 Sep 2004, 21:10
Location: München
Contact:

Post by TiKu »

WoF wrote:Works fine now. What did I do? What's that manifest files anyway and what are they for? (Everyday seems to present something new).
The manifest file tells Windows XP to use comctl32.dll version 6.0 instead of version 5.82 and to draw the application in XP style (i. e. using the active theme). Comctl32.dll contains all the common controls like SysListView32, SysTreeView32, msctls_statusbar and so on. Version 6.0's SysListView32 differs from that of version 5.82 in many details. E. g. version 6.0 gives each listview item a persistent ID on its own (an item index isn't persistent, e. g. it may change if other items are added or removed). Version 5.82 doesn't have this functionality, so ExplorerListView has to use its own code to assign item IDs. This code affects the inner workings of the ItemData property of IListViewItem. And this code seems to be the reason for the crash.
WoF wrote:Still more questions:
What would I really have to include in my application to make it use the FillListView routine from your sample? Do I really need all that code and definitions?
The best way to find out is to copy just FillListView, try to compile, copy the methods that the compiler can't find and so on.
WoF wrote:(Maybe I should replace my ListView by your ExplorerListView too.)
IMHO it's too early to use this control productive. Shortly after releasing Beta 1, I found out, that drag image creation (for multiple items) fails on some machines. I wasn't able to solve this problem yet. While this bug can be worked around by disabling drag images until I can fix it, there's no such simple work-around for the ItemData crash. I could say "don't use the ItemData property", but the ExplorerClone sample needs this property to store each item's pIDL and this pIDL is needed in many places.
If you are sure that your app will never use comctl32.dll older than version 6.0, feel free to use ExplorerListView. Otherwise the buggy ItemData property will crash your app.
WoF wrote:If I make a setup-package from my application, do I have to worry about the new typelib definitions, which are not present at the target computer?
No. TypeLibraries are compiled into the binary and mustn't be redistributed. The only exception I know is stdole2.tlb.

TiKu
Crunching for Fab36_Folding-Division at Folding@Home. Join Fab36/Fab30! - Folding@Home and BOINC
Boycott DRM! Boycott HDCP!
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

Post by WoF »

Hallo TiKu,

thanks for your generous help and very informative replies. I really appreciate our communication. :D
Um, concerning the manifest file:
After creating VB6.exe.manifest I saw certain controls now beeing displayed in the XP style. But that gives me more problems:
It seems that I cannot change the BackColor and ForeColor properties of OptionButon and CheckBox anymore. An instruction like chkMyCheckBox.ForeColor=vbWhite doesn't seem to produce any effect.

Concerning the FillListBox I will try to follow your advice and go step by step through a test compilation.

Thank you for now

WoF
User avatar
TiKu
Administrator
Administrator
Posts: 832
Joined: 28 Sep 2004, 21:10
Location: München
Contact:

Post by TiKu »

WoF wrote:Hallo TiKu,

After creating VB6.exe.manifest I saw certain controls now beeing displayed in the XP style. But that gives me more problems:
It seems that I cannot change the BackColor and ForeColor properties of OptionButon and CheckBox anymore. An instruction like chkMyCheckBox.ForeColor=vbWhite doesn't seem to produce any effect.
I guess themed buttons (checkbox, frames and option buttons are just special buttons) don't support individual fore and back colors.

TiKu
Crunching for Fab36_Folding-Division at Folding@Home. Join Fab36/Fab30! - Folding@Home and BOINC
Boycott DRM! Boycott HDCP!
WoF
Lt. Commander
Posts: 43
Joined: 04 Aug 2005, 13:18

Post by WoF »

Oh. That would mean, I can't use theming because I've to adapt the look of these controls to the look of my application.

So I will now try to determine how much of your helpfully provided code I need to fill a normal ListView control, since I understood, the necessity for theming was related to the ExplorerListView.

Thanks for now
WoF
Post Reply