Software Archive
Read-only legacy content
17061 Discussions

DlgDirList and 'copy a+b c'

Intel_C_Intel
Employee
735 Views
We are porting our old DOS code to Windows and use a simple dialog application that perfectly serves for almost all of our tasks.
The problems are following:

1. how to fill the list box with the list of files of any chosen by the user directory. I found the description of DlgDirList function, but have problems to call it from the dialog application.
2. what is the best way to merge selected files in the list box into one (like 'copy a+b c') and use this temporal file for calculations, i.e not to invoke old DOS commands with SYSTEM calls.

Thank you. Any hints or samples would be well received.

Andrei Nikouline
okean@compuserve.com
0 Kudos
9 Replies
Jugoslav_Dujic
Valued Contributor II
735 Views
Here's a sample code using DlgDirList in a dialog based application:

 
iDummy=DlgInit(IDD_DIALOG1, Dlg) 
iDummy=DlgSetSub(Dlg, IDD_DIALOG1, OnDlgInit) 
iDummy=DlgSetSub(Dlg, IDOK, OnDlgOK) 
 
iDummy=DlgModal(Dlg) 
 
CALL DlgUnInit(Dlg) 
!================================================ 
SUBROUTINE OnDlgInit(Dlg, ID, iEvent) 
USE DFWIN 
USE DFLOGM 
INCLUDE "Resource.fd" 
 
TYPE(DIALOG)::         Dlg 
INTEGER::              ID, iEvent 
 
CHARACTER(MAX_PATH)::  szCurrDir, szPathSpec 
INTEGER::              iDummy 
 
szPathSpec = "*.*"//CHAR(0) 
 
!iDummy = GetCurrentDirectory(MAX_PATH, szCurrDir) 
!szPathSpec = szCurrDir(1:SCAN(szCurrDir,CHAR(0))-1) 
!szPathSpec = TRIM(szCurrDir)//"*.*"//CHAR(0) 
 
iDummy=DlgDirList(Dlg%hWnd, szPathSpec, IDC_LIST1, 0, & 
                  DDL_ARCHIVE.OR.DDL_READWRITE) 
 
END SUBROUTINE OnDlgInit 
!================================================ 
SUBROUTINE OnDlgOK(Dlg, ID, iEvent) 
USE DFWIN 
USE DFLOGM 
INCLUDE "Resource.fd" 
 
TYPE(DIALOG)::         Dlg 
INTEGER::              ID, iEvent 
 
INTEGER::              iDummy, nItems, i 
CHARACTER(32)::        szFile 
 
nItems=SendMessage(GetDlgItem(Dlg%hWnd,IDC_LIST1), LB_GETCOUNT, 0, 0) 
DO i=0,nItems-1 
      iDummy=SendMessage(GetDlgItem(Dlg%hWnd,IDC_LIST1), LB_GETSEL, i, 0) 
      IF (iDummy.NE.0) THEN      !Item is selected 
            iDummy=SendMessage(GetDlgItem(Dlg%hWnd,IDC_LIST1), LB_GETTEXT, i, LOC(szFile)) 
            !Cut trailing char(0) from szFile 
            szFile=szFile(1:SCAN(szFile,CHAR(0))-1) 
            !TODO Do something with szFile here 
      END IF 
END DO 
 
CALL DlgExit(Dlg) 
 
END SUBROUTINE OnDlgOK 


Few notes:

- As you can see, you must call DlgDirList in dialog initialization callback. It will fail in the main routine, because at that moment dialog is not alive yet.

- Since DlgDirList won't fill DLG_NUMITEMS property for the list, you can't retrieve the selected strings (the list box is Multiple selection) using DlgGet. You have to do it using SendMessage while dialog (and thus list) is still alive. In the sample, it's in callback for OK button, of course, it could be any callback.

- In the sample, current directory cannot be changed. You may provide, say,
a "Browse" button which will call ShBrowseForFolder in order to enable user to change current directory. Unfortunately, I don't have a sample for ShBrowseForFolder at hand (my regular machine is dead by Monday at least);please repost if you're interested.

- Finally, you may consider using GetOpenFileName with OFN_ALLOWMULTISELECT flag, which would provide you full functionality you need instead of coping with DlgDirList.


As for merging files, I don't have any better idea currently.

HTH
Jugoslav
0 Kudos
Intel_C_Intel
Employee
735 Views
Jugoslav, thank you a lot for the sample and advice.
This kind of help is invaluable for beginners in API like me.
DlgDirList works fine now and I'd like to confirm my interest in the mentioned by you sample for ShBrowseForFolder.

>you may consider using GetOpenFileName with
>OFN_ALLOWMULTISELECT flag, which would provide you full
>functionality you need instead of coping with DlgDirList.

After declaring the flags in the structure as ofn%Flags = IOR(OFN_ALLOWMULTISELECT,(IOR(OFN_EXPLORER,OFN_READONLY)))
we obtain the string with the path to folder and selected files divided by NULLs. Here are two questions more.

1. Following CVF sample, when I try to change the declaration of the string variable to return the file specification from 'character*512 :: file_spec' to 'character(len=*)...' the compiler doesn't permit it and it would be convenient to have variable length regarding the number of selected files we could have (up to 200).
2. As far as I understand, now we need to parse this string into an array
containing file names with unknown length and size. Is there any special function to perform this action or should I parse 'manually', calculating first the number of substrings (filenames), maximum substring length and then allocating and filling correspondent array?

TIA, Andrei
okean@compuserve.com
0 Kudos
Jugoslav_Dujic
Valued Contributor II
735 Views
 
!======================================================================
SUBROUTINE OnBrowse(Dlg,ID,iAction)

USE DFWIN
USE DLGMOD
USE GLOBALS
USE STRINGS

IMPLICIT NONE

TYPE(Dialog):: Dlg
INTEGER,INTENT(IN):: ID,iAction

TYPE T_BROWSEINFO
INTEGER hwndOwner
INTEGER pidlRoot
INTEGER pszDisplayName ! Return display name of item selected.
INTEGER lpszTitle ! text to go in the banner over the tree.
INTEGER ulFlags ! Flags that control the return stuff
INTEGER lpfn
INTEGER lParam ! extra info that's passed back in callbacks
INTEGER iImage ! output var: where to return the Image index.
END TYPE T_BROWSEINFO

TYPE T_SHITEMID ! mkid
INTEGER(2) cb ! size of identifier, including cb itself
BYTE abID ! variable length item identifier
END TYPE T_SHITEMID

!DEC$OBJCOMMENT LIB: 'OLE32.lib'

INTERFACE
INTEGER FUNCTION SHBrowseForFolder(BI)
!MS$ATTRIBUTES STDCALL,ALIAS: '_SHBrowseForFolderA@4':: SHBrowseForFolder
INTEGER BI
END FUNCTION SHBrowseForFolder
END INTERFACE

INTERFACE
INTEGER FUNCTION SHGetPathFromIDList(lpIDList,szDir)
!MS$ATTRIBUTES STDCALL,ALIAS: '_SHGetPathFromIDListA@8':: SHGetPathFromIDList
!MS$ATTRIBUTES REFERENCE:: szDir
INTEGER lpIDList
CHARACTER*(*) szDir
END FUNCTION
END INTERFACE

INTERFACE
SUBROUTINE CoTaskMemFree(lpIDList)
!MS$ATTRIBUTES STDCALL,ALIAS: '_CoTaskMemFree@4':: CoTaskMemFree
INTEGER lpIDList
END SUBROUTINE
END INTERFACE

CHARACTER*15:: szHeader='Select Folder'C
TYPE (T_BROWSEINFO):: BI
INTEGER:: lpIDList,iSt
LOGICAL:: bSt
INTERFACE
INTEGER FUNCTION BrowseCallbackProc(hwnd, uMsg, lp, pData)
!DEC$ATTRIBUTES STDCALL:: BrowseCallbackProc
INTEGER:: hWnd, uMsg, lp, pData
END FUNCTION
END INTERFACE

INCLUDE 'Resource.fd'

szInitDir=TRIM(szInitDir)//CHAR(0)
BI%hwndOwner=Dlg%hWnd
BI%pidlRoot=NULL
BI%pszDisplayName=LOC(szInitDir)
BI%lpszTitle=LOC(szHeader)
BI%ulFlags=0
BI%lpfn=LOC(BrowseCallbackProc)
BI%lParam=0
BI%iImage=0

lpIDList=SHBrowseForFolder(LOC(BI))
bSt=SHGetPathFromIDList(lpIDList,szInitDir)

CALL CoTaskMemFree(lpIDList)

CALL C2F(szInitDir)

iSt=DlgSet(Dlg,IDC_EDIT_DIR,szInitDir)
CALL FindFilesInDir(Dlg,szInitDir)

END SUBROUTINE OnBrowse
!=================================================
INTEGER FUNCTION BrowseCallbackProc(hwnd, uMsg, lp, pData)
!DEC$ATTRIBUTES STDCALL:: BrowseCallbackProc

USE GLOBALS

IMPLICIT NONE

INTEGER:: hWnd, uMsg, lp, pData

INTEGER:: iSt

! message from browser
INTEGER, PARAMETER:: BFFM_INITIALIZED =1
INTEGER, PARAMETER:: BFFM_SELCHANGED =2

! messages to browser
INTEGER, PARAMETER:: BFFM_SETSTATUSTEXTA =(WM_USER + 100)
INTEGER, PARAMETER:: BFFM_ENABLEOK =(WM_USER + 101)
INTEGER, PARAMETER:: BFFM_SETSELECTIONA =(WM_USER + 102)
INTEGER, PARAMETER:: BFFM_SETSELECTIONW =(WM_USER + 103)
INTEGER, PARAMETER:: BFFM_SETSTATUSTEXTW =(WM_USER + 104)

INTEGER, PARAMETER:: BFFM_SETSTATUSTEXT =BFFM_SETSTATUSTEXTA
INTEGER, PARAMETER:: BFFM_SETSELECTION =BFFM_SETSELECTIONA
INTEGER, PARAMETER:: BFFM_VALIDATEFAILED =BFFM_VALIDATEFAILEDA

INTERFACE
INTEGER FUNCTION SHGetPathFromIDLi
0 Kudos
Jugoslav_Dujic
Valued Contributor II
735 Views
...continued here, since the previous message seems to have
exceeded some forum limit:

 
!================================================= 
INTEGER FUNCTION BrowseCallbackProc(hwnd, uMsg, lp, pData)  
!DEC$ATTRIBUTES STDCALL::  BrowseCallbackProc 
 
USE GLOBALS 
 
IMPLICIT NONE 
 
INTEGER:: hWnd, uMsg, lp, pData 
 
INTEGER:: iSt 
 
! message from browser 
INTEGER, PARAMETER:: BFFM_INITIALIZED        =1 
INTEGER, PARAMETER:: BFFM_SELCHANGED         =2 
 
! messages to browser 
INTEGER, PARAMETER:: BFFM_SETSTATUSTEXTA     =(WM_USER + 100) 
INTEGER, PARAMETER:: BFFM_ENABLEOK           =(WM_USER + 101) 
INTEGER, PARAMETER:: BFFM_SETSELECTIONA      =(WM_USER + 102) 
INTEGER, PARAMETER:: BFFM_SETSELECTIONW      =(WM_USER + 103) 
INTEGER, PARAMETER:: BFFM_SETSTATUSTEXTW     =(WM_USER + 104) 
 
INTEGER, PARAMETER:: BFFM_SETSTATUSTEXT  =BFFM_SETSTATUSTEXTA 
INTEGER, PARAMETER:: BFFM_SETSELECTION   =BFFM_SETSELECTIONA 
INTEGER, PARAMETER:: BFFM_VALIDATEFAILED =BFFM_VALIDATEFAILEDA 
 
INTERFACE  
      INTEGER FUNCTION SHGetPathFromIDList(lpIDList,szDir) 
      !MS$ATTRIBUTES STDCALL,ALIAS: '_SHGetPathFromIDListA@8':: SHGetPathFromIDList 
      !MS$ATTRIBUTES REFERENCE::  szDir 
      INTEGER lpIDList 
      CHARACTER*(*) szDir 
      END FUNCTION  
END INTERFACE 
 
SELECT CASE(uMsg)  
CASE (BFFM_INITIALIZED) 
      iSt=SendMessage(hwnd, BFFM_SETSELECTION, 1, LOC(szInitDir)) 
END SELECT 
BrowseCallbackProc=0 
 
END FUNCTION BrowseCallbackProc 


As for GetOpenFileName, no, strings cannot be of ALLOCATABLE length in F95. F2000 standard will allow for that, but we'll gona all wait quite a bit on that. You should parse the string manually -- the method I use is SCAN function. Docs on OPENFILENAME say that the last file name is terminated by double zero, so the code should be something like (written ad hoc, sorry if there are errors):

 
nBegin=1                     !Beginning of new file 
nFiles=0                       !Total no. of files  
DO i = 1, LEN(szFiles)     !String to search 
    nNextZero = SCAN( szFiles(nBegin:), CHAR(0) ) 
    IF (nNextZero-nLastZero .LE. 1) EXIT  !Terminal double zero 
    sMyFile(i) = szFiles(nBegin:nNextZero-1) 
    nFiles = nFiles + 1 
    nBegin = nNextZero + 1 
END DO 


Regards

Jugoslav
0 Kudos
Intel_C_Intel
Employee
735 Views
Hi,

Just a note about SHBrowseForFolder. I didn't notice it in Jugoslav's post (I'm sure it's elsewhere in his code) but CoInitialize must be called before calling SHBrowseForFolder.

-John

PS - I like the hook procedure to select the initial directory. I think I'll add it to my code. ;-)
0 Kudos
Intel_C_Intel
Employee
735 Views
Hi, Jugoslav. Thank you once more for the sample of SHBrowseForFolder.
I'll need time to try it and understand better.
Meanwhile I parsed the string of selected files by GetOpenFileName and constructed the system command like 'copy a.txt + b.txt temp.txt'. This command is located in a big string variable of length 8192.
Following compiler's recommendations I tried (instead of using SYSTEMQQ)
to create the file temp.txt with merged data, passing the part of string with command to the following subroutine containing CreateProcess function:

SUBROUTINE Do_exe(cmdline)
use dfwin
character(len=*), intent (in) :: cmdline
logical(4) :: lret
character, pointer :: AppName
type(T_STARTUPINFO) si
type(T_PROCESS_INFORMATION) pi
type(T_SECURITY_ATTRIBUTES), pointer :: sa
si%cb = 68 ; si%wShowWindow = SW_HIDE
NULLIFY(AppName)
lret = CreateProcess(AppName, cmdline, &
sa, sa, .FALSE., CREATE_SEPARATE_WOW_VDM, NULL, AppName, si, pi)
END SUBROUTINE Do_exe

It works well with EXE files but doesn't accept the string with system command. Is it possible to use CreateProcess to invoke DOS commands? If so, what am I doing wrong.

I very appreciate your help and don't want to bother at the same time. So it is my last question in this thread.

TIA, Andrei
okean@compuserve.com
0 Kudos
Intel_C_Intel
Employee
735 Views
You need to invoke the command interpreter when you use commands like copy. So, for example these would work as your commandline

'cmd /k copy a.txt + b.txt temp.txt'

'command /c copy a.txt + b.txt temp.txt'

I can't remember if Win9x supports cmd though, so choose you poison.

hth,
John
0 Kudos
Jugoslav_Dujic
Valued Contributor II
735 Views
SYSTEMQQ DFLIB function lets you do that without knowing what command interpreter is. If you prefer CreateProcess (which gives you finer control), name of command interpreter can be obtained by querying value of environment variable COMSPEC using GETENV, GETENVQQ or GetEnvironmentVariable.

Jugoslav
0 Kudos
Intel_C_Intel
Employee
735 Views
> 'cmd /k copy a.txt + b.txt temp.txt'
> I can't remember if Win9x supports cmd ...

I tried it before and forgot to put /k (or better /c).
Yes, cmd is not supported by Win9*.

'command /c copy a.txt + b.txt temp.txt' doesn't work for me. On the other hand,
no problems with p.e. 'command /c dir > temp.txt'.

At last, I merged files simply opening and reading them in Fortran and
am asking myself, why haven't done it before.

Thank you John and Jugoslav,

Andrei
0 Kudos
Reply