2013年05月06日13:00 更新
ここでは、TBrowseInfoデータとSHBrowseForFolder、BrowseCallbackProcなどの関数を使い、自由に位置とサイズを指定し変更できるWindowsコモンダイアログ「フォルダー参照(選択)」ダイアログを作成表示する方法を示します。(2013-06-17 08:20追加)
以下はWindows共通の[フォルダーの参照(選択)]ダイアログに関する話です。
Windowsソフトでフォルダーを指定する場合、[フォルダーの参照]ダイアログ(以下単にダイアログとします)を使用するのが一般的です。ダイアログにはサイズと位置に関する不便があります。多くのプログラマーとユーザーがダイアログの不便をどのように凌いでいるかという視点から以下のように、ダイアログを3タイプに分類してみます。
ダイアログを自由にサイズ変更できますが、ダイアログの位置は自由にならないというタイプです。
起動時にダイアログの左上隅が画面上で常に同じ位置に固定されていて、ダイアログを大きくサイズ変更すると、次にダイアログを起動するとき、ダイアログの下部([OK]ボタン、[キャンセル]ボタン)が画面からはみ出して見えなくなることがあり、起動する度にダイアログのサイズと位置を調整しなくてはならないという不便があります。
このタイプはWindows(マイクロソフト)が提供するダイアログをそのまま使用するだけで、プログラムで特別な工夫を施しているわけではありません。
「位置よりサイズを重視するタイプ」と分類しましたが、実際はダイアログの位置とサイズについては不便を感じつつもそういう現状を諦観の心境で受け入れているだけではないかと思います。
ダイアログを希望する位置に表示できますが、ダイアログのサイズは変更できないというタイプです。
位置は親ウィンドウまたは画面の中心という場合が多いようです。
これはダイアログのサイズが小さく固定されているために、フォルダを選択するフォルダツリーの表示領域が狭くなり、フォルダの選択操作がやりにくいという不便があります。
このタイプはⅠタイプのように、ダイアログを希望する位置に表示できないためにダイアログの下部が画面からはみだしてみえなくなったりする不便を解消しようと、ダイアログの作成ログラムに一工夫を加えたものです。しかし、位置を自由に指定できる代償として、サイズ変更ができなくなり、小さいサイズで我慢しなくてはなりません。
ダイアログを自由にサイズ変更できますし、ダイアログの位置も自由に設定できるというタイプです。
位置は自由に設定できるといっても、たいてい親ウィンドウまたは画面の中心ということになります。
フォルダ選択を操作しやすくするために、ダイアログのサイズを大きく変更した場合でも再び起動すると、ダイアログが画面の中心(または希望する位置)に表示され、ダイアログの下部が画面からはみ出して見えなくなるという不便はありません。
このタイプはダイアログ作成プログラムでⅡタイプよりさらに工夫を凝らしたものです。
Windows世界の現状では、数多あるソフトのうち、限りなく100%に近い割合でⅠタイプかⅡタイプの[フォルダーの参照]ダイアログが使用されています。一番便利なⅢタイプは数えるほどしか実例がありません。
インターネット上で私が見つけたものですが、以下にⅢタイプの[フォルダーの参照]ダイアログを使用しているソフトを掲げておきます。もちろん、他にもまだまだあると思います。(2013-03-25)
実はⅢタイプの[フォルダーの参照]ダイアログを使用している、あるソフトに感銘を受けたのが動機ですが、私(鈴川エディタ作者)もⅢタイプのダイアログを作成したいと一念発起し一週間ばかり熱中しました。そして、Ⅲタイプのダイアログを作成しました。
以下に私が作成したⅢタイプのダイアログをテストする簡単なソフトを公開します。上記の話に興味のある方はお試しください。
下記の圧縮ファイル
SelectFolderTest.zip
には次の二つの実行ファイル
簡単ですので、ヘルプはありません。
以下に32bit版の操作手順を示します。64bit版も同じです。
下記の圧縮ファイルにソースファイルと実行ファイルが収納されています。
SelectFolderSource.zip
ソースファイルは開発言語DelphiXE2用ですが、他のプログラム言語がわかる方ならば、要点は理解できるでしょう。
もし、ソースファイルで間違いやこうした方がよいのではないかというご意見がございましたら、ご連絡願います。
[フォルダーの参照]ダイアログはWindowsソフトで共通に使用されているものです。私が公開したソースファイルを元にさらに改良した場合はその要点を公開してほしいと思います。
上記圧縮ファイルに含まれるファイルのうち、二つのソースファイルを示します。
改良した[フォルダーの参照]ダイアログの中心となるソースファイルです。
unit S_FolderEx;
interface
uses
Windows, Messages, Classes, Controls, ShlObj, ActiveX;
type
TFolderEx = class(TComponent)
private
FOwner: TObject;
FTitle: string;
FComment: string;
FDefaultFolder: string;
function GetFolderFromCommonDlg(AComment: string): string;
procedure SetWindow_Pos_Size;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function Execute: string;
property Title: string read FTitle write FTitle;
property Comment: string read FComment write FComment;
property DefaultFolder: string read FDefaultFolder write FDefaultFolder;
end;
var
FolderEx: TFolderEx = nil;
implementation
const
ID_EDIT = $3744; // Edit
ID_NEWBTN = $3746; // Button
var
BrowseInfo: TBrowseInfo;
hDlg: THandle = 0;
hEdit: THandle = 0;
hNewBtn: THandle = 0;
PathName: array[0..MAX_PATH] of Char;
PathNameA: array[0..MAX_PATH] of Char;
OrgProc: FARPROC = nil;
DidShow: Boolean = False;
TextBoxChanged: Boolean = False;
function SubProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): Integer; stdcall;
begin
case uMsg of
WM_SHOWWINDOW:
if not DidShow and (FolderEx <> nil) then
begin
DidShow := True;
FolderEx.SetWindow_Pos_Size;
end;
end;
Result := CallWindowProc(OrgProc, hWnd, uMsg, wParam, lParam);
end;
function BrowseCallbackProc(hWnd: HWND; uMsg: UINT; lParam: LPARAM; lpData: LPARAM): Integer; stdcall;
var
R: TRect;
Pt: TPoint;
begin
case uMsg of
BFFM_INITIALIZED:
begin
hDlg := hWnd;
hEdit := GetDlgItem(hDlg,ID_EDIT);
hNewBtn := GetDlgItem(hDlg,ID_NEWBTN);
if hNewBtn > 0 then
begin
SetWindowText(hNewBtn, '新規作成(&N)');
GetWindowRect(hNewBtn, R);
Pt := Point(R.Left, R.Top);
Windows.ScreenToClient(hDlg, Pt);
MoveWindow(hNewBtn, Pt.X, Pt.Y, 100, R.Bottom - R.Top, False);
end;
SendMessage(hWnd, BFFM_SETSELECTION, WPARAM(TRUE), lpData);
OrgProc := Pointer(GetWindowLong(hDlg, GWL_WNDPROC));
SetWindowLong(hDlg, GWL_WNDPROC, NativeInt(@SubProc));
end;
BFFM_SELCHANGED:
if BrowseInfo.ulFlags and BIF_EDITBOX = BIF_EDITBOX then
begin
TextBoxChanged := False;
SHGetPathFromIDList(PItemIDList(lParam), PathName);
if (hEdit > 0) and (PathName <> '') then
SendMessage(hEdit, WM_SETTEXT, 0, NativeInt(@PathName));
end;
BFFM_VALIDATEFAILED:
if BrowseInfo.ulFlags and BIF_EDITBOX = BIF_EDITBOX then
begin
TextBoxChanged := True;
Windows.GetWindowText(hEdit, PathNameA, MAX_PATH);
end;
end;
Result := 0;
end;
constructor TFolderEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FOwner := AOwner;
FTitle := 'フォルダーの参照';
FComment := '';
FDefaultFolder := 'C:\';
DidShow := False;
TextBoxChanged := False;
end;
destructor TFolderEx.Destroy;
begin
SetWindowLong(hDlg, GWL_WNDPROC, NativeInt(OrgProc));
inherited Destroy;
end;
procedure TFolderEx.SetWindow_Pos_Size;
var
R0,R: TRect;
W0,H0,W,H,L,T: Integer;
begin
if hDlg > 0 then
begin
Sleep(200);
SystemParametersInfo(SPI_GETWORKAREA, 0, R0, 0);
GetWindowRect(hDlg, R);
SetWindowText(hDlg, FTitle);
W0 := R0.Right - R0.Left;
H0 := R0.Bottom - R0.Top;
W := R.Right - R.Left;
H := R.Bottom - R.Top;
if W > W0 then
W := W0;
if H > H0 then
H := H0;
L := (W0 - W) div 2;
T := (H0 - H) div 2;
if L < R0.Left then
L := R0.Left;
if T < R0.Top then
T := R0.Top;
SetWindowPos(hDlg, HWND_TOP, L, T, W, H, SWP_SHOWWINDOW);
end;
end;
function TFolderEx.Execute: string;
begin
Result := '';
hDlg := 0;
Result := GetFolderFromCommonDlg(FComment);
end;
function TFolderEx.GetFolderFromCommonDlg(AComment: string): string;
var
FolderPidl: PItemIDList;
AMalloc: IMalloc;
begin
Result := '';
ShGetMalloc(AMalloc);
FolderPidl := nil;
FillChar(BrowseInfo, SizeOf(BrowseInfo), #0);
BrowseInfo.hwndOwner := TWinControl(FOwner).Handle;
BrowseInfo.lpfn := @BrowseCallbackProc;
BrowseInfo.lpszTitle := PChar(AComment);
BrowseInfo.pidlRoot := nil;
BrowseInfo.lParam := LPARAM(PChar(FDefaultFolder));
BrowseInfo.ulFlags := BIF_RETURNONLYFSDIRS or BIF_EDITBOX or BIF_VALIDATE or
BIF_NEWDIALOGSTYLE or BIF_BROWSEFORCOMPUTER;
FolderPidl := SHBrowseForFolder(BrowseInfo);
if FolderPidl <> nil then
try
if TextBoxChanged then
Result := PathNameA
else
begin
SHGetPathFromIDList(FolderPidl, PathName);
Result := PathName;
end;
finally
AMalloc.Free(FolderPidl);
end;
end;
end.
上記テストプログラム(SelectFolder32.exe、SelectFolder64.exe)のメインウィンドウのソースファイルです。
unit SelectFolderExF;
interface
uses
Windows, Messages, SysUtils, Classes, Forms, StdCtrls, Controls;
type
TSelectFolderExForm = class(TForm)
FolderBtn: TButton;
FolderEdit: TEdit;
FolderStaticText: TStaticText;
CloseBtn: TButton;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormShow(Sender: TObject);
procedure CloseBtnClick(Sender: TObject);
procedure FolderBtnClick(Sender: TObject);
private
InitDir: string;
public
{ Public 宣言 }
end;
var
SelectFolderExForm: TSelectFolderExForm = nil;
implementation
uses
S_FolderEx;
{$R *.dfm}
procedure TSelectFolderExForm.FormCreate(Sender: TObject);
begin
{$IFDEF WIN64}
Self.Caption := '[TEST] フォルダーの参照 64';
{$ELSE}
Self.Caption := '[TEST] フォルダーの参照 32';
{$ENDIF}
InitDir := '';
end;
procedure TSelectFolderExForm.FormShow(Sender: TObject);
var
R: TRect;
begin
R := Screen.WorkAreaRect;
Self.Left := R.Left + 10;
Self.Top := R.Top + 10;
end;
procedure TSelectFolderExForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
SelectFolderExForm := nil;
end;
procedure TSelectFolderExForm.CloseBtnClick(Sender: TObject);
begin
Self.Close;
end;
procedure TSelectFolderExForm.FolderBtnClick(Sender: TObject);
var
Dir: string;
begin
FolderEx := TFolderEx.Create(Self);
if FolderEx <> nil then
begin
try
{$IFDEF WIN64}
FolderEx.Title := 'フォルダーの参照 64';
{$ELSE}
FolderEx.Title := 'フォルダーの参照 32';
{$ENDIF}
FolderEx.Comment := 'フォルダを指定してください。';
if InitDir = '' then
FolderEx.DefaultFolder := 'C:\'
else
FolderEx.DefaultFolder := InitDir;
Dir := FolderEx.Execute;
if Dir <> '' then
begin
FolderEdit.Text := Dir;
InitDir := Dir;
end;
finally
FreeAndNil(FolderEx);
end;
end;
end;
end.