Hi Jose,
I am to the point now where I need to implement OpenFileDialog. Should I simply convert your AfxOpenFileDialogW (for PowerBASIC) version or should I use the COM version that you posted elsewhere in the source code forum? For the editor, this will be used to select and then open one or more files to be edited.
I also was thinking about that. The old one has the problem of the buffer size. My wrapper has an optional parameter to specify it, but how to know in advance how big will need to be? The new one does not return all the paths in a string variable, but a collection of paths, so it is prepared for the new times, now that MAX_PATH is a thing of the past.
If you choose to use the COM version, don't forget to add CoInitialize NULL at the very beginning of WinMain and CoUninitialize at the end.
Jose,
Are their plans for a FreeBASIC Afx wrapper conversion?
James
There is not much that can be wrapped, specially if you want multiselection. Most of the work is to fill the array of COMDLG_FILTERSPEC structures and get the results enumerating the IShellItemArray interface. So get used to change techniques: instead of parsing an string to extract the paths, learn how to enumerate a COM collection.
Maybe I can do the parsing and return an array of WSTRINGs. Let's see.
This is not a wrapper, but a demonstration of how to use the IFileOpenDialog interface. This interface is too complex to be wrapped with a single function because there are dozens of options that you may need to choose or not.
Single selection:
' ########################################################################################
' Microsoft Windows
' File: COM_IFileOpenDialog.fbtpl (single selection)
' Contents: Demonstrates the use of the IFileOpenDialog interface.
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 Jose Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "win/shobjidl.bi"
#INCLUDE ONCE "Afx/CWindow.inc"
USING Afx.CWindowClass
CONST IDC_OFD = 1001
DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
END WinMain(GetModuleHandleW(NULL), NULL, COMMAND(), SW_NORMAL)
' ========================================================================================
' Displays the FileOpenDialog
' The returned pointer must be freed with CoTaskMemFree
' ========================================================================================
FUNCTION AfxIFileOpenDialog (BYVAL hwndOwner AS HWND, BYVAL sigdnName AS SIGDN = SIGDN_FILESYSPATH) AS WSTRING PTR
' // Create an instance of the FileOpenDialog interface
DIM hr AS LONG
DIM pofd AS IFileOpenDialog PTR
hr = CoCreateInstance(@CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, @IID_IFileOpenDialog, @pofd)
IF pofd = NULL THEN RETURN NULL
' // Set the file types
DIM rgFileTypes(1 TO 3) AS COMDLG_FILTERSPEC
rgFileTypes(1).pszName = @WSTR("PB code files")
rgFileTypes(2).pszName = @WSTR("Executable files")
rgFileTypes(3).pszName = @WSTR("All files")
rgFileTypes(1).pszSpec = @WSTR("*.bas;*.inc;*.bi")
rgFileTypes(2).pszSpec = @WSTR("*.exe;*.dll")
rgFileTypes(3).pszSpec = @WSTR("*.*")
pofd->lpVtbl->SetFileTypes(pofd, 3, @rgFileTypes(1))
' // Set the title of the dialog
hr = pofd->lpVtbl->SetTitle(pofd, "A Single-Selection Dialog")
' // Display the dialog
hr = pofd->lpVtbl->Show(pofd, hwndOwner)
' // Get the result
DIM pItem AS IShellItem PTR
DIM pwszName AS WSTRING PTR
IF SUCCEEDED(hr) THEN
hr = pofd->lpVtbl->GetResult(pofd, @pItem)
IF SUCCEEDED(hr) THEN
hr = pItem->lpVtbl->GetDisplayName(pItem, sigdnName, @pwszName)
FUNCTION = pwszName
END IF
END IF
' // Cleanup
IF pItem THEN pItem->lpVtbl->Release(pItem)
IF pofd THEN pofd->lpVtbl->Release(pofd)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hWnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
SELECT CASE uMsg
CASE WM_CREATE
EXIT FUNCTION
CASE WM_COMMAND
SELECT CASE LOWORD(wParam)
' // If ESC key pressed, close the application sending an WM_CLOSE message
CASE IDCANCEL
IF HIWORD(wParam) = BN_CLICKED THEN
SendMessageW hwnd, WM_CLOSE, 0, 0
EXIT FUNCTION
END IF
CASE IDC_OFD
IF HIWORD(wParam) = BN_CLICKED THEN
' // Display the Open File Dialog
DIM pwszName AS WSTRING PTR = AfxIFileOpenDialog(hwnd)
' // Display the name of the selected file
IF pwszName THEN
MessageBoxW(hwnd, *pwszName, "IFileOpenDialog", MB_OK)
CoTaskMemFree(pwszName)
END IF
EXIT FUNCTION
END IF
END SELECT
CASE WM_DESTROY
PostQuitMessage(0)
EXIT FUNCTION
END SELECT
FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Initialize the COM library
CoInitialize(NULL)
' // Set process DPI aware
AfxSetProcessDPIAware
DIM pWindow AS CWindow
pWindow.Create(NULL, "IFileOpenDialog example", @WndProc)
pWindow.SetClientSize(500, 320)
pWindow.Center
' // Add a button
pWindow.AddControl("Button", , IDC_OFD, "&Open File Dialog", 350, 250, 110, 23)
' // Process event messages
FUNCTION = pWindow.DoEvents(nCmdShow)
' // Uninitialize the COM library
CoUninitialize
END FUNCTION
' ========================================================================================
Multiple selection:
' ########################################################################################
' Microsoft Windows
' File: COM_IFileOpenDialog.fbtpl (multiple selection)
' Contents: Demonstrates the use of the IFileOpenDialog interface.
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 Jose Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "win/shobjidl.bi"
#INCLUDE ONCE "Afx/CWindow.inc"
USING Afx.CWindowClass
CONST IDC_OFD = 1001
DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
END WinMain(GetModuleHandleW(NULL), NULL, COMMAND(), SW_NORMAL)
' ========================================================================================
' Displays the FileOpenDialog (multiple selection)
' Returns a pointer to the IShellItemArray collection.
' ========================================================================================
FUNCTION AfxIFileOpenDialog (BYVAL hwndOwner AS HWND, BYVAL sigdnName AS SIGDN = SIGDN_FILESYSPATH) AS IShellItemArray PTR
' // Create an instance of the FileOpenDialog interface
DIM hr AS LONG
DIM pofd AS IFileOpenDialog PTR
hr = CoCreateInstance(@CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, @IID_IFileOpenDialog, @pofd)
IF pofd = NULL THEN RETURN NULL
' // Set the file types
DIM rgFileTypes(1 TO 3) AS COMDLG_FILTERSPEC
rgFileTypes(1).pszName = @WSTR("PB code files")
rgFileTypes(2).pszName = @WSTR("Executable files")
rgFileTypes(3).pszName = @WSTR("All files")
rgFileTypes(1).pszSpec = @WSTR("*.bas;*.inc;*.bi")
rgFileTypes(2).pszSpec = @WSTR("*.exe;*.dll")
rgFileTypes(3).pszSpec = @WSTR("*.*")
pofd->lpVtbl->SetFileTypes(pofd, 3, @rgFileTypes(1))
' // Set the title of the dialog
hr = pofd->lpVtbl->SetTitle(pofd, "A Single-Selection Dialog")
' // Allow multiselection
hr = pofd->lpVtbl->SetOptions(pofd, FOS_ALLOWMULTISELECT)
' // Display the dialog
hr = pofd->lpVtbl->Show(pofd, hwndOwner)
' // Get the result
DIM pItemArray AS IShellItemArray PTR
IF SUCCEEDED(hr) THEN
hr = pofd->lpVtbl->GetResults(pofd, @pItemArray)
FUNCTION = pItemArray
END IF
' // Release the
IF pofd THEN pofd->lpVtbl->Release(pofd)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hWnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
SELECT CASE uMsg
CASE WM_CREATE
EXIT FUNCTION
CASE WM_COMMAND
SELECT CASE LOWORD(wParam)
' // If ESC key pressed, close the application sending an WM_CLOSE message
CASE IDCANCEL
IF HIWORD(wParam) = BN_CLICKED THEN
SendMessageW hwnd, WM_CLOSE, 0, 0
EXIT FUNCTION
END IF
CASE IDC_OFD
IF HIWORD(wParam) = BN_CLICKED THEN
' // Display the Open File Dialog
DIM pItems AS IShellItemArray PTR = AfxIFileOpenDialog(hwnd)
IF pItems = NULL THEN EXIT FUNCTION
DIM dwItemCount AS LONG, idx AS LONG, pItem AS IShellItem PTR, pwszName AS WSTRING PTR
pItems->lpVtbl->GetCount(pItems, @dwItemCount)
FOR idx = 0 TO dwItemCount - 1
pItems->lpVtbl->GetItemAt(pItems, idx, @pItem)
IF pItem THEN
pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, @pwszName)
IF pwszName THEN
MessageBoxW(hwnd, *pwszName, "IFileOpenDialog", MB_OK)
CoTaskMemFree(pwszName)
pwszName = NULL
END IF
pItem->lpVtbl->Release(pItem)
pItem = NULL
END IF
NEXT
pItems->lpVtbl->Release(pItems)
EXIT FUNCTION
END IF
END SELECT
CASE WM_DESTROY
PostQuitMessage(0)
EXIT FUNCTION
END SELECT
FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Initialize the COM library
CoInitialize(NULL)
' // Set process DPI aware
AfxSetProcessDPIAware
DIM pWindow AS CWindow
pWindow.Create(NULL, "IFileOpenDialog example", @WndProc)
pWindow.SetClientSize(500, 320)
pWindow.Center
' // Add a button
pWindow.AddControl("Button", , IDC_OFD, "&Open File Dialog", 350, 250, 110, 23)
' // Process event messages
FUNCTION = pWindow.DoEvents(nCmdShow)
' // Uninitialize the COM library
CoUninitialize
END FUNCTION
' ========================================================================================
Well, it's no so hard after all, except for those that have been delaying to learn low-level COM programming during twenty years and probably will never start.
The good thing is that the returned path can be any length, not MAX_PATH, that it is no longer valid.
The old API function was limited to 32767 bytes for all the returned paths. Later, they did a hack to allow you to set the size of the buffer, but how are we going to calculate the needed size without knowing in advance how many files will be selected?
Jose,
In the Single Selection code I'm getting an error:
C:\FreeBASIC-1.05.0-win64\inc\Afx\AfxCtl.inc(3655) error 57: Type mismatch, at parameter 2 (nMinRange) of TaskDialog_SetProgressBarRange() in 'PRIVATE FUNCTION TaskDialog_SetProgressBarRange (BYVAL hTaskDlg AS HWND, BYVAL nMinRange AS SHORT, BYVAL nMaxRange AS SHORT) AS DWORD'
James
Just a change from LONG to SHORT in the function but not in the DECLARE.
Hi Jose,
How do you set the initial default folder in the COM example?
I know that I need to use the method "SetDefaultFolder" but I am not sure of how to specify the value. The following GPF's the application.
' Set the initial default folder of the dialog
Dim wPath As WString * 255 = Exepath
hr = pofd->lpVtbl->SetDefaultFolder(pofd, @wPath)
Requires the use of SHCreateItemFromParsingName.
DIM pFolder AS IShellItem PTR
SHCreateItemFromParsingName (ExePath, NULL, @IID_IShellItem, @pFolder)
IF pFolder THEN
pofd->lpVtbl->SetDefaultFolder(pofd, pFolder)
pFolder->lpVtbl->Release(pFolder)
END IF
Thanks Jose :)
Jose,
I think a user of your Afx components would be more comfortable with Fb Class wrappers around your COM based open/save dialogs where one just gets back a (w)string.
I'm afraid my Fb and COM are lacking (and no COM SaveAs example) to attempt this myself.
James
Too many options to wrap.
Quote
AddPlace
Adds a folder to the list of places available for the user to open or save items.
Advise
Assigns an event handler that listens for events coming from the dialog.
ClearClientData
Instructs the dialog to clear all persisted state information.
Close
Closes the dialog.
GetCurrentSelection
Gets the user's current selection in the dialog.
GetFileName
Retrieves the text currently entered in the dialog's File name edit box.
GetFileTypeIndex
Gets the currently selected file type.
GetFolder
Gets either the folder currently selected in the dialog, or, if the dialog is not currently displayed, the folder that is to be selected when the dialog is opened.
GetOptions
Gets the current flags that are set to control dialog behavior.
GetResult
Gets the choice that the user made in the dialog.
SetClientGuid
Enables a calling application to associate a GUID with a dialog's persisted state.
SetDefaultExtension
Sets the default extension to be added to file names.
SetDefaultFolder
Sets the folder used as a default if there is not a recently used folder value available.
SetFileName
Sets the file name.
SetFileNameLabel
Sets the text of the label next to the file name edit box.
SetFileTypeIndex
Sets the file type that appears as selected in the dialog.
SetFileTypes
Sets the file types that the dialog can open or save.
SetFilter
Deprecated in Windows 7. Sets the filter.
SetFolder
Sets a folder that is always selected when the dialog is opened, regardless of previous user action.
SetOkButtonLabel
Sets the text of the Open or Save button.
SetOptions
Sets flags to control the behavior of the dialog.
SetTitle
Sets the title of the dialog.
Unadvise
Removes an event handler that was attached through the IFileDialog::Advise method.
GetResults
Gets the user's choices in a dialog that allows multiple selection.
GetSelectedItems
Gets the currently selected items in the dialog. These items may be items selected in the view, or text selected in the file name edit box.
And classes bloat the code because unused methods aren't stripped. I stopped working with FB until I learned that PRIVATE functions aren't included in the code.
And then another for the IFileSaveDialog, with these additional methods:
ApplyProperties
Applies a set of properties to an item using the Shell's copy engine.
GetOptions
Gets the current flags that are set to control dialog behavior.
GetProperties
Retrieves the set of property values for a saved item or an item in the process of being saved.
SetCollectedProperties
Specifies which properties will be collected in the save dialog.
SetOptions
Sets flags to control the behavior of the dialog.
SetProperties
Provides a property store that defines the default values to be used for the item being saved.
SetSaveAsItem
Sets an item to be used as the initial entry in a Save As dialog.
I don't have plans to write FFC (FreeBASIC Foundation Classes).
> I'm afraid my Fb and COM are lacking (and no COM SaveAs example) to attempt this myself.
The save file dialog is very similar to the one for the single selection open file dialog.
' ########################################################################################
' Microsoft Windows
' File: COM_IFileSaveDialog.fbtpl
' Contents: Demonstrates the use of the IFileSaveDialog interface.
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 Jose Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "win/shobjidl.bi"
#INCLUDE ONCE "Afx/CWindow.inc"
USING Afx.CWindowClass
CONST IDC_OFD = 1001
DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
END WinMain(GetModuleHandleW(NULL), NULL, COMMAND(), SW_NORMAL)
' ========================================================================================
' Displays the FileSaveDialog
' The returned pointer must be freed with CoTaskMemFree
' ========================================================================================
FUNCTION AfxIFileSaveDialog (BYVAL hwndOwner AS HWND, BYVAL pwszFileName AS WSTRING PTR, _
BYVAL pwszDefExt AS WSTRING PTR, BYVAL sigdnName AS SIGDN = SIGDN_FILESYSPATH) AS WSTRING PTR
' // Create an instance of the IFileSaveDialog interface
DIM hr AS LONG
DIM psfd AS IFileSaveDialog PTR
hr = CoCreateInstance(@CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, @IID_IFileSaveDialog, @psfd)
IF psfd = NULL THEN RETURN NULL
' // Set the file types
DIM rgFileTypes(1 TO 3) AS COMDLG_FILTERSPEC
rgFileTypes(1).pszName = @WSTR("FB code files")
rgFileTypes(2).pszName = @WSTR("Text files")
rgFileTypes(3).pszName = @WSTR("All files")
rgFileTypes(1).pszSpec = @WSTR("*.bas;*.inc;*.bi")
rgFileTypes(2).pszSpec = @WSTR("*.txt")
rgFileTypes(3).pszSpec = @WSTR("*.*")
psfd->lpVtbl->SetFileTypes(psfd, 3, @rgFileTypes(1))
' // Set the title of the dialog
hr = psfd->lpVtbl->SetTitle(psfd, "File Save Dialog")
' // Set the file name
hr = psfd->lpVtbl->SetFileName(psfd, pwszFileName)
' // Set the extension
hr = psfd->lpVtbl->SetDefaultExtension(psfd, pwszDefExt)
' // Display the dialog
hr = psfd->lpVtbl->Show(psfd, hwndOwner)
' // Set the default folder
DIM pFolder AS IShellItem PTR
SHCreateItemFromParsingName (CURDIR, NULL, @IID_IShellItem, @pFolder)
IF pFolder THEN
psfd->lpVtbl->SetDefaultFolder(psfd, pFolder)
pFolder->lpVtbl->Release(pFolder)
END IF
' // Get the result
DIM pItem AS IShellItem PTR
DIM pwszName AS WSTRING PTR
IF SUCCEEDED(hr) THEN
hr = psfd->lpVtbl->GetResult(psfd, @pItem)
IF SUCCEEDED(hr) THEN
hr = pItem->lpVtbl->GetDisplayName(pItem, sigdnName, @pwszName)
FUNCTION = pwszName
END IF
END IF
' // Cleanup
IF pItem THEN pItem->lpVtbl->Release(pItem)
IF psfd THEN psfd->lpVtbl->Release(psfd)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hWnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
SELECT CASE uMsg
CASE WM_CREATE
EXIT FUNCTION
CASE WM_COMMAND
SELECT CASE LOWORD(wParam)
' // If ESC key pressed, close the application sending an WM_CLOSE message
CASE IDCANCEL
IF HIWORD(wParam) = BN_CLICKED THEN
SendMessageW hwnd, WM_CLOSE, 0, 0
EXIT FUNCTION
END IF
CASE IDC_OFD
IF HIWORD(wParam) = BN_CLICKED THEN
' // Display the Save File Dialog
DIM pwszName AS WSTRING PTR = AfxIFileSaveDialog(hwnd, "test", "bas")
' // Display the selected name
IF pwszName THEN
MessageBoxW(hwnd, *pwszName, "IFileSaveDialog", MB_OK)
CoTaskMemFree(pwszName)
END IF
EXIT FUNCTION
END IF
END SELECT
CASE WM_DESTROY
PostQuitMessage(0)
EXIT FUNCTION
END SELECT
FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL szCmdLine AS ZSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Initialize the COM library
CoInitialize(NULL)
' // Set process DPI aware
AfxSetProcessDPIAware
DIM pWindow AS CWindow
pWindow.Create(NULL, "IFileOpenDialog example", @WndProc)
pWindow.SetClientSize(500, 320)
pWindow.Center
' // Add a button
pWindow.AddControl("Button", , IDC_OFD, "File &Save Dialog", 350, 250, 110, 23)
' // Process event messages
FUNCTION = pWindow.DoEvents(nCmdShow)
' // Uninitialize the COM library
CoUninitialize
END FUNCTION
' ========================================================================================
The major problem for low-level COM programming with FB is the lack of native support for BSTRs. Without it, any attempt to make it easier to use is futile.
And then the first question posted will be: "Is there an easy way to center the dialog?" :)