Selecting a directory with TOpenDialog
Asked Answered
D

6

64

I'd really like to know the various ways I could select a directory with the TOpenDialog, whether it be downloading a new component or using what is provided by Delphi, but preferably using what is provided by Delphi.

Prior to this, I have been using the SelectDirectory command but I think it'd be a difficulty for the users of my program to look for the specified directory.

I think the SelectDirectory is 'weak' because it can be a long process when searching for the directory you want. Say for example, you want to navigate to the Application Data directory. How long or difficult would it be to navigate there? In the end, users may not even reach their desired directory.

I need something like this where the user can copy and paste directories into the directory address bar at the top there.

enter image description here

Thank you for all your answers.

Detective answered 14/9, 2011 at 20:38 Comment(11)
SelectDirectory uses the system native directory selection dialog. You appear to want to use something else, but you don't say what is weak about SelectDirectory, and you don't say what features you are looking for in the replacement.Kobayashi
@David: It is not that simple. The FileCtrl.SelectDirectory has two overloaded implementations. One of them produces a Windows 3.1-styled dialog, while the other produces a native dialog.Orvas
@Andreas then the answer surely is to use the correct overload. Do you want to write that answer? Ha, you already did!!Kobayashi
@peter I think your best option is TFileOpenDialog with fdoPickFolders on Vista+ and the SHBrowseForFolder version of SelectDirectory on XP and down.Kobayashi
What you need is to implement your own browse for folder using a form and some brainMiserly
Don't implement your own browse for folder code. Impossible to future proof and very hard to do well.Kobayashi
@David I think FindFirst and FindNext are very future proof.Miserly
@Miserly That API is guaranteed never to span the shell namespace now or in the future!Kobayashi
@David, @opc0de: I agree totally with David. It is much better to use the native OS dialogs. Personally, I always use the TFileOpenDialog on Vista+ and fallback using the (good!) SelectDirectory on XP.Orvas
Quick and dirty way i've seen in various free windows software: use save dialog and ignore filename of result. On SelectDirectory function: it is merely a wrapper for SHBrowseForFolder, however it do not use all of the advantages modern shell provides (this includes the edit control - BIF_EDITBOX {v 4.71}). I suggest to use this function directly instead, or reuse someone's ready-made wrapper.Strathspey
+1 In studying SHBrowseForFolder, it's amazing how flexible it is. This comes out in making a VCL wrapper for it. I never got why TOpenDialog was of interest for directory selection anyway given what you can do with SHBrowseForFolderSpaceport
O
87

You can use the TFileOpenDialog (on Vista+):

with TFileOpenDialog.Create(nil) do
  try
    Options := [fdoPickFolders];
    if Execute then
      ShowMessage(FileName);
  finally
    Free;
  end;

Personally, I always use the TFileOpenDialog on Vista+ and fallback using the SelectDirectory (the good one!) on XP, like this:

if Win32MajorVersion >= 6 then
  with TFileOpenDialog.Create(nil) do
    try
      Title := 'Select Directory';
      Options := [fdoPickFolders, fdoPathMustExist, fdoForceFileSystem]; // YMMV
      OkButtonLabel := 'Select';
      DefaultFolder := FDir;
      FileName := FDir;
      if Execute then
        ShowMessage(FileName);
    finally
      Free;
    end
else
  if SelectDirectory('Select Directory', ExtractFileDrive(FDir), FDir,
             [sdNewUI, sdNewFolder]) then
    ShowMessage(FDir)
Orvas answered 14/9, 2011 at 20:45 Comment(3)
Nah, i dislike such branching on the Windows version. Also, merge, this solo performance lacks different ways as requested by OP.Strathspey
TFileOpenDialog != TOpenDialog ... TOpenDialog doesn't have such an option and TFileOpenDialog doesn't exist in FireMonkeyEsperanzaespial
This answer is the perfect one. Thank you very much!Jeaz
O
68

You do know that the two overloaded functions called FileCtrl.SelectDirectory produce entirely different dialogs, right?

SelectDirectory(s, [], 0);
Screenshot
SelectDirectory('Select a directory', s, s, []);
Screenshot
Orvas answered 14/9, 2011 at 21:2 Comment(5)
"Välf mapp" sure beats "Select Directory" +1Kobayashi
@David: Well, consider it a proof of the 'nativeness' of the dialog!Orvas
Really you ought to combine your two answers into one and add some discussion about the behaviour switch between Vista+ and XP.Kobayashi
You know, i actually didnt know there were two versions of this proc. I have hardly ever used it - but epic that i didnt have to dance with shell -- thanks!Viborg
I think you can't use the same s variable for the Root parameter and the Directory parameter (second and third parameter of SelectDirectory('Select a directory', s, s, []);)Nun
D
9

Just include

FileCtrl.pas

var
  sDir:String;
begin
  SelectDirectory('Your caption','',sDir);
end;

Just leave second argument empty if want to see all directories including desktop. If you set second argument to any valid Path, then your dialog will have that path to top folder and you can not navigate beyond that.

For example:

SelectDirectory('Your caption','C:\',sDir) will not let you select anything beyond C:\, like D:\ or E:\ etc.

So it is good to leave it empty.

Discontinuity answered 28/11, 2016 at 19:44 Comment(0)
H
6

Just found the code below that seems to work fine in XP and Vista, Win7. It provides a UI for a user to select a directory. It uses TOpenDialog, but sends it a few messages to clean up the appearance for the purposes of selecting a directory.

After suffering from the limited capabilities provided by Windows itself, it's a pleasure to be able to give my users a familiar UI where they can browse and select a folder comfortably.

I'd been looking for something like this for a long time so thought I'd post it here so others can benefit from it.

Here's what it looks like in Win 7:

screen capture

//***********************
//** Choose a directory **
//**   uses Messages   **
//***********************
  //General usage here:
  //  http://www.delphipages.com/forum/showthread.php?p=185734
  //Need a class to hold a procedure to be called by Dialog.OnShow:
  type TOpenDir = class(TObject)
  public
    Dialog: TOpenDialog;
    procedure HideControls(Sender: TObject);
  end;
  //This procedure hides de combo box of file types...
  procedure TOpenDir.HideControls(Sender: TObject);
  const
    //CDM_HIDECONTROL and CDM_SETCONTROLTEXT values from:
    //  doc.ddart.net/msdn/header/include/commdlg.h.html
    //  CMD_HIDECONTROL = CMD_FIRST + 5 = (WM_USER + 100) + 5;
    //Usage of CDM_HIDECONTROL and CDM_SETCONTROLTEXT here:
    //  msdn.microsoft.com/en-us/library/ms646853%28VS.85%29.aspx
    //  msdn.microsoft.com/en-us/library/ms646855%28VS.85%29.aspx
    CDM_HIDECONTROL =    WM_USER + 100 + 5;
    CDM_SETCONTROLTEXT = WM_USER + 100 + 4;
    //Component IDs from:
    //  msdn.microsoft.com/en-us/library/ms646960%28VS.85%29.aspx#_win32_Open_and_Save_As_Dialog_Box_Customization
    //Translation into exadecimal in dlgs.h:
    //  www.koders.com/c/fidCD2C946367FEE401460B8A91A3DB62F7D9CE3244.aspx
    //
    //File type filter...
    cmb1: integer  = $470; //Combo box with list of file type filters
    stc2: integer  = $441; //Label of the file type
    //File name const...
    cmb13: integer = $47c; //Combo box with name of the current file
    edt1: integer  = $480; //Edit with the name of the current file
    stc3: integer  = $442; //Label of the file name combo
  var H: THandle;
  begin
    H:= GetParent(Dialog.Handle);
    //Hide file types combo...
    SendMessage(H, CDM_HIDECONTROL, cmb1,  0);
    SendMessage(H, CDM_HIDECONTROL, stc2,  0);
    //Hide file name label, edit and combo...
    SendMessage(H, CDM_HIDECONTROL, cmb13, 0);
    SendMessage(H, CDM_HIDECONTROL, edt1,  0);
    SendMessage(H, CDM_HIDECONTROL, stc3,  0);
    //NOTE: How to change label text (the lentgh is not auto):
    //SendMessage(H, CDM_SETCONTROLTEXT, stc3, DWORD(pChar('Hello!')));
  end;
//Call it when you need the user to chose a folder for you...
function GimmeDir(var Dir: string): boolean;
var
  OpenDialog: TOpenDialog;
  OpenDir: TOpenDir;
begin
  //The standard dialog...
  OpenDialog:= TOpenDialog.Create(nil);
  //Objetc that holds the OnShow code to hide controls
  OpenDir:= TOpenDir.create;
  try
    //Conect both components...
    OpenDir.Dialog:= OpenDialog;
    OpenDialog.OnShow:= OpenDir.HideControls;
    //Configure it so only folders are shown (and file without extension!)...
    OpenDialog.FileName:= '*.';
    OpenDialog.Filter:=   '*.';
    OpenDialog.Title:=    'Chose a folder';
    //No need to check file existis!
    OpenDialog.Options:= OpenDialog.Options + [ofNoValidate];
    //Initial folder...
    OpenDialog.InitialDir:= Dir;
    //Ask user...
    if OpenDialog.Execute then begin
      Dir:= ExtractFilePath(OpenDialog.FileName);
      result:= true;
    end else begin
      result:= false;
    end;
  finally
    //Clean up...
    OpenDir.Free;
    OpenDialog.Free;
  end;
end;
Hajji answered 5/9, 2013 at 13:58 Comment(2)
I like this, but it's kind of annoying that you have to select the folder twice on the last step. And you can't select anything except the last directory in a tree.Doctrinaire
No matter what you set for InitialDir this opens up on the Documents folder which makes it useless.Mandolin
C
3

If you are using JVCL you can use TJvSelectDirectory. With this you can switch between old and new style by setting a property. For example:

Dlg := TJvSelectDirectory.Create(Self);
try
    Dlg.Title := MyTitle;
    Dlg.InitialDir := MyStartDir;
    Dlg.Options := Dlg.Options + [sdAllowCreate, sdPerformCreate];     
    Dlg.ClassicDialog := False;   //switch style
    if Dlg.Execute() then
      NewDir := Dlg.Directory;
finally
    Dlg.Free;
end; 
Carbrey answered 17/7, 2015 at 7:59 Comment(0)
T
0

This is a simple folder selector if anyone is still interested.

Code is attributed to its author in source. I don't see being available anymore.

Be sure to pass the owning form as AOwner to make sure this is a modal dialog.

unit FolderBrowser;

//by Johnny Mamenko, (c) 1999
//e-mail: [email protected]
//http://attend.to/johnny

interface

uses
  Windows, Messages, SysUtils, Classes, controls, shlobj, DntFunc;


type
  EFolderBrowserException = class(Exception);
  TBrowseFlag = (bfComputersOnly, bfPrintersOnly, bfDirsOnly, bfStatusText);
  TBrowseFlags = set of TBrowseFlag;

  TFolderChangeEvent = procedure ( const Folder: string;
                                   var EnabledOK : integer;
                                   //0  - Disables the OK button
                                   //1  - Enables the OK button
                                   //-1 - leave as is
                                   var StatusText : string) of object;

  TFolderBrowser = class (TComponent)
  private
    FTitle : string;
    FBrowseFlags : TBrowseFlags;
    FFolder: string;
    FOwnerHandle : HWND;
    FOnChangeFolder: TFolderChangeEvent;
    procedure SetFolder(const Value: string);
    procedure SetOnChangeFolder(const Value: TFolderChangeEvent);
  protected
  public
    constructor Create(AOwner : TComponent); override;
    function Execute: boolean;
  published
    property BrowseFlags : TBrowseFlags read FBrowseFlags write FBrowseFlags;
    property Folder : string read FFolder write SetFolder;
    property Title : string read FTitle write FTitle;
    property OnChangeFolder : TFolderChangeEvent read FOnChangeFolder write SetOnChangeFolder;
  end;

procedure Register;
function FolderCallBack(Wnd: HWND; uMsg: UINT; lParam, lpData: LPARAM): Integer stdcall;

implementation

var
  CurrentOpenedFolder : string;
  CurrentEventHandler : TFolderChangeEvent;

procedure Register;
begin
  RegisterComponents( 'Johnny', [ TFolderBrowser ] );
end;

function FolderCallBack(Wnd: HWND; uMsg: UINT; lParam, lpData: LPARAM): Integer stdcall;
var
  a  : array[0..MAX_PATH] of Char;
  EnabledOK : integer;
  StatusText, Folder : string;
begin
  Result:=0;
  if uMsg=BFFM_INITIALIZED then begin
    StrPCopy(a,CurrentOpenedFolder);
    SendMessage(Wnd, BFFM_SETSELECTION, 1, Integer(@a[0]));
    exit;
    end;//if uMsg=BFFM_INITIALIZED
  if uMsg=BFFM_SELCHANGED then begin
    EnabledOK:=-1;
    StatusText:='';
    SHGetPathFromIDList(Pointer(lParam),a);
    Folder:=StrPas(a);
    if Assigned(CurrentEventHandler) and (Folder<>'')
      then CurrentEventHandler(Folder, EnabledOK, StatusText);
    if EnabledOK<>-1 then SendMessage(Wnd, BFFM_ENABLEOK, EnabledOK, EnabledOK);
    if StatusText<>''
      then SendMessage(Wnd, BFFM_SETSTATUSTEXT, EnabledOK, Integer(PChar(StatusText)));
    end;//if uMsg=BFFM_SELCHANGED
end;

{TFolderBrowser}

constructor TFolderBrowser.Create(AOwner : TComponent);
begin
  if not(AOwner is TWinControl) then Raise EFolderBrowserException.Create('I need WinControl!!!');
  inherited Create(AOwner);
  FOwnerHandle:=(AOwner As TWinControl).Handle;
  FTitle:='Select Folder';
  FBrowseFlags:=[];
  FFolder:='';
end;

function TFolderBrowser.Execute: boolean;
var bi : TBrowseInfoA;
    a  : array[0..MAX_PATH] of Char;
    b : PChar;
    idl : PItemIDList;
begin
  b:=StrAlloc(Length(FTitle)+1);
  try
    StrPCopy(b,FTitle);
    bi.hwndOwner:=FOwnerHandle;
    bi.pszDisplayName:=@a[0];
    bi.lpszTitle:=b;
    bi.ulFlags:=BIF_BROWSEFORCOMPUTER*Byte(bfComputersOnly in BrowseFlags)+
                BIF_BROWSEFORPRINTER*Byte(bfPrintersOnly in BrowseFlags)+
                BIF_RETURNONLYFSDIRS*Byte(bfDirsOnly in BrowseFlags)+
                BIF_STATUSTEXT*Byte(bfStatusText in BrowseFlags);
    bi.lpfn:=FolderCallBack;
    bi.lParam:=0;
    bi.pidlRoot:=Nil;
    CurrentOpenedFolder:=FFolder;
    CurrentEventHandler:=FOnChangeFolder;
    idl:=SHBrowseForFolder(bi);
    if idl<>nil then begin
      SHGetPathFromIDList(idl,a);
      FFolder:=StrPas(a);
      Result:=true;
      end//if idl<>nil
    else Result:=false;
  finally
    StrDispose(b);
  end;//finally
end;

procedure TFolderBrowser.SetFolder(const Value: string);
begin
  FFolder:=Value;
end;

procedure TFolderBrowser.SetOnChangeFolder(const Value: TFolderChangeEvent);
begin
  FOnChangeFolder:=Value;
end;

initialization
  CurrentOpenedFolder:='';
  CurrentEventHandler:=Nil;
end.

[Screenshot of folder browser]

Tague answered 26/6 at 19:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.