
IShellItemFilter Demo
Normally with an Open/Save dialog, you supply filters of a list of file types you want to display. But what if instead of 'only x', you wanted to filter by 'all except x' or similar, excluding only a specific file type? Or even show all files of a particular type, except those that met some other criteria (like created before a certain date)? It's entirely possible to get this level of control using a backend filter supported on the newer IFileDialog, the .SetFilter method with the IShellItemFilter class.
This is a followup to an earlier project that used a similar principle on the SHBrowseForFolder dialog: [VB6] SHBrowseForFolder - Custom filter for shown items: BFFM_IUNKNOWN/IFolderFilter
You can do this on Open/Save (and the new folder picker too) also, using a different but similar interface: IShellItemFilter.
IFileDialog includes a .SetFilter method, this project shows how to create the class for it. It uses a return so has to be swapped out, so the class itself is small:
Code:
Option Explicit
Implements IShellItemFilter
Private mOld4 As Long
Private Sub Class_Initialize()
Dim pVtable As IShellItemFilter
Set pVtable = Me
mOld4 = SwapVtableEntry(ObjPtr(pVtable), 4, AddressOf IncludeItemVB)
End Sub
Private Sub Class_Terminate()
Dim pVtable As IShellItemFilter
Set pVtable = Me
mOld4 = SwapVtableEntry(ObjPtr(pVtable), 4, mOld4)
End Sub
Private Sub IShellItemFilter_IncludeItem(ByVal psi As IShellItem)
End Sub
Private Sub IShellItemFilter_GetEnumFlagsForItem(ByVal psi As IShellItem, pgrfFlags As SHCONTF)
End Sub
Code:
Public Function IncludeItemVB(ByVal this As IShellItemFilter, ByVal psi As IShellItem) As Long
Dim lpName As Long, sName As String
Dim dwAtr As Long
If (psi Is Nothing) = False Then
psi.GetAttributes SFGAO_FILESYSTEM Or SFGAO_FOLDER, dwAtr
If ((dwAtr And SFGAO_FILESYSTEM) = SFGAO_FILESYSTEM) And ((dwAtr And SFGAO_FOLDER) = 0) Then 'is in normal file system, is not a folder
psi.GetDisplayName SIGDN_PARENTRELATIVEPARSING, lpName
sName = LPWSTRtoStr(lpName)
' Debug.Print "IShellItemFilter_IncludeItem?" & sName & "|" & gSpec
If PathMatchSpecW(StrPtr(sName), StrPtr(gSpec)) Then
IncludeItemVB = S_FALSE 'should not show
Else
IncludeItemVB = S_OK 'should show
End If
End If
Else
Debug.Print "IncludeItemVB.NoItem"
End If
End Function
Also note that this overrides the normal 'include' filters that you're used to using, like if instead of all files *.* you had *.exe, then set the exclude filter to *a*.exe, the dialog would show all .exe files except for ones with an 'a' in their name.
Adding the filter to a normal Open call is simple:
Code:
Dim fod As New FileOpenDialog
Set cSIFilter = New cShellItemFilter 'declared as a Public in the module
Dim psi As IShellItem
Dim tFilt() As COMDLG_FILTERSPEC
ReDim tFilt(0)
tFilt(0).pszName = "All Files"
tFilt(0).pszSpec = "*.*"
With fod
.SetFileTypes UBound(tFilt) + 1, VarPtr(tFilt(0))
.SetTitle "Browse away"
.SetOptions FOS_DONTADDTORECENT
.SetFilter cSIFilter
.Show Me.hWnd
.GetResult psi
If (psi Is Nothing) = False Then
Dim lp As Long, sRes As String
psi.GetDisplayName SIGDN_FILESYSPATH, lp
Label2.Caption = LPWSTRtoStr(lp)
End If
End With
Requirements
-Windows Vista or newer (the new dialogs weren't available in XP)
-oleexp v4.0 or newer (only for the IDE, not needed for compiled exe)
† - If you wanted to refuse to let the user select an excluded file, even manually, you could also do that without closing the dialog by using an events class, and not allowing the dialog to close on the OnFileOk event. See the original IFileDialog project which implements the event sink.