[VB6] Using the new IFileDialog interface for customizable Open/Save (TLB)
LaVolpe put out an excellent class module that implements these interfaces, but I've been working on using them through a different approach for some time and wanted to post it as well since, while requiring a TLB, it offers advantages like having IShellItem as a declarable type used all over projects, interfaces, and APIs.
IFileDialog / IFileOpenDialog / IFileSaveDialog
IFileSaveDialog and IFileOpenDialog were introduced in Windows Vista to supersede the GetOpenFileName/GetSaveFileName API, and offer several advantages (although certainly some drawbacks). Windows provides a default implementation, so they're fairly easy to use in VB. Among the advantages is easy addition of custom controls, something that was not previously possible in VB (or at least so hard no one bothered).
PROJECT UPDATED 2016 Nov 24th
The attached sample project has been changed to reference oleexp.tlb 4.0 or higher, which supercedes the previous references to both oleexp3.tlb and olelib.tlb. That is the only change.
PROJECT UPDATED
Since I did it for myself, I thought I would share. If you're using the Advise sink, you might have noted that some events, like OnFolderChanging, offered the ability to change the response if you returned something. But since it has to be a Sub in order to be the HRESULT VB requires it to be but then discards and forces it into a sub, this wasn't possible. The new versions shows how to use a vtable-swap to replace the class module sub with a regular function that can return a variable.
Also note that to reduce the proliferation of different versions of my typelib, I'm no longer including whatever version happens to be current at the time. Always go to the main project thread for the latest version.
The typelib:
This project uses oleexp.tlb, which is my own fork/expansion of olelib.tlb.
Minimum required oleexp version is 4.0. Add under Project->References. Required only for IDE; not needed with compiled app.
IShellItem:
For modern Windows, IShellItem is becoming more and more important. This project will familiarize you with using this object. The typelib based approach to this offers the advantage of being able to use IShellItem and related types project-wide, and pass them around easily, both within the project and to APIs and interfaces that use them. The sample project has helper functions that show how to create and manipulate IShellItem and related functions.
--
The attached ZIP includes a sample project illustrating the use of the dialogs. There's examples for a very simple Open, a typical Open dialog, a simple Save dialog, a multi-file-open dialog, and a highly customized open dialog.
In Your Own Project
To use IFileDialog-based Open/Save, your project needs to add a reference to olelib and oleexp. cFileDialogEvents.cls is required only if you want to receive feedback while the dialog is open (including from added custom controls).
-----------------------------------
Here's how simple a basic Open File dialog is with this tlb:
Code:
Dim fodSimple As FileOpenDialog
Dim isiRes As IShellItem
Dim lPtr As Long
Set fodSimple = New FileOpenDialog
With fodSimple
.SetTitle "Simple File Open"
.Show Me.hWnd
.GetResult isiRes
isiRes.GetDisplayName SIGDN_FILESYSPATH, lPtr
Text1.Text = BStrFromLPWStr(lPtr, True)
End With
Set isiRes = Nothing
Set fodSimple = Nothing
That's all it takes to get the very simplest Open File dialog going, and shows how the class is used.
Events
While previously subclassing was required to receive event notifications while the dialog was displayed, this is now accomplished simply by adding the optional cFileDialogEvents class and calling the .Advise method.
This same class also receives events from any custom controls added.
Customization
I thought that even with this approach, it was going to be hard. But the class is set up to make this a very easy process.
To add a simple label and a button,
Code:
Dim fdc As IFileDialogCustomize
Set fdc = pDlg 'pDlg is the FileOpenDialog object
pDlg.Advise cFDE, 0 'the events class
fdc.AddText 1000, "This is a test label."
fdc.AddPushButton 1001, "New Button"
With that, the events class will have its OnButtonClicked method called when its clicked.
Here's the full sample code that produces the dialog in the above screenshot:
Code:
On Error Resume Next 'A major error is thrown when the user cancels the dialog box
List1.Clear
Dim isiRes As IShellItem
Dim isiDef As IShellItem 'default folder
Dim FOLDERID_Pictures As UUID
Dim pidlDef As Long
Dim lPtr As Long
Dim lOptions As FILEOPENDIALOGOPTIONS
'Set up filter
Dim FileFilter() As COMDLG_FILTERSPEC
ReDim FileFilter(1)
FileFilter(0).pszName = "Image Files"
FileFilter(0).pszSpec = "*.jpg;*.gif;*.bmp"
FileFilter(1).pszName = "All Files"
FileFilter(1).pszSpec = "*.*"
'set up default folder: note that this only shows the very first time
' after that, the last directory is default
' automatically. override with SetFolder.
Call CLSIDFromString(StrPtr(fidPictures), FOLDERID_Pictures)
Call SHGetKnownFolderIDList(FOLDERID_Pictures, 0, 0, pidlDef)
If pidlDef Then
Call SHCreateShellItem(0, 0, pidlDef, isiDef)
End If
Set fod = New FileOpenDialog
Set cEvents = New cFileDialogEvents
With fod
.Advise cEvents, 0
.SetTitle "Select Thine File Sir"
.GetOptions lOptions
lOptions = lOptions Or FOS_FILEMUSTEXIST Or FOS_FORCESHOWHIDDEN 'just an example of options... shows hidden files even if they're normally not shown
.SetOptions lOptions
.SetOkButtonLabel "Mine File"
.SetFileNameLabel "Look! A custom file label!!!"
If (isiDef Is Nothing) = False Then
.SetFolder isiDef
End If
.SetFileTypes 2, VarPtr(FileFilter(0).pszName)
'Now we'll begin adding custom controls
'First, we set up the interface
'The control IDs can be any number, and should
'really be stored as consts
Set fdc = fod
fdc.AddText 1000, "This is a test label."
fdc.AddPushButton 1001, "New Button"
fdc.MakeProminent 1001 'Moves to by the open button; only checkboxes, buttons, combos, menus can be made prominent
fdc.AddPushButton 1002, "Some Other Button"
fdc.StartVisualGroup 2000, "VG-1"
fdc.AddCheckButton 2001, "Checkers!", 1
fdc.AddSeparator 2002
'For menus, and radio buttons/combos, first you add the control, then add items with AddControlItem
fdc.AddMenu 2010, "Checkers?"
fdc.AddControlItem 2010, 3000, "Pretty good."
fdc.AddControlItem 2010, 3001, "Pretty bad."
fdc.AddControlItem 2010, 3002, "Neutral!"
fdc.AddEditBox 2003, "Other."
fdc.EndVisualGroup
fdc.StartVisualGroup 4000, "Radio station?"
fdc.AddRadioButtonList 4001
fdc.AddControlItem 4001, 4010, "Radio Station Alpha"
fdc.AddControlItem 4001, 4011, "Radio Station Beta"
fdc.EndVisualGroup
fdc.AddComboBox 5000
fdc.AddControlItem 5000, 5010, "Combo Alpha"
fdc.AddControlItem 5000, 5011, "Combo Beta"
fdc.AddControlItem 5000, 5012, "Combo Gamma"
fdc.SetSelectedControlItem 5000, 5011
.Show Me.hWnd
.GetResult isiRes
isiRes.GetDisplayName SIGDN_FILESYSPATH, lPtr
Text1.Text = BStrFromLPWStr(lPtr, True)
End With
If pidlDef Then Call CoTaskMemFree(pidlDef)
Set isiRes = Nothing
Set isiDef = Nothing
Set fod = Nothing