• Welcome to PlanetSquires Forums.
 

AfxIFileSaveDialog (trouble setting initial folder on display)

Started by Paul Squires, August 27, 2019, 01:06:50 PM

Previous topic - Next topic

Paul Squires

Hi Jose,

I am still having trouble trying to set the default/initial folder that should display with the SaveAs dialog is shown. It has never worked for me ever since we discussed it way back in 2016:  https://www.planetsquires.com/protect/forum/index.php?topic=3850.msg28382#msg28382

I have posted my test code below in the hopes that you can point out where the problem might be.

Look for this code that I added to your template code.

' *** TRY TO SET THE INITIAL FOLDER WHEN THE SAVE DIALOG DISPLAYS ***
' *** I've tried both SetDefaultFolder and SetFolder and neither seems to work.
' *** This is a follow-up to the discussion here:
' ***      https://www.planetsquires.com/protect/forum/index.php?topic=3850.msg28382#msg28382
' ***


' ########################################################################################
' Microsoft Windows
' File: COM_IFileSaveDialog.fbtpl
' Contents: Demonstrates the use of the IFileSaveDialog interface.
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 José 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 UNICODE
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "Afx/CWindow.inc"
#INCLUDE ONCE "win/shobjidl.bi"
USING Afx

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)

' // Forward declarations
DECLARE FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
DECLARE 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

' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
                  BYVAL hPrevInstance AS HINSTANCE, _
                  BYVAL szCmdLine AS ZSTRING PTR, _
                  BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
   AfxSetProcessDPIAware

   ' // Initialize the COM library
   CoInitialize(NULL)

   ' // Create the main window
   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)

   

   ' // Dispatch messages
   FUNCTION = pWindow.DoEvents(nCmdShow)

   ' // Uninitialize the COM library
   CoUninitialize

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main window callback 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_COMMAND
         SELECT CASE GET_WM_COMMAND_ID(wParam, lParam)
            ' // If ESC key pressed, close the application sending an WM_CLOSE message
            CASE IDCANCEL
               IF GET_WM_COMMAND_CMD(wParam, lParam) = BN_CLICKED THEN
                  SendMessageW hwnd, WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
            CASE IDC_OFD
               IF GET_WM_COMMAND_CMD(wParam, lParam) = 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
         ' // Quit the application
         PostQuitMessage(0)
         EXIT FUNCTION

   END SELECT

   ' // Default processing of Windows messages
   FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================

' ========================================================================================
' Displays the File Save Dialog
' 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



' *** TRY TO SET THE INITIAL FOLDER WHEN THE SAVE DIALOG DISPLAYS ***
' *** I've tried both SetDefaultFolder and SetFolder and neither seems to work.
' *** This is a follow-up to the discussion here:
' ***      https://www.planetsquires.com/protect/forum/index.php?topic=3850.msg28382#msg28382
' ***

' SetDefaultFolder    
' ----------------
' Sets the folder used as a default if there is not a recently used folder value available.
   
' SetFolder    
' ---------
' Sets a folder that is always selected when the dialog is opened, regardless of previous user action.   

   ' // Set the default folder
   DIM pFolder AS IShellItem PTR
   dim as CWSTR wszFolder = "D:\myfolder\test"   ' <-- obviously change this to a valid folder
   SHCreateItemFromParsingName (wszFolder, 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
' ========================================================================================

Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

Based on user feedback, Microsoft changed the behavior of the Open and Save dialogs to always remember the last folder used. SetDefaultFolder only sets the folder used as a default if there is not a recently used folder value available. SetFolder should work, but id does not.

With the old GetOpenFileName and GetSaveFileName they did the same with the initial directory parameter, but at least they provided a workaround consisting in passing the full path in the file name parameter:


DIM wszFile AS WSTRING * 260 = "C:\Programs\Tests\Test.bas" ' or "*.bas" or "*.*"
DIM wszFilter AS WSTRING * 260 = "BAS files (*.BAS)|*.BAS|" & "All Files (*.*)|*.*|"
DIM dwFlags AS DWORD = OFN_EXPLORER OR OFN_FILEMUSTEXIST OR OFN_HIDEREADONLY OR OFN_OVERWRITEPROMPT
DIM cws AS CWSTR = AfxSaveFileDialog(hwnd, "", wszFile, "", wszFilter, "BAS", @dwFlags)
IF LEN(cws) THEN MessageBoxW(hwnd, cws, "File", MB_OK)


José Roca

Found the solution:

Move:


   ' // Display the dialog
   hr = psfd->lpVtbl->Show(psfd, hwndOwner)


After


   ' // Set the default folder
   DIM pFolder AS IShellItem PTR
   dim as CWSTR wszFolder = "C:\myfolder\test"   ' <-- obviously change this to a valid folder
   SHCreateItemFromParsingName (wszFolder, NULL, @IID_IShellItem, @pFolder)
   IF pFolder THEN
      psfd->lpVtbl->SetDefaultFolder(psfd, pFolder)
      pFolder->lpVtbl->Release(pFolder)
   END IF


That is:


   ' // Set the default folder
   DIM pFolder AS IShellItem PTR
   dim as CWSTR wszFolder = "C:\myfolder\test"   ' <-- obviously change this to a valid folder
   SHCreateItemFromParsingName (wszFolder, NULL, @IID_IShellItem, @pFolder)
   IF pFolder THEN
      psfd->lpVtbl->SetDefaultFolder(psfd, pFolder)
      pFolder->lpVtbl->Release(pFolder)
   END IF

   ' // Display the dialog
   hr = psfd->lpVtbl->Show(psfd, hwndOwner)


Paul Squires

Awesome! Yes, that does seem to work perfectly! Thanks a million - that problem was driving me crazy for a long time.
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

Paul Squires

It's amazing how this one small code fix has made me so happy :) :) :)   
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

Small change, but very important. The call to the Show method must be the last because, otherwise, the call to SetFolder isn't executed until the dialog is closed.

I have uploaded modified templates to GitHub.

Paul Squires

Yes, in hindsight it all makes perfect sense that the parameters should be set before displaying the dialog. I don't know how much I looked at that code over that past months and such an easy fix didn't click in for me. I was so fixated on looking at the SetFolder/SetDefaultFolder and thinking that I wasn't setting the IShellItem correctly (even though it did return SUCCESS result code). Oh well, happy it is now working. I will ensure that WinFBX and all the samples and templates are updated for the next WinFBE release.
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer