AfxOpenFileDialog

Windows specific questions.
Post Reply
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

AfxOpenFileDialog

Post by deltarho[1859] »

@José Roca

Hi José

I am having a problem with AfxOpenFileDialog. If I use an empty sting for wszInitialDir my application crashes with a memory violation. If I use NULL I get a type mismatch.

I see that lpstrInitialDir is platform dependent according to the OPENFILENAME structure but even when I use CurDir for wszInitialDir the directory opened is not the current directory.

I am on Windows 10 and I have a horrible feeling that both your code and mine are OK but Windows 10 is misbehaving.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AfxOpenFileDialog

Post by deltarho[1859] »

Oh dear, I have just been playing with PowerBASIC's 'Display Openfile' and that appears to be behaving as expected.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: AfxOpenFileDialog

Post by Josep Roca »

It is a problem with FB 64 bit when you pass "" and the code tries to modify the content of the variable.

Replace AfxOpenFileDialog and AfxSaveFileDialog in AfxWin.inc with:

Code: Select all

' ========================================================================================
' Creates an Open dialog box that lets the user specify the drive, directory, and the name
' of a file or set of files to be opened.
' - hwndOwner: A handle to the window that owns the dialog box. This parameter can be any
'   valid window handle, or it can be NULL if the dialog box has no owner.
' - wszTitle: A string to be placed in the title bar of the dialog box. If this member is NULL,
'   the system uses the default title (that is, Save As or Open).
' - wszFile: The file name used to initialize the File Name edit control. When the GetOpenFileName
'   or GetSaveFileName function returns successfully, this buffer contains the drive designator,
'   path, file name, and extension of the selected file.
'   If the OFN_ALLOWMULTISELECT flag is set and the user selects multiple files, the buffer
'   contains the current directory followed by the file names of the selected files. For
'   Explorer-style dialog boxes, the directory and file name strings are NULL separated,
'   with an extra NULL character after the last file name. For old-style dialog boxes, the
'   strings are space separated and the function uses short file names for file names with
'   spaces. You can use the FindFirstFile function to convert between long and short file
'   names. If the user selects only one file, the lpstrFile string does not have a separator
'   between the path and file name.
' - wszInitialDir: The initial directory.
' - wszFilter: A buffer containing pairs of "|" separated filter strings. The first string
'   in each pair is a display string that describes the filter (for example, "Text Files"),
'   and the second string specifies the filter pattern (for example, "*.TXT"). To specify
'   multiple filter patterns for a single display string, use a semicolon to separate the
'   patterns (for example, "*.TXT;*.DOC;*.BAK"). A pattern string can be a combination of
'   valid file name characters and the asterisk (*) wildcard character. Do not include spaces
'   in the pattern string.
'   The system does not change the order of the filters. It displays them in the File Types
'   combo box in the order specified in wszFilter. If wszFilter is NULL, the dialog box
'   does not display any filters.
' - wszDefExt: The default extension. GetOpenFileName and GetSaveFileName append this
'   extension to the file name if the user fails to type an extension. This string can be
'   any length, but only the first three characters are appended. The string should not
'   contain a period (.). If this member is NULL and the user fails to type an extension,
'   no extension is appended.
' - pdwFlags: A set of bit flags you can use to initialize the dialog box. When the dialog
'   box returns, it sets these flags to indicate the user's input. For example, to check
'   if the user has checked the read only checkbox:
'   IF (pdwFlags AND OFN_READONLY) = OFN_READONLY THEN ...
'   This value can be a combination of the following flags:
'   See complete list and explanations at:
'   https://msdn.microsoft.com/en-us/library/windows/desktop/ms646839(v=vs.85).aspx
' - pdwBufLen: The size of the buffer, in charactersm where the names of the selected
'   files will be returned.
' Return value:
'   An string containing a comma separated list of the selected files.
'   Parse the number of ",". If only one, then the user has selected only a file and the
'   string contains the full path. If more, The first substring contains the path and the
'   others the files.
'   If the user has not selected any file, an empty string is returned.
'   On failure, an empty string is returned and, if not null, the pdwBufLen parameter will
'   be filled by the size of the required buffer in characters.
' Usage example:
'   DIM wszFile AS WSTRING * 260 = "*.*"
'   DIM wszInitialDir AS STRING * 260 = CURDIR
'   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_ALLOWMULTISELECT
'   DIM cws AS CWSTR = AfxOpenFileDialog(hwnd, "", wszFile, wszInitialDir, wszFilter, "BAS", @dwFlags, NULL)
'   AfxMsg cws
' ========================================================================================
PRIVATE FUNCTION AfxOpenFileDialog ( _
   BYVAL hwndOwner AS HWND _                    ' // Parent window
 , BYREF wszTitle AS WSTRING _                  ' // Caption
 , BYREF wszFile AS WSTRING _                   ' // Filename
 , BYREF wszInitialDir AS WSTRING _             ' // Start directory
 , BYREF wszFilter AS WSTRING _                 ' // Filename filter
 , BYREF wszDefExt AS WSTRING _                 ' // Default extension
 , BYVAL pdwFlags AS DWORD PTR = NULL _         ' // Flags
 , BYVAL pdwBufLen AS DWORD PTR = NULL _        ' // Buffer length
 ) AS CWSTR

   DIM dwFlags AS DWORD, dwBufLen AS DWORD
   IF pdwFlags THEN dwFlags = *pdwFlags
   IF pdwBufLen THEN dwBufLen = *pdwBuflen

   ' // Filter is a sequence of WSTRINGs with a final (extra) double null terminator
   ' // The "|" characters are replaced with nulls
   DIM wszMarkers AS WSTRING * 4 = "||"
   IF RIGHT(wszFilter, 1) <> "|" THEN wszMarkers += "|"
   DIM cwsFilter AS CWSTR = wszFilter & wszMarkers
   DIM dwFilterStrSize AS DWORD = LEN(cwsFilter)
   ' // Replace markers("|") with nulls
   DIM pchar AS WCHAR PTR = *cwsFilter
   FOR i AS LONG = 0 TO LEN(cwsFilter) - 1
      IF pchar[i] = ASC("|") THEN pchar[i] = 0
   NEXT

   ' // If the initial directory has not been specified, assume the current directory
   ' // FreeBasic 64 bit fails if we pass an empty string and we try to modify wszInitialDir
   DIM _wszInitialDir AS WSTRING * MAX_PATH = wszInitialDir
   IF LEN(_wszInitialDir) = 0 THEN _wszInitialDir = CURDIR
   ' // The size of the buffer must be at least MAX_PATH characters
   IF dwBufLen = 0 THEN
      IF (dwFlags AND OFN_ALLOWMULTISELECT = OFN_ALLOWMULTISELECT) THEN dwBufLen = 32768  ' // 64 Kb buffer
   END IF
   IF dwBufLen < 260 THEN dwBufLen = 260   ' // Make room for at least one path
   ' // Allocate the file name and a marker ("|") to be replaced with a null
   DIM cwsFile AS CWSTR = wszFile & "|"
   ' // Store the position of the marker
   DIM cbPos AS LONG = LEN(cwsFile) - 1
   ' // Allocate room for the buffer
   IF LEN(cwsFile) < dwBufLen THEN cwsFile += SPACE(dwBufLen - LEN(cwsFile))
   DIM dwFileStrSize AS DWORD = LEN(cwsFile)
   ' // The filename must be null terminated (replace the marker with a null)
   pchar = *cwsFile
   pchar[cbPos] = 0

   ' // Fill the members of the structure
   DIM ofn AS OPENFILENAMEW
   ofn.lStructSize     = SIZEOF(ofn)
   IF AfxWindowsVersion < 5 THEN ofn.lStructSize = 76
   ofn.hwndOwner       = hwndOwner
   ofn.lpstrFilter     = *cwsFilter
   ofn.nFilterIndex    = 1
   ofn.lpstrFile       = *cwsFile
   ofn.nMaxFile        = dwFileStrSize
   ofn.lpstrInitialDir = @_wszInitialDir
   IF LEN(wszTitle) THEN ofn.lpstrTitle = @wszTitle
   ofn.Flags = dwFlags OR OFN_EXPLORER
   IF LEN(wszDefExt) THEN ofn.lpstrDefExt = @wszDefExt

   ' // Call the open file dialog
   IF GetOpenFilenameW(@ofn) THEN
      pchar = *cwsFile
      FOR i AS LONG = 0 TO dwFileStrSize - 1
         ' // If double null, exit
         IF pchar[i] = 0 AND pchar[i + 1] = 0 THEN EXIT FOR
         ' // Replace null with ","
         IF pchar[i] = 0 THEN pchar[i] = ASC(",")
      NEXT
      ' // Trim trailing spaces
      cwsFile = RTRIM(cwsFile, CHR(32))
      IF RIGHT(**cwsFile, 1) = "," THEN cwsFile = LEFT(**cwsFile, LEN(cwsFile) - 1)
   ELSE
      ' // Buffer too small
      IF CommDlgExtendedError = FNERR_BUFFERTOOSMALL THEN
         dwBufLen = ASC(**cwsFile)
      END IF
      cwsFile = ""
   END IF

   ' // Return the retrieved values
   IF pdwFlags THEN *pdwFlags = ofn.Flags
   IF pdwBufLen THEN *pdwBufLen = dwBufLen
   RETURN cwsFile

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

' ========================================================================================
' The parameters are the same that for AfxOpenFileDialog, except the optional pdwBufferLen.
' In the pdwFlags parameter you may add OFN_OVERWRITEPROMPT to be asked if you want to
' overwrite an existing file.
' Usage example:
'   DIM wszFile AS WSTRING * 260 = "*.*"
'   DIM wszInitialDir AS STRING * 260 = CURDIR
'   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, wszInitialDir, wszFilter, "BAS", @dwFlags)
'   AfxMsg cws
' ========================================================================================
PRIVATE FUNCTION AfxSaveFileDialog ( _
   BYVAL hwndOwner AS HWND _                    ' // Parent window
 , BYREF wszTitle AS WSTRING _                  ' // Caption
 , BYREF wszFileName AS WSTRING _               ' // Filename
 , BYREF wszInitialDir AS WSTRING _             ' // Start directory
 , BYREF wszFilter AS WSTRING _                 ' // Filename filter
 , BYREF wszDefExt AS WSTRING _                 ' // Default extension
 , BYVAL pdwFlags AS DWORD PTR = NULL _         ' // Flags
 ) AS CWSTR

   DIM dwFlags AS DWORD
   IF pdwFlags THEN dwFlags = *pdwFlags

   ' // Filter is a sequence of WSTRINGs with a final (extra) double null terminator
   ' // The "|" characters are replaced with nulls
   DIM wszMarkers AS WSTRING * 4 = "||"
   IF RIGHT(wszFilter, 1) <> "|" THEN wszMarkers += "|"
   DIM cwsFilter AS CWSTR = wszFilter & wszMarkers
   DIM dwFilterStrSize AS DWORD = LEN(cwsFilter)
   ' // Replace markers("|") with nulls
   DIM pchar AS WCHAR PTR = *cwsFilter
   DIM i AS LONG
   FOR i = 0 TO LEN(cwsFilter) - 1
      IF pchar[i] = ASC("|") THEN pchar[i] = 0
   NEXT

   ' // If the initial directory has not been specified, assume the current directory
   ' // FreeBasic 64 bit fails if we pass an empty string and we try to modify wszInitialDir
   DIM _wszInitialDir AS WSTRING * MAX_PATH = wszInitialDir
   IF LEN(_wszInitialDir) = 0 THEN _wszInitialDir = CURDIR
   DIM wszFile AS WSTRING * MAX_PATH = wszFileName
   DIM cwsFile AS CWSTR = wszFile & "|"
   ' // Store the position of the marker
   DIM cbPos AS LONG = LEN(cwsFile) - 1
   ' // Allocate room for the buffer
   IF LEN(cwsFile) < MAX_PATH THEN cwsFile += SPACE(MAX_PATH - LEN(cwsFile))
   DIM dwFileStrSize AS DWORD = LEN(cwsFile)
   ' // The filename must be null terminated (replace the marker with a null)
   pchar = *cwsFile
   pchar[cbPos] = 0

   ' // Fill the members of the structure
   DIM ofn AS OPENFILENAMEW
   ofn.lStructSize     = SIZEOF(ofn)
   IF AfxWindowsVersion < 5 THEN ofn.lStructSize = 76
   ofn.lpstrFilter     = *cwsFilter
   ofn.nFilterIndex    = 1
   ofn.lpstrFile       = *cwsFile
   ofn.nMaxFile        = dwFileStrSize
   ofn.lpstrInitialDir = @_wszInitialDir
   IF LEN(wszTitle) THEN ofn.lpstrTitle = @wszTitle
   ofn.Flags = dwFlags OR OFN_EXPLORER
   IF LEN(wszDefExt) THEN ofn.lpstrDefExt = @wszDefExt

   ' // Call the save filename dialog
   IF GetSaveFilenameW(@ofn) = 0 THEN cwsFile = ""

   ' // Return the retrieved values
   IF pdwFlags THEN *pdwFlags = ofn.Flags
   RETURN cwsFile

END FUNCTION
' ========================================================================================
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AfxOpenFileDialog

Post by deltarho[1859] »

Thanks José. That took a bit of doing - I have UAC at full throttle and Afx is in "C:\Program Files (x86)". Anyway I got there.

I am using FB 32. I am not crashing on an empty initial directory now.

I have an application in two folders, Bob and Alice. I am simulating Bob and Alice exchanging encrypted data. The first time I use the Open File Dialog in either folder the opening directory is the application folder in each case. If I then navigate to a target file not in either folder and select it I then get another Open File Dialog requesting a task key and that opens in the application folder; where the task keys are. This behavior is exactly as I want.

However, if I introduce another member of the 'syndicate' called Jack, say, and create a folder for him then the first time I use the Open File Dialog in that folder the opening directory is of the target directory I used when in either Bob or Alice and not Jack's folder.

Windows 7 changed the way that the initial directory is chosen. To my mind it was an exceptionally stupid change and they should have left well alone - it was working fine with Windows 2000/XP/Vista. It did not break pre Windows 7 code but that code no longer behaves as expected.

What I will do is delete Bob's, Alice's and Jack's folders and start again to see if I can fathom out what is going on.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: AfxOpenFileDialog

Post by Josep Roca »

> Thanks José. That took a bit of doing - I have UAC at full throttle and Afx is in "C:\Program Files (x86)". Anyway I got there.

If I were you, I would choose a different folder.

AfxOpenFileDialog uses CURDIR if you pass an empty string.

The crash with FB 64 bit was my fault, but as it works with 32 bit and it worked with both 32/64 in Windows 7, I wasn't aware of it. This month I have started to use Windows 10. I will have to use CONST AS WSTRING from now to avoid these bugs.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AfxOpenFileDialog

Post by deltarho[1859] »

Jose Roca wrote:AfxOpenFileDialog uses CURDIR if you pass an empty string.
I know. I tried an empty string as an experiment and mentioned it because it crashed.

I am actually using KeyPath = AfxGetExePath, the application's folder.

This is the problem I have (from OPENFILENAME structure at MSDN)

1. If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory. [1]

Pre Windows 7 we had:
1. If lpstrFile contains a path, that path is the initial directory.
2. Otherwise, lpstrInitialDir specifies the initial directory.

What I want is the pre Windows 7 algorithm.

I created three new folders for Alice, Bob and Jack. I got Alice to choose a target file well from the three folders. The task key request saw the target file folder open and not the application folder in accordance with [1]. Bob and Jack first use of Open go to the same target file folder.

I am sorry Microsoft but that is not an application memory but a global memory. Why would anyone want to use a global MRU?

PowerBASIC's 'DISPLAY OPENFILE' is proprietary and embedded in the compiler; which has not changed for a few years. Perhaps it is locked into the 'old way' of doing things.

I am tempted to write a PowerBASIC dll < Yer gotta laugh>
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: AfxOpenFileDialog

Post by deltarho[1859] »

I have just run this in PowerBASIC:

Code: Select all

#COMPILE EXE "test.exe"
#DIM ALL
#INCLUDE "win32api.inc"

%OfdStyleOpen = %OFN_EXPLORER OR %OFN_FILEMUSTEXIST OR %-OFN_HIDEREADONLY

FUNCTION PBMAIN () AS LONG
Local szFileName As AsciiZ * %Max_Path
Display OpenFile 0, , , "Test", "F:\alicex", Chr$( "All files (*.*)", 0, "*.*", 0), "", "", %OfdStyleOpen To szFileName
msgbox szfilename
Display OpenFile 0, , , "test", "F:\alicex", Chr$( "All files (*.*)", 0, "*.*", 0), "", "", %OfdStyleOpen To szFileName
msgbox szfilename
Display OpenFile 0, , , "Test", "", Chr$( "All files (*.*)", 0, "*.*", 0), "", "", %OfdStyleOpen To szFileName
msgbox szfilename
End Function
The first Open was alicex. I selected a file miles away.
The second Open was 'unphased' with where I had been and opened in alicex.
The third Open was given an empty string for the initial folder and it opened in the application's directory.

PowerBASIC is using old technology.

I have just read a post, going back a few years, where one guy described the Windows 7 algorithm as 'sagacity' - my very sentiment. One poor guy was getting hammered by his customers and it had nothing to do with him.
Post Reply