为什么Delphi TOpenDialog无法在初始目录中打开?

发布于 2025-01-16 22:54:29 字数 527 浏览 0 评论 0原文

我正在使用 TOpenDialog (在 Delphi 10.4 中)向用户显示我在其 Documents 文件夹中为他们安装的 PDF 文件。在该文件夹中,我创建了一个文件夹 MyFolder10.2 并将 PDF 文件复制到其中。

代码很简单并且过去一直有效,即使现在它仍然可以在我较旧的较慢的 Win10 机器上运行。但在我的较新、更快的 Win10 计算机上,它只能在某些时候工作。当它不起作用时,会打开一个文件对话框,但在其他目录中(不确定它来自哪里),并且它不会过滤 .pdf 中设置的文件类型(.pdf)代码>TOpenDialog组件。

有什么办法可以解开这个谜团吗?

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

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

惟欲睡 2025-01-23 22:54:29

在 Vista 之前,TOpenDialogGetOpenFileName() API,其中 TOpenDialog.InitialDir 映射到OPENFILENAME.lpstrInitialDir 字段。

在 Vista 及更高版本上,TOpenDialog(通常,取决于配置)包装 IFileDialog/IFileOpenDialog相反,API,其中 TOpenDialog.InitialDir 映射到 IFileDialog.SetFolder()方法(不是 IFolderDialog.SetDefaultFolder(),正如人们所期望的那样)。

根据 OPENFILENAME文档:

lpstrInitialDir

类型:LPCTSTR

初始目录。 不同平台选择初始目录的算法有所不同。

Windows 7:

  1. 如果 lpstrInitialDir [TOpenDialog.InitialDir] 的值与应用程序第一次使用“打开”或“另存为”对话框时传递的值相同,则为最近选择的路径由用户用作初始目录。
  2. 否则,如果 lpstrFile [TOpenDialog.FileName] 包含路径,则该路径就是初始目录。
  3. 否则,如果 lpstrInitialDir 不为 NULL [TOpenDialog.InitialDir 不为空],则指定初始目录。
  4. 如果lpstrInitialDir为NULL [TOpenDialog.InitialDir为空]并且当前目录包含指定过滤器类型的任何文件,则初始目录为当前目录。< /里>
  5. 否则,初始目录为当前用户的个人文件目录。
  6. 否则,初始目录是桌面文件夹。

Windows 2000/XP/Vista:

  1. 如果 lpstrFile [TOpenDialog.FileName] 包含路径,则该路径是初始目录。
  2. 否则,lpstrInitialDir [TOpenDialog.InitialDir] 指定初始目录。
  3. 否则,如果应用程序过去使用过“打开”或“另存为”对话框,则选择最近使用的路径作为初始目录。但是,如果应用程序长时间未运行,则其保存的所选路径将被丢弃。
  4. 如果lpstrInitialDir为NULL [TOpenDialog.InitialDir为空]并且当前目录包含指定过滤器类型的任何文件,则初始目录为当前目录。< /里>
  5. 否则,初始目录为当前用户的个人文件目录。
  6. 否则,初始目录是桌面文件夹。

并根据常见项目对话框文档:

控制默认文件夹

Shell 命名空间中的几乎任何文件夹都可以用作对话框的默认文件夹(当用户选择打开或保存文件时显示的文件夹)。在调用 Show [TOpenDialog.Execute()] 之前调用 IFileDialog::SetDefaultFolder 来执行此操作。

默认文件夹是用户第一次从应用程序打开对话框时在其中启动对话框的文件夹。之后,该对话框将在用户打开的最后一个文件夹或他们用来保存项目的最后一个文件夹中打开。请参阅状态持久性了解更多详细信息。

您可以通过调用 IFileDialog::SetFolder [TOpenDialog.InitialDir,强制对话框在打开时始终显示相同的文件夹,而不管之前的用户操作如何>]。但是,我们不建议这样做。如果您在显示对话框之前调用 SetFolder,则不会显示用户最近保存或打开的位置。除非这种行为有非常具体的原因,否则这不是良好的或预期的用户体验,应该避免。在几乎所有情况下,IFileDialog::SetDefaultFolder 是更好的方法。

首次在“保存”对话框中保存文档时,应遵循与在“打开”对话框中相同的准则来确定初始文件夹。如果用户正在编辑以前存在的文档,请在存储该文档的文件夹中打开对话框,然后使用该文档的名称填充编辑框。在调用 Show [TOpenDialog.Execute()] 之前,使用当前项目调用 IFileSaveDialog::SetSaveAsItem()

既不是 TOpenDialog 也不是 TFileOpenDialog 具有映射到 IFileDialog.SetDefaultFolder()IFileDialog.SetSaveAsItem() 方法的属性。但是,TFileOpenDialog.Dialog 属性确实使您可以访问底层的 IFileDialog,因此您可以手动调用这些方法,例如在 TFileOpenDialog.OnExecute 事件中。

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.

将军与妓 2025-01-23 22:54:29

@Remy Lebeau 的详细回答促使我再次尝试修复一个有问题的常见场景,尽管我进行了多次尝试,但我之前尚未成功修复:
我有一个带有 TFileOpenDialog 和 TFileSaveDialog 的图像编辑 VCL 应用程序,通常用于在连接的 Android 设备上逐个编辑一堆屏幕截图,并将其保存在台式计算机上。
问题是在计算机上保存后,FileOpenDialog 的 DefaultFolder 没有粘贴到打开的 Android 文件夹中。现在已根据我的喜好进行了修复,并且需要使用对话框的人可能会对解决方案感兴趣。

测试代码:

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.

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文