Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
29233 Discussões

Estabishing a directory from which to open files using f.InitialzeOpen

TommyCee
Principiante
1.127 Visualizações

In the app I'm building, I'm using the ever-familiar function InitializeOpen to launch Windows' Open dialog:

[bash]integer(4) function InitializeOpen()

use *globals !to pass FileSpec to s.OpenInputFile()

!Declare structure used to pass and receive attributes:

! Declare filter specification.  This is a concatenation of pairs of null-terminated 
! strings.  The first string in each pair is the file type name, the second is a 
! semicolon-separated list of file types for the given name.  The list ends with 
! a trailing null-terminated empty string.

!Set filter to ASCII text file:
character*(*),parameter :: FilterSpec = "APP Input File (*.txt)"C//"*.txt;*.TXT"C//""C

!Declare string variable to return the file specification. Initialize with an initial 
!FileSpec, if any; otherwise null string:

OFN%lStructSize = SizeOf(OFN)
OFN%hWndOwner = NULL  ! For non-console applications, set this to the 
                      ! hWnd of the Owner window, e.g., ghWndMain.

OFN%hInstance = NULL  ! For Win32 applications, you can set this
                      ! to the appropriate hInstance

OFN%lpStrFilter = loc(FilterSpec)
OFN%lpStrCustomFilter = NULL
OFN%nMaxCustFilter = 0
OFN%nFilterIndex = 1          !Specifies initial filter value (see URL)
OFN%lpStrFile = loc(FileSpec) !FileSpec = Full Path FileName
OFN%nMaxFile = SizeOf(FileSpec)
!OFN%LPStrFileTitle = loc(szTitleName//".txt"C) !from OpenScribe2
OFN%nMaxFileTitle = 0         !OpenScribe 2 sets this to 25
OFN%lpStrInitialDir = NULL    !Use Windows default directory
!!OFN%lpStrInitialDir = 'H:'  !Use my H-Drive
OFN%lpStrTitle = loc(""C)     !OpenScribe2 sets this to null
OFN%Flags = OFN_PathMustExist !OpenScribe2 sets this to null
!OFN%NFileOffset = NULL       !from OpenScribe2
!OFN%NFileExtension =NUL      !from OpenScribe2
OFN%lpStrDefExt = loc("txt"C)
!OFN%LCustData = NULL         !from OpenScribe2  
OFN%lpFNHook = NULL
OFN%lpTemplateName = NULL

InitializeOpen = 1

end function InitializeOpen[/bash]

As a default, when a file is opened, the app. points to C:\\My Documents.

After initially browsing to a more ideal directory, I'd like to track browsing information so that, once a file is selected, the app remembers this directory and:

A) returns there when the app is used the next time;

B) points there first when a file is saved (e.g., SaveAs)

Two lines in the function are noteworthy:

OFN%lpStrInitialDir = NULL !Use Windows default directory

OFN%lpStrInitialDir = 'H:\' !Use my H-Drive

The first one may have something to do w/ forcing the default to C:\\My Documents.

The second one seemed like one I could at least set to force it to look in my H-drive, yet when enabled, it triggers a nasty Windows error (which is why I have it commented it out).

It could be that I must use an INI file in conjunction w/ f.InitialzeOpen but I'm not sure how to configure.

Finally, I did search this forum and did not see this question posted by anyone.

Any thoughts/experience are appreciated.

0 Kudos
12 Respostas
dannycat
Novo colaborador I
1.127 Visualizações

Once you have obtained the file you can extract the directory name and store it withinan application "saved" or global variable and this can be used as the initial setting for OFN%lpStrInitialDir next time round. If however you wish to save the name for use next time you run the application you could use the registry (rather than an INI file)to save settings for your program. (There are good examples of using the registry in Norman Lawerence's book Compaq Visual Fortran).

If one only needs to select a directory location (and not any particular file) it gets more problematic. The SHBrowseForFolder function can be used but unfortunately I don't think it is possible to set the initial directory using this. If any one knows how to then please let me know.

I hope this helps.

anthonyrichards
Novo colaborador III
1.127 Visualizações
I presume you use OFN as an argument to GETOPENFILENAME? In my experience, the next time you use GETOPENFILENAME it will open at the directory last chosen, so long as you do not include OFN_NOCHANGEDIR as one of the flags in the OFN structure.
Paul_Curtis
Contribuidor valorado I
1.127 Visualizações

Once a path has been selected, it can then be pre-pended onto filenames to be opened, and saved in an INI file or in the registry for use with subsequent program invocations. If all file opens include the complete file path name, there will be no ambiguity about the "default" path (usually set to the location of the program's executable, which is also easily discoverable in Windows). Here is a wrapper function for selecting a path:

[bash]! Shows a folder chooser shell window. The path to the chosen
! folder is placed in folderPath, which is a character array of
! the specified length. Returns true if the user chose a folder,
! or false if the operation was cancelled. The title caption
! is specified as a string table resource ID in the optional
! argument title.
!
LOGICAL FUNCTION CmnDlgChooseFolder (hwndParent, folderPath, title)
	USE ExtraWinTy
	USE ifcom, only: COMInitialize, COMUnInitialize
    IMPLICIT NONE

    INTEGER(HANDLE), INTENT(IN)          :: hwndParent
    CHARACTER(LEN=*), INTENT(INOUT)      :: folderPath
    INTEGER, INTENT(IN), OPTIONAL		 :: title       ! stringtable id value

    TYPE(T_BROWSEINFO)				  	 :: bi
    CHARACTER(LEN=MAX_PATH)				 :: buffer
    INTEGER								 :: pidl		! pointer to id list
    INTEGER								 :: status, rval2
    CHARACTER(LEN=200)					 :: titleBuffer
    INTEGER								 :: titleId
    TYPE(T_STRRET)					     :: rstring
    INTEGER								 :: IShellFolder_desktop

	CmnDlgChooseFolder = .FALSE.
    
	IF (PRESENT(title)) THEN
        titleId = title
    ELSE
        titleId = IDS_DEFAULT_CHOOSEFOLDER_TITLE
    END IF
    titleBuffer = STGet(titleId, 200)

	CALL COMInitialize (status)

    buffer = folderPath

    !    TYPE T_BROWSEINFO
    !        SEQUENCE
    !        INTEGER                        :: hwndOwner
    !        INTEGER                        :: pidlRoot
    !        INTEGER                        :: pszDisplayName
    !        INTEGER                        :: lpszTitle
    !        INTEGER                        :: ulFlags
    !        INTEGER                        :: lpfn
    !        INTEGER                        :: lParam
    !        INTEGER                        :: iImage
    !    END TYPE T_BROWSEINFO

    bi%hwndOwner      = hwndParent
    bi%pidlRoot       = NULL
    bi%pszDisplayName = LOC(buffer)
    bi%lpszTitle      = LOC(titleBuffer)
    bi%ulFlags        = BIF_RETURNONLYFSDIRS
    bi%lpfn           = NULL
    bi%lParam         = 0
    bi%iImage         = 0

    !	SHBrowseForFolder returns an item identifier list.
    pidl = SHBrowseForFolder (bi)

    IF (pidl /= 0) THEN

		SELECT CASE (windows_version)
		
		!	Win95 and Win98
		CASE (VER_PLATFORM_WIN32_WINDOWS)

			! We need to ask the shell to decode the item identifier
			! list into a parseable name. First, retrieve an IShellFolder 
			! COM interface handle to the desktop folder.
			rval2 = SHGetDesktopFolder(LOC(IShellFolder_desktop))
			IF (rval2 == NOERROR) THEN

				! Use the GetDisplayNameOf method to convert the item
				! identifier list into a string. The SHGDN_FORPARSING flag
				! indicates we want the parseable path, not the general-
				! purpose display label.
				rstring%uType = STRRET_CSTR
				rval2 = IShellFolder_GetDisplayNameOf (IShellFolder_desktop,&
													   pidl,				&
													   SHGDN_FORPARSING,	&
													   LOC(rstring)			)
				IF (rval2 == NOERROR) THEN
					folderPath = rstring%cStr
					CmnDlgChooseFolder = .TRUE.
				END IF
			END IF

		!	WinNT, Win2K, WinXP
		CASE (VER_PLATFORM_WIN32_NT)
			CmnDlgChooseFolder = SHGetPathFromIDList (pidl, folderPath)

		END SELECT

        CALL CoTaskMemFree (pidl)
    END IF
	CALL COMUnInitialize ()

END FUNCTION CmnDlgChooseFolder
[/bash]

dannycat
Novo colaborador I
1.127 Visualizações

Thanks Paul,

What is in the ExtraTy module? Does it form part of standard modules or is it user defined?

Paul_Curtis
Contribuidor valorado I
1.127 Visualizações

Sorry for the omission. ExtraWinTy contains Win32 data type definitions which were omitted from the original set supplied with DVF, and this list used to be very long. As DVF -> CVF -> IVF has evolved, the missing types have mostly been filled in, so this module has shrunk. I haven't checked whether these items are now supplied with IVF; at any rate, here they are:

[bash]MODULE ExtraWinTy
USE ifwinty

TYPE T_BROWSEINFO
SEQUENCE
INTEGER(HANDLE) :: hwndOwner
INTEGER :: pidlRoot
INTEGER :: pszDisplayName
INTEGER :: lpszTitle
INTEGER :: ulFlags
INTEGER :: lpfn
INTEGER(fLPARAM) :: lParam
INTEGER :: iImage
END TYPE T_BROWSEINFO

!typedef struct _STRRET
!{
! UINT uType; // One of the STRRET_* values
! union
! {
! LPWSTR pOleStr; // OLESTR that will be freed
! LPSTR pStr; // ANSI string that will be freed (needed?)
! UINT uOffset; // Offset into SHITEMID
! char cStr[MAX_PATH]; // Buffer to fill in (ANSI)
! } DUMMYUNIONNAME;
!} STRRET, *LPSTRRET;

TYPE T_STRRET
SEQUENCE
INTEGER :: uType
CHARACTER(LEN=MAX_PATH) :: cStr
END TYPE T_STRRET

END MODULE ExtraWinTy
[/bash]

dannycat
Novo colaborador I
1.127 Visualizações

Thanks again for that, however I don't seem to be able to resolve some of the functions and constants used in the routine using IVF 11.1.056. Namely:

C:\Win\FEM2000\Windows\w32_GetPath.f90(104): error #6404: This name does not have a type, and must have an explicit type. [BIF_RETURNONLYFSDIRS]

C:\Win\FEM2000\Windows\w32_GetPath.f90(110): error #6404: This name does not have a type, and must have an explicit type. [SHBROWSEFORFOLDER]

C:\Win\FEM2000\Windows\w32_GetPath.f90(122): error #6404: This name does not have a type, and must have an explicit type. [SHGETDESKTOPFOLDER]

C:\Win\FEM2000\Windows\w32_GetPath.f90(129): error #6404: This name does not have a type, and must have an explicit type. [STRRET_CSTR]

C:\Win\FEM2000\Windows\w32_GetPath.f90(132): error #6404: This name does not have a type, and must have an explicit type. [SHGDN_FORPARSING]

C:\Win\FEM2000\Windows\w32_GetPath.f90(130): error #6404: This name does not have a type, and must have an explicit type. [ISHELLFOLDER_GETDISPLAYNAMEOF]

C:\Win\FEM2000\Windows\w32_GetPath.f90(143): error #6404: This name does not have a type, and must have an explicit type. [SHGETPATHFROMIDLIST]

where are these defined in your code?

TommyCee
Principiante
1.127 Visualizações

Thanks for all your replies. It seems since DannyCat's first reply, we got way off "in the weeds". I should have weighed back in sooner.

Let's step back to DannyCat's suggestion:

Once you have obtained the file you can extract the directory name and store it withinan application "saved" or global variable and this can be used as the initial setting for OFN%lpStrInitialDir next time round.

This shows we're on the same track as this was precisely the line I pointed to. But please note what I said:

The second one seemed like one I could at least set to force it to look in my H-drive, yet when enabled, it triggers a nasty Windows error (which is why I have it commented it out).

Before we attempt to do DannyCat's suggestion, it seems like the following line (or something close to it) should take me to my H-drive:

OFN%lpStrInitialDir = 'H:'

Why does it trigger the app-stopping error? (It compiles fine.) Is the syntax wrong? Again, I have found no guidance on this (using a customized direct to a particular directory in the line noted in this function), seeming to suggest the the world - or most of it - is happy with the default line that takes you to MyDocuments (i.e., OFN%lpStrInitialDir = NULL). Pardon my cynicism.

Assuming this could work, of course, the rest of DannyCat's logic makes sense (exception noted below). Theoretically, I could - once I get to the file I'm seeking - capture its directory by parsing the full file spec.

As I understand the rest of DCat's suggestion ("... store it withinan application "saved" or global variable and this can be used as the initial setting for OFN%lpStrInitialDir next time round."), I predict a problem. Yes, the directory can be stored off into a global variable but its contents would only be available while the app is running. When the app is loaded next time, that cell is empty. Maybe I'm missing something.

To be clear, I'm definitely not interested in "remembering" the file name - only the working directory.

IF I could get the offending line I cited above to work within f.InitializeOpen(), I believe I could extract the full-path directory name and write it to an INI file, which I could read when the app is launched each time. This would seem loads easier than the complicated code Paul Curtis kindly provided.

Again, I appreciate all the offerings here. I'm looking for the simplest way to "get there".

Paul_Curtis
Contribuidor valorado I
1.127 Visualizações
See the attached.
dannycat
Novo colaborador I
1.127 Visualizações

Are you trying to keep track of just the directory after selecting a file, or after just selecting a folder? If it is the former then common dialog procedure that is used in f.InitializeOpen example can be used. Once the file has been selected the full path is returned in string assignede.g. OFN%lpstrFile = loc(filename)and the offset to the filename itself is returned in OFN%nFileOffset. Therefore directory name will be in filename(1:OFN%nFileOffset). You can then save this in the application or registry or INI file, - where ever you wish!

Be aware that strings need to be NULL terminated.

If you are just selecting the directory then you can't use file open/save common dialogs to do this, you need to use the shell routines kindly provided by Paul.One of thereasons Paul joined this discussion was to answer my question regarding setting the initial directiory in the SHBrowseForFolder function.

You don't seem sure what you want to do and consequently we have difficultly helping you with your issues.

PS Thanks again to Paul for the code, I can make good use of this and ditch my 'orrible C code version at last.

anthonyrichards
Novo colaborador III
1.127 Visualizações

All your strings must be C-strings and therefore 'null' terminated.

Thus ='H:' is wrong,

whereas = 'H:'c is correct

(='H:'//char(0) is also correct.).

Your error is caused by your not terminating the directory string correctly.

I repeat that, in my experience, susequent calls to the common dialog GETOPENFILENAME open the windowat the directory selected by the previous call, so it 'remembers' where it was (perhaps by using the registry), so long as the user selects a file and presses the 'OK' button. This is confirmed by a statement on the Microsoft website that 'When the user selects at least one file and clicks the OK button, the process' current working directory is changed to the directory contain the file(s) being opened'

TommyCee
Principiante
1.127 Visualizações

Thanks for additional replies, one & all. I appreciate the code you provided, Paul. Not quite sure what to do w/ it but it seems like one hell of piece of work.

To DCat, as I hope I made clear, let me explain again. I run the app for the "first time" and, when I invoke f.InitializeOpen, it's OK if it takes me to MyDocuments. I browse from within that dialog and find the file I'm seeking (in, say, some directory on an entirely different drive). The app is closed. When I later re-launch the app and invoke theOpenFile dialog (via f.InitializeOpen), I want it to take me to the very subdirectory that holds the file I opened last time! Nothing more complicated than that. The other info you provided:

Once the file has been selected the full path is returned in string assignede.g. OFN%lpstrFile = loc(filename)and the offset to the filename itself is returned in OFN%nFileOffset. Therefore directory name will be in filename(1:OFN%nFileOffset).

was very useful.

The first part of Anthony's reply:

All your strings must be C-strings and therefore 'null' terminated.

Thus ='H:' is wrong,

whereas = 'H:'c is correct

(='H:'//char(0) is also correct.).

Your error is caused by your not terminating the directory string correctly.

was very interesting and - at first blush - seemed like it hit at the core of my initial question. I tried using this exact strnig:

OFN%lpStrInitialDir = 'H:'c

presumably to force it - at least initially -to go to the H-drive (root directory) when the OpenDialog is launched. But at compilation, I get this strange message:

Warning: The backslash character and following character is not a valid escape sequence

The backslash character & the following character ('); nothing wrong with the (') preceding the H.

I would like to know your thoughts on this.

Now, the last part of Anthony's reply (reiterating his earler coment) was the most important:

I repeat that, in my experience, susequent calls to the common dialog GETOPENFILENAME open the windowat the directory selected by the previous call, so it 'remembers' where it was (perhaps by using the registry), so long as the user selects a file and presses the 'OK' button.

Firstly, to his earler question (I presume you use OFN as an argument to GETOPENFILENAME?), YES.

Here's the best part. Just now, though never before, I ran f.InitializeOpen w/ the default

OFN%lpStrInitialDir = NULL

and guess what? It behaved exactly as you predicted (and as I wished): it returns to where it was before.

So, I guess all's well that ends well.

anthonyrichards
Novo colaborador III
1.127 Visualizações

OOPs! My error. I forgot that when the compiler can see the backslash in a string such as 'H:'cterminated wtih the 'c characters (which identifies it as a C-string, C-strings are constructed and stored by the compiler, which adds the null character to the end of the string and stores it), it will treat the combinationof backslash+next characteras an escape sequence. All you need to do is double up the back slash thus

OFN%lpStrInitialDir = 'H:\'c

Alternatively, the following should work

OFN%lpStrInitialDir = 'H:'//char(0)

since, without the terminating 'C the compiler does not view the RHS as a C-string, and hence does not take the sight of a backslash as the start of an escape sequence pair of characters, and does not automatically add a terminating null character to the end of the character string.

Always remember when specifying the length of a character string, to allow an extra character for the addition of the null character to the end, if you are specifying a C-string as an argument. Similarly, when creating a buffer to receive a C-string, allow for the null character that will be used to terminate the string.

Windows API functions invariable require and return C-strings, strings terminated with at least one (sometimes two) null characters

Responder