FileDialog Multiselect

Started by Nicola, November 10, 2023, 10:20:29 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Nicola

Hi Charles,
Speaking of FileDialogs, could you include the ability to select multiple files and have an array that contains the DIR and the files that have been selected?

I found this example that, given my still lack of knowledge of O2, I can't translate.

Could this be a nice implementation?


      ofn.Flags = OFN_EXPLORER | OFN_ALLOWMULTISELECT;
        if (GetOpenFileName(&ofn)==TRUE)
        {
        printf("files selected\n");
        char* ptr = ofn.lpstrFile;
        ptr[ofn.nFileOffset-1] = 0;
        printf("Directory path: %s\n", ptr);
        ptr += ofn.nFileOffset;
       
        while (*ptr)
            {
            fCount++;          
            printf("File: %i %s\n", fCount, ptr);
            ptr += (lstrlen(ptr)+1);
            }
           
        printf("\n");              
        printf("selected %i files\n", fCount); 
        }
        else
        {
        printf("no files selected\n");
        }
       
        break;


Charles Pegge

Hi Nicola,

You can select only 1 block of files on each dialog call, but not multiple blocks:

uses FileDialogs
uses console
OPENFILENAME ofn
char FileBuf[2048]
int  FileBufLen=2048
'
 ofn.lStructSize       = sizeof(OPENFILENAME)
'ofn.hwndOwner         = hWnd
 ofn.hInstance         = GetModuleHandle
'ofn.lpstrFilter       = filter
 ofn.lpstrCustomFilter = null
 ofn.nMaxCustFilter    = 0
'ofn.nFilterIndex      = 1
 ofn.lpstrFile         = FileBuf 'coupling to char buffer
 ofn.nMaxFile          = FileBufLen
 ofn.lpstrFileTitle    = null
 ofn.nMaxFileTitle     = 0
'ofn.lpstrInitialDir   = idir
 ofn.lpstrTitle        = "Multi-Select"
 ofn.Flags             = OFN_EXPLORER | OFN_ALLOWMULTISELECT
 ofn.nFileOffset       = 0
 ofn.nFileExtension    = 0
 ofn.lpstrDefExt       = null
 ofn.lCustData         = 0
 ofn.lpfnHook          = 0
 ofn.lpTemplateName    = null


if GetOpenFileName ofn
  print "files selected" cr
  char*pt
  @pt=@FileBuf
  int j
  int fcount
  j=ofn.nFileOffset
  print "Directory path: "+left(pt,j)+cr
  @pt+=j     
  while asc(pt)
    fCount++
    print "File: " fCount+tab+pt+cr
    @pt+=len(pt)+1
  wend
  print cr
  print "selected " fcount " files"+cr
else
  print "no files selected"+cr
endif
wait


Nicola

Great Charles. :)

I tried putting this in the FileDialogs. I got this...
Unfortunately, I don't know how to get an array to return from a function.

'no case sensitivity

#ifndef HAS_COREWIN
  uses corewin
#endif

% OFN_READONLY                 0x00000001
% OFN_OVERWRITEPROMPT          0x00000002
% OFN_HIDEREADONLY             0x00000004
% OFN_NOCHANGEDIR              0x00000008
% OFN_SHOWHELP                 0x00000010
% OFN_ENABLEHOOK               0x00000020
% OFN_ENABLETEMPLATE           0x00000040
% OFN_ENABLETEMPLATEHANDLE     0x00000080
% OFN_NOVALIDATE               0x00000100
% OFN_ALLOWMULTISELECT         0x00000200
% OFN_EXTENSIONDIFFERENT       0x00000400
% OFN_PATHMUSTEXIST            0x00000800
% OFN_FILEMUSTEXIST            0x00001000
% OFN_CREATEPROMPT             0x00002000
% OFN_SHAREAWARE               0x00004000
% OFN_NOREADONLYRETURN         0x00008000
% OFN_NOTESTFILECREATE         0x00010000
% OFN_NONETWORKBUTTON          0x00020000
% OFN_NOLONGNAMES              0x00040000     '// force no long names for 4.x modules
% OFN_EXPLORER                 0x00080000     '// new look commdlg
% OFN_NODEREFERENCELINKS       0x00100000
% OFN_LONGNAMES                0x00200000     '// force long names for 3.x modules
% OFN_ENABLEINCLUDENOTIFY      0x00400000     '// send include message to callback
% OFN_ENABLESIZING             0x00800000
% OFN_DONTADDTORECENT          0x02000000
% OFN_FORCESHOWHIDDEN          0x10000000     '// Show All files including System and hidden files


type OPENFILENAMEA
  DWORD  lStructSize
  SYS    hwndOwner
  SYS    hInstance
  CHAR*  lpstrFilter
  CHAR*  lpstrCustomFilter
  DWORD  nMaxCustFilter
  DWORD  nFilterIndex
  CHAR*  lpstrFile
  DWORD  nMaxFile
  CHAR*  lpstrFileTitle
  DWORD  nMaxFileTitle
  CHAR*  lpstrInitialDir
  CHAR*  lpstrTitle
  DWORD  Flags
  WORD   nFileOffset
  WORD   nFileExtension
  CHAR*  lpstrDefExt
  LONG   lCustData
  SYS    lpfnHook
  CHAR*  lpTemplateName
end type

typedef OPENFILENAMEA OPENFILENAME

Declare GetModuleHandle      lib "kernel32.dll" alias "GetModuleHandleA" (optional char*n) as sys
Declare GetOpenFileName      Lib "comdlg32.dll" Alias "GetOpenFileNameA" (OPENFILENAME*opfn) As sys
Declare GetSaveFileName      Lib "comdlg32.dll" Alias "GetSaveFileNameA" (OPENFILENAME*opfn) As sys
Declare CommDlgExtendedError Lib "comdlg32.dll" () as dword

[font=Verdana, Arial, Helvetica, sans-serif][/font]
[font=Verdana, Arial, Helvetica, sans-serif]'FileDialog( $ iDir , $ filter ,$ title , % parent , flag )[/font]

Function FileDialog(string iDir,filter,Title,Name, sys Hwnd, Flags, a) As String
'===============================================================================
String Selex

OPENFILENAME ofn
int FileNameLen=2048
char         FileName[2048]
if Name then FileName=Name
int          retval

 ofn.lStructSize       = sizeof(OPENFILENAME)
 ofn.hwndOwner         = hWnd
 ofn.hInstance         = GetModuleHandle
 ofn.lpstrFilter       = filter
 ofn.lpstrCustomFilter = null
 ofn.nMaxCustFilter    = 0
 'ofn.nFilterIndex      = 1
 ofn.lpstrFile         = FileName 'coupling to char buffer
 ofn.nMaxFile          = FileNameLen
 ofn.lpstrFileTitle    = null
 ofn.nMaxFileTitle     = 0
 ofn.lpstrInitialDir   = idir
 ofn.lpstrTitle        = title
 ofn.Flags             = flags
 ofn.nFileOffset       = 0
 ofn.nFileExtension    = 0
 ofn.lpstrDefExt       = null
 ofn.lCustData         = 0
 ofn.lpfnHook          = 0
 ofn.lpTemplateName    = null


sys retval
'
if a then
 retval=GetSaveFileName(ofn)
else
 retval=GetOpenFileName(ofn)
end if
'
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms646916(v=vs.85).aspx

if retval
  char*pt
  @pt=@Filename
  int j
  int fcount
  j=ofn.nFileOffset
  Selex = left(pt,j)+";"
  @pt+=j    
  while asc(pt)
    fCount++
    Selex=Selex+pt+";"
    @pt+=len(pt)+1
  wend
  return fCount+";"+Selex
else
  return ""
end if

End Function


Function GetFileName(string name, sys a, string filt="") as string
'=================================================================
'
'dir="OxygenBasic\demos\GUI"
'
sys    hwnd
string filter,dir,title
'
if filt then
  filter=filt
else
  string sep=chr(0)
  filter=
  "all files"+sep+"*.*"+sep+
  "text"+sep+"*.txt"+sep+
  "basic"+sep+"*.bas;*.o2bas"+sep+
  "include"+sep+"*.inc"+sep+
  "header"+sep+"*.h"+sep+
  sep
end if
'
if a then
  title="Save File as"
else
  title="Load File"
end if
'
sys flags = OFN_EXPLORER or OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY
'
return FileDialog(dir,filter,title,name,hwnd,flags,a)
'
end function



'! SHBrowseForFolder Lib "shell32" (BrowseInfo *lpbi) as sys
'! SHGetPathFromIDList Lib "shell32" (sys pidList, sys lpBuffer) As sys



function BrowseCallbackProc(sys hwnd, int uMsg, sys wParam, lParam) as int, callback
====================================================================================
'Used with the BrowseForFolder function to select a default folder
  % BFFM_ENABLEOK       0x465
  % BFFM_SETSELECTION   0x466
  % BFFM_SETSTATUSTEXT  0x464
  % BFFM_INITIALIZED    1
  % BFFM_SELCHANGED     2
  % BFFM_VALIDATEFAILED 3
  '
  if uMsg = BFFM_INITIALIZED
    SendMessage(hwnd, BFFM_SETSELECTION, uMsg, lParam )
  endif
  return 0  ' the function should always return 0
end function


type BrowseInfo
  sys hWndOwner
  sys pIDLRoot
  sys pszDisplayName
  sys lpszTitle
  int ulFlags
  sys lpfnCallback
  sys lParam
  int iImage
end type
'
function BrowseForDir(sys hWnd,  string strTitle="", strInitDir="\" ) as string
===============================================================================
 '% BIF_RETURNONLYFSDIRS = 1
 '% BIF_EDITBOX = &H10
  % MAX_PATH = 260
  '
  BrowseInfo tbi
  tbi.hWndOwner = hwnd
  tbi.lpszTitle = strptr(strTitle)
 'tbi.ulFlags = BIF_RETURNONLYFSDIRS or BIF_EDITBOX
  tbi.lpfnCallback = @BrowseCallbackProc
  tbi.lParam = strptr(strInitDir)
  sys h = SHBrowseForFolder(@tbi)
  if h
    char s[MAX_PATH]
    SHGetPathFromIDList(h, s)
    return s
  endif
end function

'USAGE:
'sys hWnd=0
'print strDir = BrowseForDir(hWnd,"Select a Folder","c:\users")


The program to try I used this:
uses filedialogs_1

string sep=chr(0)
string filter="pdf"+sep+"*.pdf"
string tipOFN, f
sys tipo

int i

sub printout()
[font=Verdana, Arial, Helvetica, sans-serif]   f=FileDialog("C:\",filter,tipofn,"", 0,tipo,0)[/font]
  print f
 
end sub



tipOFN="OFN_EXPLORER or OFN_ALLOWMULTISELECT"
tipo=OFN_EXPLORER or OFN_ALLOWMULTISELECT
printout

tipOFN="OFN_EXPLORER"
tipo=OFN_EXPLORER
printout
ExitProcess(0)

tipOFN="OFN_EXPLORER or OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY or OFN_ALLOWMULTISELECT"
tipo=OFN_EXPLORER or OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY or OFN_ALLOWMULTISELECT
printout


tipOFN="OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY or OFN_ALLOWMULTISELECT"
tipo=OFN_OVERWRITEPROMPT or OFN_HIDEREADONLY or OFN_ALLOWMULTISELECT
printout

tipOFN="OFN_OVERWRITEPROMPT or OFN_ALLOWMULTISELECT"
tipo=OFN_OVERWRITEPROMPT or OFN_ALLOWMULTISELECT
printout

tipOFN="OFN_EXPLORER or OFN_ALLOWMULTISELECT"
tipo=OFN_EXPLORER or OFN_ALLOWMULTISELECT
printout

tipOFN="OFN_ALLOWMULTISELECT or OFN_LONGNAMES"
tipo=OFN_ALLOWMULTISELECT or OFN_LONGNAMES
printout



Charles Pegge

Hi Nicola,

Because it is customized, I would recommend exposing the full OPENFILENAME structure in your application, and process the result directly. Then you can set up a string array to capture the filenames instead of printing them.

But to return an array of strings, from any procedure, deploy the string array param like this:
procedure foo(string *s[])
...
end procedure

dim string f[100]
foo f[]

Nicola

#4
Thanks Charles.

I'm probably saying a lot of nonsense, but wouldn't it be possible to do the opposite? That is, return the pointer to an array worked in the procedure:

procedure foo()
dim s[100]
...
  return *s[]

end procedure

dim string f
*f = foo[]

Charles Pegge

#5
It is possible but there's trouble from the (string) garbage collector so you have to use bstrings and dispose of them yourself.

Also, when working in a multi-threaded application, it is not safe to pass string arrays naively.

Do you want to go down this technical rabbit hole?  :)

Nicola

#6
Of course not...
It's better not to end up like a rabbit. ???

That's why my idea of making a string easily workable with a split return, using a separator character that can't be used for a filename.
The string contains the number of selected files in the first place, then the Directory and then the names of the files.

Pierre Bellisle

#7
fun with GetOpenFileName
//  GetOpenFileName with the OFN_ALLOWMULTISELECT flag

#define OFN_READONLY             0x00000001
#define OFN_OVERWRITEPROMPT      0x00000002
#define OFN_HIDEREADONLY         0x00000004
#define OFN_NOCHANGEDIR          0x00000008
#define OFN_SHOWHELP             0x00000010
#define OFN_ENABLEHOOK           0x00000020
#define OFN_ENABLETEMPLATE       0x00000040
#define OFN_ENABLETEMPLATEHANDLE 0x00000080
#define OFN_NOVALIDATE           0x00000100
#define OFN_ALLOWMULTISELECT     0x00000200
#define OFN_EXTENSIONDIFFERENT   0x00000400
#define OFN_PATHMUSTEXIST        0x00000800
#define OFN_FILEMUSTEXIST        0x00001000
#define OFN_CREATEPROMPT         0x00002000
#define OFN_SHAREAWARE           0x00004000
#define OFN_NOREADONLYRETURN     0x00008000
#define OFN_NOTESTFILECREATE     0x00010000
#define OFN_NONETWORKBUTTON      0x00020000
#define OFN_NOLONGNAMES          0x00040000
#define OFN_EXPLORER             0x00080000
#define OFN_NODEREFERENCELINKS   0x00100000
#define OFN_LONGNAMES            0x00200000
#define OFN_ENABLEINCLUDENOTIFY  0x00400000
#define OFN_ENABLESIZING         0x00800000
#define OFN_DONTADDTORECENT      0x02000000
#define OFN_FORCESHOWHIDDEN      0x10000000

#define HWND_DESKTOP             0x00000000
#define FNERR_BUFFERTOOSMALL     0x00003003
#define NulChar                  chr(0)

typedef struct tagOFNA {  // structure used by GetOpenFileName()
  dword lStructSize;
  sys   hwndOwner;
  sys   hInstance;
  sys   lpstrFilter;
  sys   lpstrCustomFilter;
  dword nMaxCustFilter;
  dword nFilterIndex;
  sys   lpstrFile;
  dword nMaxFile;
  sys   lpstrFileTitle;
  dword nMaxFileTitle;
  sys   lpstrInitialDir;
  sys   lpstrTitle;
  dword Flags;
  word  nFileOffset;
  word  nFileExtension;
  sys   lpstrDefExt;
  sys   lCustData;
  sys   lpfnHook;
  sys   lpTemplateName;
} OPENFILENAMEA, *LPOPENFILENAMEA;

! GetModuleHandle      lib "kernel32.dll" alias "GetModuleHandleA"(optional char*n) as sys
! GetOpenFileName      lib "comdlg32.dll" alias "GetOpenFileNameA"(OPENFILENAMEA*opfn) as sys
! CommDlgExtendedError lib "comdlg32.dll" alias "CommDlgExtendedError"() as dword
'_____________________________________________________________________________

function FileDialog(sys hWin, string title, dir, filter) as string

 string filename          = nuls 1048576 'one meg for multi select filenames, try 5 to see error message.
 OPENFILENAMEA ofn
 ofn.lStructSize          = sizeof(OPENFILENAMEA)
 ofn.hwndOwner            = hWin
 ofn.hInstance            = GetModuleHandle(NulChar)
 ofn.lpstrFilter          = strptr filter
 ofn.lpstrCustomFilter    = null
 ofn.nMaxCustFilter       = 0
 ofn.nFilterIndex         = 1
 ofn.lpstrFile            = strptr filename
 ofn.nMaxFile             = len(filename)
 ofn.lpstrFileTitle       = null
 ofn.nMaxFileTitle        = 0
 ofn.lpstrInitialDir      = strptr dir
 ofn.lpstrTitle           = strptr title
 ofn.Flags                = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_FORCESHOWHIDDEN

 if GetOpenFileName(ofn) then 'zero mean success
  long charpos = instr(filename, NulChar & NulChar ) 'find ending double nul character
  if charpos then filename = left(filename, charpos) 'remove ending zero characters
  long index
  for index = 1 to len(filename)
    if asc(filename, index) = NulChar then asc(filename, index) = 13 'replace characters zero with CR
  next
  return filename 'job done, first line is folder, other are filename
 else 'failed
  long LastError = CommDlgExtendedError()
  if LastError = 0 then
    print "Cancelled by user !"
  elseif LastError = FNERR_BUFFERTOOSMALL then
    'If the buffer is too small, the function returns FALSE and the CommDlgExtendedError function returns FNERR_BUFFERTOOSMALL.
    'In this case, the first two bytes of the lpstrFile buffer contain the required size, in bytes or characters.
    dim NeededLenght as word at strptr filename
    print "CommDlgExtendedError: FNERR_BUFFERTOOSMALL mean ofn.lpstrFile buffer is too small ! Needed minimum " & str(NeededLenght) & " bytes." '12291
  else
    print "CommDlgExtendedError: error #" & str(LastError)
  endif
 endif

end function
'_____________________________________________________________________________

 'code start...
 sys    hWin  = HWND_DESKTOP
 string title  = "Test File Opening Dialog"
 string dir    = "."
 string filter = "all (*.*)"      & NulChar & "*.*"           & NulChar &
                "text (*.txt)"    & NulChar & "*.txt"         & NulChar &
                "basic (*.bas)"   & NulChar & "*.bas;*.o2bas" & NulChar &
                "include (*.inc)" & NulChar & "*.inc"         & NulChar &
                "header (*.h)"    & NulChar & "*.h"           & NulChar & NulChar
 print FileDialog(hWin, title, dir, filter)
'_____________________________________________________________________________
'

Theo Gottwald

Your code appears to be a Windows API-based file dialog function, likely written in a language resembling BASIC or a similar dialect. It's using the OPENFILENAME structure and related functions to create a file open dialog. Here's a breakdown and analysis of key parts of your code:

Constant Definitions: The #define statements define constants typically used with the OPENFILENAME structure, providing various options for the file dialog (like OFN_READONLY, OFN_FILEMUSTEXIST, etc.).

Structure Definition: The tagOFNA struct defines the OPENFILENAMEA structure, which is used by the GetOpenFileName function. This structure holds information the function needs to initialize the dialog.

Function Declarations: The ! prefixed lines declare external functions from Windows DLLs (kernel32.dll and comdlg32.dll). These are likely function prototypes for calling these DLL functions.

FileDialog Function: This is the main function that creates and shows the file dialog. It initializes an OPENFILENAMEA structure and calls GetOpenFileName. If successful, it processes the returned file name(s), replacing NUL characters with carriage returns (CR).

Error Handling: The code checks the return value of GetOpenFileName and uses CommDlgExtendedError to get additional error information if needed.

Buffer Size: The filename string is initially allocated 1 MB, which is quite large but necessary for multi-select scenarios. However, this could be overkill for single file selection and could be optimized.

Test Code: The last part of the script sets up parameters for the FileDialog function and calls it.

Potential Issues and Improvements:
Buffer Size: While 1 MB should be more than sufficient, consider if such a large buffer is always necessary, or if it could be adjusted based on expected usage.

Error Handling: The error handling is basic. It might be beneficial to provide more detailed error messages or handling, especially for other error codes that CommDlgExtendedError might return.

Code Style: The mix of lowercase and uppercase, along with the use of 'sys' and 'string' types, suggests a particular coding style or language requirement. Ensure consistency throughout the code.

Documentation: Adding comments explaining each part of the code, especially around the flags used in the OPENFILENAMEA structure, would make the code more readable and maintainable.

Return Value of GetOpenFileName: The comment zero mean success seems incorrect. Usually, a non-zero value indicates success in Windows API calls. Double-check this.

Compatibility: Ensure that this code is compatible with the versions of Windows you intend to support, especially with regard to the 32-bit and 64-bit differences and any potential Unicode issues.

Overall, the code seems well-structured for its intended purpose. The use of the Windows API is appropriate for a file dialog in a Windows environment, and the function appears to cover the essential features needed for file selection.

Zlatko Vid

Theo
Don't get me wrong ..but why all this long explanation
it is stuff/ things we already know and documentation
we can find on many places..
I build this FileDialog 10 years ago in my include file

Pierre Bellisle

Aurel, 
I do think that Theo really like AI. ~:-)

Theo,
Here is what Bill say about the GetOpenFileName()'s return value.
-If the user specifies a file name and clicks the OK button, the return value is nonzero.
-If the user cancels or closes the Open dialog box or an error occurs, the return value is zero.