Reputation: 2110
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.
Thank you for all your answers.
Upvotes: 64
Views: 126360
Reputation: 7394
Just found the code below that seems to work fine in XP, Vista and 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 Win7:
//***********************
//** 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;
Upvotes: 6
Reputation: 1
This is a simple folder selector. Code is attributed to its author in the source - I don't see it being available anymore. Be sure to pass the owning form as AOwner
to make this is a modal dialog.
unit FolderBrowser;
//by Johnny Mamenko, (c) 1999
//e-mail: mamenko@iname.com
//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.
Upvotes: 0
Reputation: 109003
You do know that the two overloaded functions called FileCtrl.SelectDirectory
produce entirely different dialogs, right?
SelectDirectory(s, [], 0);
SelectDirectory('Select a directory', s, s, []);
Upvotes: 69
Reputation: 119
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.
Upvotes: 9
Reputation: 5566
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;
Upvotes: 3
Reputation: 109003
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)
Upvotes: 87