|
-
Oct 8th, 2017, 07:58 AM
#1
Re: [VB6] ucShellBrowse: A modern replacement for Drive/FileList w/ extensive feature
Known Issues
The following are bugs known to exist in the current version. They will be fixed in the next update if possible, but I include fixes here in case it's something you can't wait for.
- On certain versions of Windows 11, a crash occurs in FindFirstFileNt on the RtlFreeUnicodeString call. This call was an improper usage of the API and you can safely comment that line out.
- Certain special locations on Win10, such as Quick Access, have been designed to deliberately break 3rd party programs such as this. They expose an ICategoryProvider, supply a default categorization key, but attempting to obtain the ICategorizer causes an error. This results in the folder not loading at all as a hard error like this wasn't handled. A workaround is available by adding the bolded lines around the following line in LVLoadFolder:
Code:
On Error Resume Next
pCatProvider.CreateCategory tSCGUID, IID_ICategorizer, pcppv 'pCatG '
On Error GoTo e0
This allows the folder to load, and items can be used. However, special properties (e.g. Pinned in Quick Access) are mostly unavailable, as Microsoft has apparently deliberately broken them; they'll load for one or two items but not all of them, which can only be explained by deliberately making it incompatible.
- An app crash occurs if you refresh This PC 10 times or more. I'm investigating the cause. It may potentially be fixed in the current version (11.2) but I'm not confident enough to remove it yet.
Long-term issues with Windows or VB6 IDE
- (Intentional Windows behavior, workaround available) When you run this program, or any program, as Administrator, in Windows 10 and above Mapped Network Drives do not show unless either you re-map them from an elevated process, or in Registry Editor, navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System and create a new DWORD named EnableLinkedConnections and give it a value of 1. You can re-map the drives within ucShellBrowse (and ucShellTree) too: Navigate to the Desktop, then (if EnableShellMenu is True), right clicking 'This PC' will have the 'Map network drive...' option--- NOTE: While the mapping will appear in the dialog, complete it again anyway. After that, a Refresh will show the newly remapped drive going forward, in all apps, not just the one with ucShellBrowse.
- (Not well understood, workaround) There may be an issue with the control going striped then appcrash after modifying other controls when there's two UserControls on the same form, similar to the last one. This time it appears to be APIs that are triggering it. I don't understand the precise cause or complete set of circumstances, so if this is a problem for you, for now just don't include any Public APIs in the module with your Sub Main.
Another triggering circumstance seems to be if an Enum value from the bas with Main is defined in the TLB but not the UserControl and used in the control.
- (Unfixable, partial workaround) When OS zoom is set to other than 100% and dpiAware is not enabled in the manifest (or 'Disable display scaling on high-DPI settings' checked on the Properties->Compatibility tab), use of the shell context menu triggers graphical glitches. Subsequent right-clicks will have the menu pop-up nowhere near where clicked, and if running from the IDE all sorts of distortions in it.
There's no known fix for this issue, which effects numerous VB6 apps. If you use 100% scaling, it won't effect you, otherwise, the workaround is using the dpiAware=true or 'Disable scaling' options; ucShellBrowse is DPI-aware and scales itself.
- (Unfixable, possible workaround) If you have 64-bit Microsoft Office installed and its msoshext.dll is the registered property handler for post-2007 Office files (typically those with an 'x' in the extension, .docx, .xlsx, etc), you will not be able to view/edit the custom properties like Author, Title, etc in the Details Pane or display columns. There does not seem to be any possible fix for this, as there's no way to load a 64-bit property handler from a 32-bit application; CoCreateInstance with the flags that are supposed to can't create the class. Nor is there a way to specify difference property handlers for 32bit apps vs. 64bit apps that I'm aware of. Only option would be to install 32bit Office instead for VB6. An alternative would be to use twinBASIC and the 64bit version of ucShellBrowse.
- (Unfixable, no workaround) In a similar bug, while the control can load the Programs and Features control panel applet, it can only display the 32-bit programs.
Examples of Different Configurations and Features

Full, tight integration with the ucShellTree control.
 
New features: Showing the parent tree in the list (left), and loading the children of the selected folder in DirOnly/DirOnlyWithCtls modes (right).
 
You can configure it to be a simple thumbnail list, which includes thumbnails of embedded mp3 album art as shown on the right. Note that Windows doesn't come with a built-in FLAC handler, but if you install a 32-bit one, the control will be able to read/write those too.
 
Any property that's able to be edited can be set through the control, including through a popup date/time control or dropdown list where needed.

Multiple controls can be placed on the same form to create different layouts.

Turn the control into a Font Previewer. The root is set to the fonts folder; navigating elsewhere is disabled. Fonts with multiple styles appear in the dropdown, selecting them there or double clicking loads the list of individual styles for previews and info.

The Computer/This PC folder shows bars for the percent full in Details/Tile/Contents views, and automatically groups by drive type.

You can add a custom footer bar, and there's InfoTips that show a selection of properties, like Explorer does.

Progress marquee while loading folders
 
Popup frame that allows you to select columns. The picture on the left shows the frame as it initially appears. Shown on the right is the two advanced options: The filter bar, which lets you search for the property you want, and Advanced Mode-- This shows a whole bunch of properties not normally accessible as column options; because many of them are either not text, inapplicable to normal files/folders, or otherwise not suitable for being a display column. But many of them are valid as columns, such as the GPS properties shown above.

You can add entirely custom columns. An event is raised for each file giving the names/paths/shell interface to it, and you can specify the column data. As shown above, these custom columns have the option to support images.

You can specify a custom root, and browse FTP sites.

Certain columns have hyperlinks, this controls supports them.

Group by any property, and view the standard Explorer right-click context menu.

Custom font, tiled background, and no border.

Ratings stars, subsetted groups, custom foreground and background colors

Automatically populated Special Folders submenu for the Bookmarks menu.
 
You can open special locations like 'Devices and Printers' and 'Programs and Features', and use the right-click menu to carry out their custom actions like Uninstall a program, or Set As Default for a printer.

The search feature. You can start a quick search by just typing a string in the box on the form and pressing enter, or double-click the box to bring up the popup with additional options. You can optionally have the control box disabled, but have a menu item on right-click that brings up the popup (with additional box to type text in).
Notes Using the control
The control can be used as either a regular UserControl, or can be compiled into an OCX. Since the control is now entirely self-contained, I recommend just adding the .ctl to your project. The control is added via the Project->Add UserControl menu option. Make sure ucShellBrowse.ctl and ucShellBrowse.ctx are in the same directory, but you only add the .ctl. As noted above, you need to go to Project->References and make sure oleexp.tlb (OLEEXP - Modern Shell Interfaces for VB6) is listed added as a reference when using the control this way, but once again, you need not distribute the typelib with your exe, it's only for the IDE.
Once added, you can add the control to a form and it will be created with the default options. The pictures at the top of this post, and in the post that follows, show the incredible diversity of the control. The major options you'll want to look at first include:
ControlType - The default is 'Directory and Files', which shows the combobox with the folders dropdown, and the listview with the files. You can change that to Directories Only or Files Only, with the former having two additional modes, Drives Only, and Directories Only With Controls, which instead of being a plain combobox, also shows the Back/Forward, Up, View, Bookmarks, and Search Box controls-- all of which can be independently set to be shown or hidden (in the Directories Only mode, they're all hidden regardless of the individual settings, which is why this is a separate mode).
ViewMode - This sets the initial view type of the ListView with the files. All modes are supported for startup: The default is Details, and the others are Small Icons, Medium Icons, Large Icons, XL Icons, Tiles, Content, List, or Thumbnails (size set via ThumbnailSize option). Not all modes can be viewed in the IDE Design Mode, but will be when the program runs.
BrowserPath - The value entered here will be used for startup. It defaults to App.Path. There's also the following options: BrowserStartLast (start in the last path the control was viewing; uses the BrowserPath value the first time), or BrowserStartBlank, to not load any folder.
ComboType and ComboCanEdit - The default is a typical dropdown, but you can change it to DropdownList or Simple. Allowing editing permits typing in a full path, a folder name in the current path, environmental variable shortcuts, or custom shortcuts (see the ValidateTextNavigate event). There's also the AutoComplete option, which is available but disabled by default.
CustomRoot, CustomRootLocked, and ComputerAsRoot - By default, the directory dropdown, like Explorer, has its root as the Desktop. You can change that to Computer (This PC in Win10) with the ComputerAsRoot option, or specify an entirely custom path. The custom path can be anywhere the control can navigate, including special locations, network locations, and FTP addresses. The CustomRootLocked option will limit browsing to the root folder and its children, not allowing the user to navigate anywhere else in the file system.
So those are the major options to look at when first configuring the control. After that, there's many, many other options from the color options, to layout options for all the individual elements, to numerous details about how the file view works. You'll want to read through each option, they all have descriptions that describe what they do.
Compiling to OCX
It's very simple to use the control as an OCX: Open ShellBrowse.vbp from the main folder, in the properties for the control, change Public to True, you can then compile. Then proceed to move, register, and use as you would any other .ocx. A project with the OCX does not require oleexp.tlb. If needed I could also send the compiled OCX, just ask here or PM.
The demo projects use the control as a .ctl.
Multiple Controls
You can have more than one ucShellBrowse on a form. If you want to have them linked, like one with the directory dropdown only, then one with files only elsewhere on the form, you'll need to change the path in one when the other changes:
Code:
Private Sub ucShellBrowse1_DirectoryChanged(ByVal sFullPath As String, siItem As oleexp.IShellItem, pidlFQ As Long)
ucShellBrowse2.BrowserOpenItem siItem
End Sub
Private Sub ucShellBrowse2_DirectoryChanged(ByVal sFullPath As String, siItem As oleexp.IShellItem, pidlFQ As Long)
ucShellBrowse1.BrowserOpenItem siItem
End Sub
Note: This section previously indicated usage of a variable to detect if a manual change was happening to prevent an infinite loop, but that need was eliminated by adding a sanity check to the control many versions ago now, so all you need is the above.
Also, to make sure virtual objects are navigated correctly, it's best to use BrowserOpenItem instead of BrowserPath.
Printing Directory List
This is a pretty common usage, which is why I added the simple .Files(), .Folders(), etc. But what if you want to output a fancy list:
This is done in a project form rather than the .ctl. First, add oleexp addons mIID.bas and mPKEY.bas (if copying from here; the demo project in the zip has its own copies of the needed UUIDs/PKEYs). Second, in a module we'll borrow some support code from the control:
Code:
'(See Form1 in the Demo folder for the 3 API declares used below if you don't already have them.)
Public Function LPWSTRtoStr(lPtr As Long, Optional ByVal fFree As Boolean = True) As String
SysReAllocString VarPtr(LPWSTRtoStr), lPtr
If fFree Then
Call CoTaskMemFree(lPtr)
End If
End Function
Public Function GetPropertyKeyDisplayString(pps As IPropertyStore, pkProp As oleexp.PROPERTYKEY, Optional bFixChars As Boolean = True) As String
'Gets the string value of the given canonical property; e.g. System.Company, System.Rating, etc
'This would be the value displayed in Explorer if you added the column in details view
Dim lpsz As Long
Dim ppd As IPropertyDescription
PSGetPropertyDescription pkProp, IID_IPropertyDescription, ppd
If ((pps Is Nothing) = False) And ((ppd Is Nothing) = False) Then
PSFormatPropertyValue ObjPtr(pps), ObjPtr(ppd), PDFF_DEFAULT, lpsz
SysReAllocString VarPtr(GetPropertyKeyDisplayString), lpsz
CoTaskMemFree lpsz
If bFixChars Then
GetPropertyKeyDisplayString = Replace$(GetPropertyKeyDisplayString, ChrW$(&H200E), "")
GetPropertyKeyDisplayString = Replace$(GetPropertyKeyDisplayString, ChrW$(&H200F), "")
GetPropertyKeyDisplayString = Replace$(GetPropertyKeyDisplayString, ChrW$(&H202A), "")
GetPropertyKeyDisplayString = Replace$(GetPropertyKeyDisplayString, ChrW$(&H202C), "")
End If
Set ppd = Nothing
Else
Debug.Print "GetPropertyKeyDisplayString.Error->PropertyStore or PropertyDescription is not set."
End If
End Function
Then we get to the code to do it. Before calling this, do Select All (Ctrl+A) manually (since there's no other way in this version to get an array of all items)... then with everything selected:
Code:
Private Sub Command3_Click()
On Error GoTo e0
Dim pArSel As IShellItemArray
Dim siChild As IShellItem, si2 As IShellItem2
Dim pst As IPropertyStore
Dim nItems As Long
Dim lpsz As Long, sTmp As String
Dim sOut As String
Dim dwAtr As SFGAO_Flags
Dim i As Long
Set pArSel = ucShellBrowse1.SelectedItems
pArSel.GetCount nItems
Debug.Print " Name Size Date Modified Attributes"
Debug.Print "---------------------------------------------------------------------------"
For i = 0 To (nItems - 1)
pArSel.GetItemAt i, siChild
If siChild Is Nothing Then
Debug.Print "No child"
Exit Sub
End If
Set si2 = siChild
siChild.GetAttributes SFGAO_FOLDER, dwAtr
If (dwAtr And SFGAO_FOLDER) = SFGAO_FOLDER Then
sOut = "<DIR> "
Else
sOut = " "
End If
siChild.GetDisplayName SIGDN_NORMALDISPLAY, lpsz
sTmp = LPWSTRtoStr(lpsz)
sOut = sOut & sTmp
sOut = sOut & Space$(30 - Len(sOut))
si2.GetPropertyStore GPS_OPENSLOWITEM Or GPS_BESTEFFORT, IID_IPropertyStore, pst
sTmp = GetPropertyKeyDisplayString(pst, PKEY_Size)
sTmp = Space$(13 - Len(sTmp)) & sTmp & " "
sOut = sOut & sTmp
sTmp = GetPropertyKeyDisplayString(pst, PKEY_DateModified)
sTmp = sTmp & Space$(20 - Len(sTmp))
sOut = sOut & sTmp
sTmp = GetPropertyKeyDisplayString(pst, PKEY_FileAttributes)
sTmp = "-" & sTmp
sOut = sOut & sTmp
Debug.Print sOut
Set pst = Nothing
Next i
pArSel.GetPropertyStore GPS_BESTEFFORT Or GPS_OPENSLOWITEM, IID_IPropertyStore, pst
If (pst Is Nothing) = False Then
sTmp = GetPropertyKeyDisplayString(pst, PKEY_Size)
Set pst = Nothing
Else
sTmp = ""
End If
Debug.Print "---------------------------------------------------------------------------"
Debug.Print " " & nItems & " items " & sTmp
Exit Sub
e0:
Debug.Print "Form1.Command3_Click->Error: " & Err.Description & ", 0x" & Hex$(Err.Number)
End Sub
Changing to text output is just a matter of replacing Debug.Print with a write to a text file.
Localization This project contains a large number of English-language strings, which presents a problem for users who speak another language. The code in Version 5.3 was reorganized to make localization as easy as possible: all of the text* is located in one place; near the top of the control in the User Consts section just below the changelog. Control captions are set from this section as well. So for example, to turn the whole thing French-
Code:
'Control captions:
Private Const scc_Up = "Haut"
Private Const scc_Back = " Retour"
Private Const scc_Forward = "Svnt"
Private Const scc_View = "Vue"
Private Const scc_Ok = "OK"
Private Const scc_Cancel = "Annuler"
Private Const scc_Save = "Enreg."
Private Const scc_ChCols = "Colonnes"
Private Const scc_Prv = "Aperçu"
Private Const scc_Width = "Largeur"
Private Const scc_PropCol = "Propriétés"
'These are applied to the controls always; changing the caption in the Properties window has no effect at runtime.
Private Const mn_sMore As String = "Plus..." 'There's no system resource to load localized translations like the other items
Private Const mn_sLock As String = "Garder memes colonnes" 'on the column menu, so you may change it manually here if you wish
'Note: The lock item won't appear if columns are locked through the LockColumns property
Private Const mn_sLW = "Bloquer largeur"
Private Const mn_sLH = "Bloquer hauteur"
Private Const mn_sSel = "Selectionner"
Private Const mnv_sGrpHdr = "Grouper par"
Private Const mnv_sSortHdr = "Trier par"
Private Const mnv_sSortAsc = "Ordre croissant"
Private Const mnv_sSortDsc = "Ordre décroissant"
Private Const mnv_sli As String = "Grandes Icônes"
Private Const mnv_sSM = "Petites Icônes"
Private Const mnv_sLS = "Liste"
Private Const mnv_sDT = "Détails"
Private Const mnv_sTV = "Tuiles"
Private Const mnv_sCT = "Contenu"
Private Const mnv_sJM = "Vignettes"
Private Const mnv_sNN = "(aucun)"
Private Const mnv_sNF = "Nouveau dossier"
Private Const mnv_sPR = "Propriétés"
Private Const mnv_sRF = "Rafraîchir"
Private Const mnv_sSB = "Barre d'état"
Private Const mnv_sPV = "Volet aperçu"
Private Const mnv_sDB = "Barre de détails"
Private Const mnv_sPS = "Collez"
Private Const mnv_sBK = "Signets"
Private Const m_def_GroupSubsetLinkText As String = "Plus de fichiers..."
Private Const m_sColCap As String = "Choisir des colonnes"
'(and so on. A number of strings have been omitted because of the post character limit, and before that it was many versions out of date anyway. Please consult the control itself for remaining items to translate.)
|
 |
Since I don't want to make a particular resource file mandatory, I didn't set this up as a string table in one, but you could easily set that up too with everything in one place like this. Did the best I could for the circumstances.
And please forgive me if I got something wrong in my example, I was nearly fluent but haven't used French in about 13 years now.
* - The column names in the ListView, and the column data itself, are loaded from Windows, and thus will already appear in whatever the current system language is.
UPDATE: As of Version 8.0, all images are now built into the control. You no longer need to worry about keeping them together, you only need the .ctl and .ctx. The alternative manifest for the demo with DpiAware set to False is now included in the main download. It's now possible to simply add the .ctl to your project with an existing resource/manifest, and nothing else need be done. No more separate images! Thanks go to LaVolpe and The_trick.
As per above, the icons have been encoded into the .ctl. The binary data from the icons was encoded into a bitmap, which could then be set as the image for a picturebox, and then be decoded back to binary data and processed as an icon/png file. All of the default icons are now built into the control as images in a picturebox that are decoded to binary data. If you wish to add additional images to the control (or use the method in your own project), I'm attaching the tool I developed around The_trick's code. The decoding function is also included but not used. There are no dependencies.
You do not need to have this or do anything with this to run the control complete with the default images, just to add additional images beyond the ones it comes with, or to replace the images already contained in it.
NOTE: Since so many people were downloading this tool, I developed it into a more formal sample, complete with a demo to decode things. You'll find this tool here:
[VB6] Encode binary data to bitmap for use in a PictureBox
Last edited by fafalone; Oct 12th, 2023 at 01:58 AM.
Reason: Cleared known issues for 10.0R4
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|