AfxOpenFileDialogW

Started by Paul Squires, June 06, 2016, 03:26:50 PM

Previous topic - Next topic

Paul Squires

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.


Paul Squires
PlanetSquires Software

José Roca

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.

José Roca

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.

James Fuller

Jose,
Are their plans for a FreeBASIC Afx wrapper conversion?

James

José Roca

#4
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.

José Roca

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
' ========================================================================================


José Roca

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
' ========================================================================================



José Roca

#7
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?

James Fuller

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

José Roca

Just a change from LONG to SHORT in the function but not in the DECLARE.

Paul Squires

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)

Paul Squires
PlanetSquires Software

José Roca

#11
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



Paul Squires

Paul Squires
PlanetSquires Software

James Fuller

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

José Roca

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.