Why does Delphi TOpenDialog fail to open in the Initial Directory?

706 views Asked by At

I am using TOpenDialog (in Delphi 10.4) to show the user the PDF files I have installed for them in their Documents folder. In that folder, I have created a folder MyFolder10.2 and copied the PDF files there.

The code is simple and has worked in the past, and even now it still works on my older slower Win10 machine. But on my newer faster Win10 computer, it only works SOME of the time. When it does not work, a file dialog is opened but in some other directory (not sure where that comes from), and it does not filter the file type (.pdf) that was set in the TOpenDialog component.

Any way to track down this mystery?

docPath:= GetEnvironmentVariable('USERPROFILE') + '\Documents\MyFolder10.2\';
OpenDocsDlg.InitialDir := docPath;
OpenDocsDlg.Execute;
2

There are 2 answers

4
Remy Lebeau On BEST ANSWER

Prior to Vista, TOpenDialog is a wrapper for the GetOpenFileName() API, where TOpenDialog.InitialDir maps to the OPENFILENAME.lpstrInitialDir field.

On Vista and later, TOpenDialog (usually, depending on configuration) wraps the IFileDialog/IFileOpenDialog API instead, where TOpenDialog.InitialDir maps to the IFileDialog.SetFolder() method (not to IFolderDialog.SetDefaultFolder(), as one would expect).

Per the OPENFILENAME documentation:

lpstrInitialDir

Type: LPCTSTR

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

Windows 7:

  1. If lpstrInitialDir [TOpenDialog.InitialDir] 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.
  2. Otherwise, if lpstrFile [TOpenDialog.FileName] contains a path, that path is the initial directory.
  3. Otherwise, if lpstrInitialDir is not NULL [TOpenDialog.InitialDir is not empty], it specifies the initial directory.
  4. If lpstrInitialDir is NULL [TOpenDialog.InitialDir is empty] and the current directory contains any files of the specified filter types, the initial directory is the current directory.
  5. Otherwise, the initial directory is the personal files directory of the current user.
  6. Otherwise, the initial directory is the Desktop folder.

Windows 2000/XP/Vista:

  1. If lpstrFile [TOpenDialog.FileName] contains a path, that path is the initial directory.
  2. Otherwise, lpstrInitialDir [TOpenDialog.InitialDir] specifies the initial directory.
  3. 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.
  4. If lpstrInitialDir is NULL [TOpenDialog.InitialDir is empty] and the current directory contains any files of the specified filter types, the initial directory is the current directory.
  5. Otherwise, the initial directory is the personal files directory of the current user.
  6. Otherwise, the initial directory is the Desktop folder.

And per the Common Item Dialog documentation:

Controlling the Default Folder

Almost any folder in the Shell namespace can be used as the default folder for the dialog (the folder presented when the user chooses to open or save a file). Call IFileDialog::SetDefaultFolder prior to calling Show [TOpenDialog.Execute()] to do so.

The default folder is the folder in which the dialog starts the first time a user opens it from your application. After that, the dialog will open in the last folder a user opened or the last folder they used to save an item. See State Persistence for more details.

You can force the dialog to always show the same folder when it opens, regardless of previous user action, by calling IFileDialog::SetFolder [TOpenDialog.InitialDir]. However, we do not recommended doing this. If you call SetFolder before you display the dialog box, the most recent location that the user saved to or opened from is not shown. Unless there is a very specific reason for this behavior, it is not a good or expected user experience and should be avoided. In almost all instances, IFileDialog::SetDefaultFolder is the better method.

When saving a document for the first time in the Save dialog, you should follow the same guidelines in determining the initial folder as you did in the Open dialog. If the user is editing a previously existing document, open the dialog in the folder where that document is stored, and populate the edit box with that document's name. Call IFileSaveDialog::SetSaveAsItem() with the current item prior to calling Show [TOpenDialog.Execute()].

Neither TOpenDialog nor TFileOpenDialog have properties that map to the IFileDialog.SetDefaultFolder() or IFileDialog.SetSaveAsItem() methods. However, the TFileOpenDialog.Dialog property does give you access to the underlying IFileDialog, so you can manually call these methods, such as in the TFileOpenDialog.OnExecute event.

0
ZHN On

The detailed answer by @Remy Lebeau triggered me to try again to fix a problematic common scenario I've not managed to fix before, despite numerous attempts: I have an image edit VCL application with a TFileOpenDialog and a TFileSaveDialog that is often used for editing a bunch of screenshots one by one on a connected Android device which are saved on a desktop computer. The problem was that the DefaultFolder for the FileOpenDialog didn't stick to the opened Android folder after a save on the computer. That is now fixed to my liking, and the solution might be of interest to someone who needs to play around with the dialogs.

The test code:

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  Winapi.ShlObj, Winapi.KnownFolders, Winapi.ActiveX;

type
  TMainForm = class(TForm)
    FileOpenDialog1: TFileOpenDialog;
    FileSaveDialog1: TFileSaveDialog;
    OpenButton: TButton;
    SaveButton: TButton;
    Memo1: TMemo;
    procedure FileOpenDialog1Execute(Sender: TObject);
    procedure FileSaveDialog1Execute(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure OpenButtonClick(Sender: TObject);
    procedure SaveButtonClick(Sender: TObject);
  private
    FOpenShellItem: IShellItem;
    FSaveShellItem: IShellItem;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

function GetItemName(ShellItem: IShellItem; const Flags: Cardinal): string;
var
  pszItemName: LPCWSTR;
begin
  Result := '';
  if ShellItem.GetDisplayName(Flags, pszItemName) = S_OK then
  begin
    Result := pszItemName;
    CoTaskMemFree(pszItemName);
  end;
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  pch: PChar;
begin
  DesktopFont := True;

  if SHGetKnownFolderPath(FOLDERID_Pictures, 0, 0, pch) = S_OK then
  begin
    FileOpenDialog1.DefaultFolder := pch;
    FileSaveDialog1.DefaultFolder := pch;
    CoTaskMemFree(pch);
  end;
end;

procedure TMainForm.OpenButtonClick(Sender: TObject);
var
  ShellItem: IShellItem;
  ParentItem: IShellItem;
begin
  if FileOpenDialog1.Execute(Handle) then
  begin
    if fdoAllowMultiSelect in FileOpenDialog1.Options then
      FileOpenDialog1.ShellItems.GetItemAt(0, ShellItem)
    else
      ShellItem := FileOpenDialog1.ShellItem;
    if ShellItem.GetParent(ParentItem) = S_OK then
      FOpenShellItem := ParentItem;


    Memo1.Lines.Add('Opened');
    Memo1.Lines.Add('ItemParsingName:   ' +
      GetItemName(ShellItem, SIGDN_DESKTOPABSOLUTEPARSING));
    Memo1.Lines.Add('ItemNormalName:   ' +
      GetItemName(ShellItem, SIGDN_NORMALDISPLAY));
    Memo1.Lines.Add('ParentItemParsingName:   ' +
      GetItemName(ParentItem, SIGDN_DESKTOPABSOLUTEPARSING));
    Memo1.Lines.Add('ParentItemNormalame:   ' +
      GetItemName(ParentItem, SIGDN_NORMALDISPLAY));
    Memo1.Lines.Add('----------');
  end;
end;

procedure TMainForm.SaveButtonClick(Sender: TObject);
var
  ParentItem: IShellItem;
begin
  if FileSaveDialog1.Execute(Handle) then
  begin
    if FileSaveDialog1.ShellItem.GetParent(ParentItem) = S_OK then
      FSaveShellItem := FileSaveDialog1.ShellItem;


    Memo1.Lines.Add('Saved');
    Memo1.Lines.Add('ItemParsingName:   ' +
      GetItemName(FileSaveDialog1.ShellItem, SIGDN_DESKTOPABSOLUTEPARSING));
    Memo1.Lines.Add('ItemNormalName:   ' +
      GetItemName(FileSaveDialog1.ShellItem, SIGDN_NORMALDISPLAY));
    Memo1.Lines.Add('ParentItemParsingName:   ' +
      GetItemName(ParentItem, SIGDN_DESKTOPABSOLUTEPARSING));
    Memo1.Lines.Add('ParentItemNormalName:   ' +
      GetItemName(ParentItem, SIGDN_NORMALDISPLAY));
    Memo1.Lines.Add('----------');
  end;
end;

procedure TMainForm.FileOpenDialog1Execute(Sender: TObject);
begin
  if Assigned(FOpenShellItem) then
    FileOpenDialog1.Dialog.SetFolder(FOpenShellItem);
end;

procedure TMainForm.FileSaveDialog1Execute(Sender: TObject);
begin
  //Note the IFileSaveDialog typecast
  if Assigned(FSaveShellItem) then
    IFileSaveDialog(FileSaveDialog1.Dialog).SetSaveAsItem(FSaveShellItem);
end;

end.