Page 1 of 2

[Solved - OS fault] File Requesters open in previous path

Posted: Thu Oct 31, 2019 1:34 pm
by kenmo
Spin off this thread: viewtopic.php?f=13&t=73886

This is a minor "bug" behavior I have noticed for a while but never investigated.
The File Requesters open in the last selected directory, even if you give a specific directory argument.

It's either PureBasic or Windows, caching the directory in some way? Can it be overridden?

Code: Select all

CompilerIf (#PB_Compiler_Debugger)
  MessageRequester("Instructions",
      "1. Compile this to an EXE (Desktop/test.exe or similar)" + #LF$ +
      "2. Run it" + #LF$ + 
      "3. When the first requester opens, choose a file in a different folder" + #LF$ +
      "4. The second requester opens in that folder (bug)" + #LF$ +
      "5. Run the program again, and it opens in that changed folder (bug)" + #LF$ + #LF$ +
      "Some sort of Windows caching issue ??" )
CompilerElse

  ;CurrentDir.s = GetCurrentDirectory()
  ;MessageRequester("Current Dir", CurrentDir)
  
  ProgramDir.s = GetPathPart(ProgramFilename())
  MessageRequester("Program Dir", ProgramDir)
  
  OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)
  OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)

CompilerEndIf

Re: File Requesters open in previous path

Posted: Thu Oct 31, 2019 1:47 pm
by kenmo
This seems to be why. Can it be overridden?
https://docs.microsoft.com/en-us/window ... nfilenamea
lpstrInitialDir

Type: LPCTSTR

The initial directory. The algorithm for selecting the initial directory varies on different platforms.

Windows 7:

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.
Otherwise, if lpstrFile contains a path, that path is the initial directory.
Otherwise, if lpstrInitialDir is not NULL, it specifies the initial directory.
If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.

Windows 2000/XP/Vista:

If lpstrFile contains a path, that path is the initial directory.
Otherwise, lpstrInitialDir specifies the initial directory.
Otherwise, if the application has used an Open or Save As dialog box in the past, the path most recently used is selected as the initial directory. However, if an application is not run for a long time, its saved selected path is discarded.
If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
I guess it's been a Windows complaint for 8+ years :)
https://social.msdn.microsoft.com/Forum ... w-to-avoid

Re: File Requesters open in previous path

Posted: Thu Oct 31, 2019 7:20 pm
by kenmo
Sorry to reply to my own question, but I figured out a workaround that seems to work on Windows 10.
Just 2 procedures + 1 call, before you open any requesters.

Windows 7+ remembers the first DefaultDir you call. If you call that DefaultDir again, Windows ignores it and opens in the last selected path.

Apparently this was an attempt to solve lazy programs which don't track the user's selected path, when the user expects it to. But it broke other programs' intended behavior.

So a workaround is... first open DefaultDir to a temporary folder you'll likely never call again. Then future DefaultDir will work as expected.
Preferably, open DefaultDir as hidden as possible, in this workaround it closes it as soon as possible.

Code: Select all

;-
;- -----------------------------
;- WORKAROUND START
;-

CompilerIf (#PB_Compiler_OS = #PB_OS_Windows)
Procedure _PrepareFileRequester(*OFN.OPENFILENAME)
  OpenFileRequester(PeekS(*OFN\lpstrTitle), PeekS(*OFN\lpstrInitialDir), "", 0)
EndProcedure

Procedure PrepareFileRequester()
  Static Prepared.i = #False
  If (Not Prepared)
    Prepared = #True
    If (OSVersion() >= #PB_OS_Windows_7)
      Protected Title.s = " "
      While (FindWindow_(#Null, @Title))
        Title + " "
      Wend
      Protected TempDir.s = GetTemporaryDirectory() + "." + Str(Date())
      CreateDirectory(TempDir)
      Protected OFN.OPENFILENAME
      OFN\lpstrTitle      = @Title
      OFN\lpstrInitialDir = @TempDir
      Protected *Thread = CreateThread(@_PrepareFileRequester(), @OFN)
      Protected *Window
      Repeat : Delay(0) : *Window = FindWindow_(#Null, @Title) : Until (*Window)
      SendMessage_(*Window, #WM_CLOSE, 0, 0)
      Repeat : Delay(0) : Until (Not IsThread(*Thread))
      DeleteDirectory(TempDir, "")
    EndIf
  EndIf
EndProcedure

CompilerElse
Macro PrepareFileRequester()
  ;
EndMacro
CompilerEndIf

;-
;- WORKAROUND END
;- -----------------------------


;-
;-

CompilerIf (#PB_Compiler_Debugger)
  MessageRequester("Instructions",
      "1. Compile this to an EXE (Desktop/test.exe or similar)" + #LF$ +
      "2. Run it" + #LF$ +
      "3. When the first requester opens, choose a file in a different folder" + #LF$ +
      "4. The second requester opens in that folder (bug)" + #LF$ +
      "5. Run the program again, and it opens in that changed folder (bug)" + #LF$ + #LF$ +
      "Some sort of Windows caching issue ??" )
CompilerElse
 
  ProgramDir.s = GetPathPart(ProgramFilename())
  MessageRequester("Program Dir", ProgramDir)
  
  PrepareFileRequester() ;- ADD THIS ONE LINE
  
  OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)
  SaveFileRequester("Title", ProgramDir, "All Files|*.*", 0)

CompilerEndIf
;-
EDIT - See MUCH nicer solution here: viewtopic.php?p=549667#p549667

Re: File Requesters open in previous path

Posted: Sun Nov 03, 2019 3:50 am
by breeze4me

Re: File Requesters open in previous path

Posted: Sun Nov 03, 2019 11:23 pm
by kenmo
Not for me (tested on Win 7 just now).
It uses the OpenFileName_() API, which has the issue, exactly what is discussed in that MSDN link.
https://social.msdn.microsoft.com/Forum ... w-to-avoid

Maybe you misunderstand the (subtle) issue.
Take Justin's code and replace the bottom with this:

Code: Select all

;- #TEST
; compile an EXE (with a unique name) and run it
; the first dialog opens in Desktop (correct)
; the second dialog ignores Desktop and opens in whatever folder you last selected
GetOpenFileName(0, "", GetUserDirectory(#PB_Directory_Desktop), "All files (*.*)|*.*|MP3 (*.mp3)|*.mp3", 1, "", "", #Null, @iFilter, @fileTitle$, #OFN_FILEMUSTEXIST)
GetOpenFileName(0, "", GetUserDirectory(#PB_Directory_Desktop), "All files (*.*)|*.*|MP3 (*.mp3)|*.mp3", 1, "", "", #Null, @iFilter, @fileTitle$, #OFN_FILEMUSTEXIST)

This answer from that link is interesting, maybe I'll experiment with this, and if it works, suggest it to the PB team as a fix.
You should be able to put the full path in the lpstrFile field of the OPENFILENAME struct (and leave lpstrInitialDir empty). This is generally what is used in the "Save As" scenario for instance, where the dialog starts in the same folder as the item you are "save as"ing exists. You'll probably need to supply a filename too though (which will appear in the filename box).
EDIT 2019-11-05 This seems to work (specify a lpstrFile and it will always open in that folder, the OS won't ignore your specification) BUT it seems you MUST specify a file name (even ".") which is a small annoyance, you can't open with an empty name field.

Re: File Requesters open in previous path

Posted: Mon Nov 04, 2019 12:05 am
by breeze4me
OK, you are right. The compiled exe doesn't work.

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Fri Feb 21, 2020 12:34 pm
by Little John
kenmo, thank you for the detailed research, and for the workaround!
Much appreciated.

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Fri Feb 21, 2020 1:07 pm
by IdeasVacuum
Hi kenmo

I have seen this issue before and assumed it was a PB glitch. Very presumptuous of MS for sure.

Good workaround, thanks for sharing.

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Fri Feb 21, 2020 5:30 pm
by kenmo
About the issue:

Basically, Windows is trying to be clever to cover up for old, user-unfriendly programs.
If you open a requester in the same directory multiple times, Windows thinks "oh, you don't *really* want to open in that directory, you must want the last folder the user browsed to!"
Of course this is not always what we want! We can implement that ourselves.

About my workaround:

If it's not clear, what it does is open a one-time Requester in a temporary directory, and immediately close it (hopefully without seeing it).
As long as you don't open a Requester in that path in the rest of the program, Windows won't do this behavior where it assumes you want the user's last selected folder.

About threading:

The code I posted above opens a Requester from a child thread, and closes it from the main thread.
In real testing, since that post, it seems to work more reliably if the Requester is opened from the main thread (and closed from a child thread).
I will post my updated code later today.

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Sat Feb 22, 2020 10:57 am
by Little John
Hi kenmo, your code works fine here (on Windows 10).

However, when using your workaround in a program, some care must be taken:
If the program has an open window when PrepareFileRequester() is called, then the program hangs (because FindWindow_() does not work as expected anymore).

Code: Select all

ProgramDir.s = GetPathPart(ProgramFilename())
MessageRequester("Program Dir", ProgramDir)

OpenWindow(0, 10, 10, 200, 200, "Test")

PrepareFileRequester()   ;- Does NOT work! --> Call this BEFORE any call of OpenWindow().

OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)
SaveFileRequester("Title", ProgramDir, "All Files|*.*", 0)

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Sat Feb 22, 2020 5:13 pm
by JHPJHP
Hi All,

I cannot remember if the following worked in previous versions of Windows, but for Windows 10 I have not had a problem.

Add a Backslash

Code: Select all

CompilerIf #PB_OS_Windows
  PathAddBackslash_(ProgramDir)
CompilerEndIf

Code: Select all

CompilerIf (#PB_Compiler_Debugger)
  MessageRequester("Instructions",
      "1. Compile this to an EXE (Desktop/test.exe or similar)" + #LF$ +
      "2. Run it" + #LF$ + 
      "3. When the first requester opens, choose a file in a different folder" + #LF$ +
      "4. The second requester opens in that folder (bug)" + #LF$ +
      "5. Run the program again, and it opens in that changed folder (bug)" + #LF$ + #LF$ +
      "Some sort of Windows caching issue ??" )
CompilerElse

  ;CurrentDir.s = GetCurrentDirectory()
  ;MessageRequester("Current Dir", CurrentDir)
  
  ProgramDir.s = GetPathPart(ProgramFilename())
  
  CompilerIf #PB_OS_Windows
    PathAddBackslash_(ProgramDir)
  CompilerEndIf
  MessageRequester("Program Dir", ProgramDir)
  
  OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)
  OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)

CompilerEndIf

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Sat Feb 22, 2020 6:11 pm
by Little John
Hi JHPJHP, unfortunately adding

Code: Select all

PathAddBackslash_(ProgramDir)
does not solve the problem here (on Windows 10 Pro x64, version 1909).

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Sat Feb 22, 2020 6:46 pm
by JHPJHP
Hi Little John,

That's the problem with Windows, I always feel like I am programming in a bubble.

Microsoft Window 10 Pro :: 10.0.18363 Build 18363 :: x64
Image

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Sat Feb 22, 2020 10:26 pm
by kenmo
Huh, I wonder why it doesn't happen for you... maybe Microsoft finally removed this "feature" in a version of Windows 10?

Try building it to something other than "test.exe" since Windows caches this (and other things) by executable name... build it as something unique like test1234NeverRanThisBefore.exe and see how it behaves.

Thanks for the demonstration video!

Re: [Solved - OS fault] File Requesters open in previous pat

Posted: Sat Feb 22, 2020 10:26 pm
by JHPJHP
Hi kenmo,

I looked at some of my recent projects to figure out why it's been working, and found that inadvertently I've been doing a variation of the following.
- force a double backslash on the default directory after the first call to OpenFileRequester

The OpenFileRequester will keep opening until Cancel is pressed.
- please let me know if this breaks the Windows bubble

Code: Select all

CompilerIf (#PB_Compiler_Debugger)
  MessageRequester("Instructions",
      "1. Compile this to an EXE (Desktop/test.exe or similar)" + #LF$ +
      "2. Run it" + #LF$ + 
      "3. When the first requester opens, choose a file in a different folder" + #LF$ +
      "4. The second requester opens in that folder (bug)" + #LF$ +
      "5. Run the program again, and it opens in that changed folder (bug)" + #LF$ + #LF$ +
      "Some sort of Windows caching issue ??" )
CompilerElse

  ProgramDir.s = GetPathPart(ProgramFilename())
  MessageRequester("Program Dir", ProgramDir)

  test$ = OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)
  CompilerIf #PB_OS_Windows
    PathAddBackslash_(ProgramDir) : ProgramDir + "\"
  CompilerEndIf

  While test$ <> ""
    MessageRequester("OpenFileRequester", test$)
    test$ = OpenFileRequester("Title", ProgramDir, "All Files|*.*", 0)
  Wend

CompilerEndIf