Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2014年06月22日] [更新 2015年12月11日] Delphi 一般・その他
ダイアログ関係の関数類
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
[整理番号 - 0860]



  • 2015年12月11日
  • システム関係のアイコンをリソースから取得する記事を追加




ダイアログ関係の関数類


主に Dialogs ユニットに実装されているダイアログ関数の紹介記事です。これらの関数類は、主にメッセージ表示用として使用されています。
Delphi のオンラインヘルプの、これらの関数の記事は、実際の動作とは違う記述があります。各自で確認してください。

ダイアログのテンプレートも用意されています。IDE の [ファイル] [新規作成] から選択して使用できます。これらは、ユニットを直接使用することも、継承して利用することもできます。

Windows のシステム関係ダイアログを表示するための専用関数はありません。
ShellExecute, ShellExecuteEx あるいは CreateProcess 関数等を使用して起動、表示します。

ダイアログを表示する際、位置とサイズを指定して表示するサンプルと、ダイアログのカスタマイズの例を掲載していますが、全てのダイアログに対する汎用的な方法はありません。また必ずしも実用性があるわけではありません。
コミュニティの場で見かけた質問内容を、実際に確認するためのテストもあります。



[備考]
カスタマイズしたダイアログを表示する確実な方法は TForm から自作することです。そうすれば表示位置や表示サイズも、フォントの大きさも自由に設計できます。
ただし、画像は自分で用意する必要があります。


図1
ダイアログのテンプレート

本ページで掲載しているダイアログ以外にも、以下のダイアログがあります。
これらは、主にデータベースのログイン (ログオン) の際に使用するダイアログです。これらのダイアログは、TForm あるいは TForm を継承したフォームで作成されています。戻り値は論理値です。



ログインダイアログは、デフォルトでは英語表記になるようです。ライブラリパスに以下を追加すると日本語表記になります。


$(BDS)\source\db\ja             // Delphi XE
$(BDS)\source\data\vclctrls\ja  // Delphi XE5



図2
DBLogDlg.LoginDialog
図3
DBLogDlg.LoginDialogEx
User Name 変更不可の DBLogDlg.LoginDialog
図4
DBLogDlg.RemoteLoginDialog
図5
DBPWDlg.PasswordDialog




01_ダイアログ関係コンポーネント


コンポーネントパレットにダイアログ表示用のコンポーネント類があります。これらは主に、ファイルを選択したり、保存したり、各種設定のためのダイアログです。
Delphi 2007 で、Windows Vista 対応になりました。それに伴って、Vista 専用のダイアログが実装されています。それらは [Vista Dialogs] タブにあります。

[Vista Dialogs] の中に TTaskDialog というのがあります。このダイアログは多機能です。
Delphi 2007 より前の Delphi でも、Windows API の TaskDialogIndirect 関数を使用すると、このダイアログと同じ機能のダイアログを作成することができます。



図6
ダイアログ関係コンポーネント
  • 上は [Dialogs] タブ
  • 下は [Vista Dialogs] タブで、以下のコンポーネントがある
    TFileOpenDialog
    TFileSaveDialog
    TTaskDialog

TTaskDialog は、Flags プロパティに tfPositionRelativeToWindow メンバを含めると、親ウィンドウの中央に表示されます。また、以下のように、OnDialogCreated イベント内で SetWindowsPos 関数等を使用して、ダイアログの表示位置を設定することができます。



リスト1
procedure TForm1.TaskDialog1DialogCreated(Sender: TObject);
var
  LPosX : Integer;
  LPosY : Integer;
begin
  LPosX := 100;
  LPosY := 100;
  SetWindowPos(TTaskDialog(Sender).Handle,
               0,
               LPosX,
               LPosY,
               0,
               0,
               SWP_NOSIZE or SWP_NOZORDER);
end;


[備考]
TOpenDialog コンポーネント等、エクスプローラ風のダイアログでは、ランタイムテーマが無効の時は、ダイアログのスタイルは Windows XP で表示した時と同じになります。ランタイムテーマが有効な場合、OnShow イベントの実現部を実装すると、ダイアログのススタイルは Windows XP で表示した時と同じになります。また、ランタイムテーマが有効な場合、継承クラスを作成して DoShow メソッドを実装しても、その DoShow メソッドは実行されません。
[Vista Dialogs] タブの TFileOpenDialog コンポーネント等には、OnShow イベントも DoShow メソッドもありません。




02_UseLatestCommonDialogs 変数


Windows Vista, 7 では、ダイアログのデザインが変更されています。
下の図は MessageDlg 関数でダイアログを表示したものです。最初の図が Windows Vista 以降でのダイアログです。UseLatestCommonDialogs 変数の値を False にすると、その下の図のように、ダイアログのデザインが Windows XP スタイルとなります。

UseLatestCommonDialogs 変数の値は、デフォルトで True となっています。Windows XP で実行する場合は、この変数の値は無視され、False と同じ結果となります。



図7
UseLatestCommonDialogs が True の時
  • Windows Vista スタイルのダイアログ
図8
UseLatestCommonDialogs が False の時
  • Windows XP スタイルのダイアログ

リスト2
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  Dialogs.UseLatestCommonDialogs := True;

  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtInformation, [mbYes], 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  Dialogs.UseLatestCommonDialogs := False;

  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtInformation, [mbYes], 0);
end;




03_Dialogs.ShowMessage  -  [ OK ] ボタンだけの簡単なダイアログ


[OK] ボタンだけの単純なダイアログです。
ダイアログのキャプションはアプリケーションのタイトルとなります。関数形式ではありません。したがって戻り値はありません。


図9
[OK] ボタンだけのダイアログ

リスト3
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.ShowMessage(StrText);
end;




04_Dialogs.ShowMessageFmt  -  書式設定機能付きダイアログ


書式設定の機能付きの ShowMessage です。


図10
書式設定機能付き ShowMessage

リスト4
procedure TForm1.Button1Click(Sender: TObject);
var
  hWindow : HWND;
begin
  hWindow := Handle;
  Application.Title := 'ダイアログ関係関数テスト';
  Dialogs.ShowMessageFmt('%s $%.8x ( %.d )', ['16 進数表示', hWindow, hWindow]);
end;




05_Dialogs.ShowMessagePos  -  位置指定ダイアログ


表示位置が指定可能な ShowMessage です。


図11
位置を指定して表示した例

リスト5
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
  hWindow : HWND;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.ShowMessagePos(StrText, Self.Left + 20, Self.Top + 100);
end;




06_Application.ShowException 関数  -  例外処理内のダイアログ


例外処理の中で使用するダイアログ関数です。
例外処理の中で意識的に使用しない場合は、このダイアログが自動的に表示されます。


図12
例外処理の中で使用するダイアログ

リスト6
procedure TForm1.Button1Click(Sender: TObject);
var
  A : Integer;
  B : Integer;
begin
  A := 0;
  try
    B := 123 div A;
  except 
    on E: EZeroDivide do
      Application.ShowException(E);
  end;
end;


例外処理のクラスと例外メッセージの文字列は、例えば次のようなコードで取得できます。
例外やエラーが発生した場合、どんな例外が発生したのかを調べる目的に使用できます。



図13
例外処理クラスと例外メッセージ文字列の取得

リスト7
procedure TForm1.Button2Click(Sender: TObject);
var
  A : Integer;
  B : Integer;
  StrText : String;
begin
  A := 0;
  try
    B := 123 div A;
  except
    on E: Exception do begin
      StrText := E.ClassName + sLineBreak + E.Message;
      Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
    end;
  end;
end;




07_Application.MessageBox 関数


TApplication コンポーネントの MessageBox 関数の実行例です。
この関数は UseLatestCommonDialogs 変数の値の影響を受けません。表示するボタンも複数表示できますが、MessageDlg 関数と違い、[すべて] のボタンを表示する機能はありません。

この関数は、内部で Windows API の MessageBox 関数を実行しています。このように、内部で他の関数を実行している関数を、その関数のラッパ関数といっています。ラッピング、つまり包み込んでいるという意味です。Windows API の MessageBox 関数のラッパともいいます。

下図は、表示するアイコンを変えて実行した結果です。アイコンは、指定しなければ表示しません。
また、MB_TOPMOST をフラグに含めると、Z オーダーの最上位として表示され、他のウインドウがアクティブになっても、最前面に表示されたままとなります。



図14
アイコンの指定が MB_ICONWARNING の場合
図15
アイコンの指定が MB_ICONINFORMATION の場合
図16
アイコンの指定が MB_ICONQUESTION の場合
図17
アイコンの指定が MB_ICONSTOP の場合

リスト8
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Application.MessageBox(PChar(StrText), '情報', MB_ICONWARNING or MB_TOPMOST);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Application.MessageBox(PChar(StrText), '情報', MB_ICONQUESTION);
end;

procedure TForm1.Button4Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Application.MessageBox(PChar(StrText), '情報', MB_ICONSTOP,);
end;


複数のボタンを表示した場合の処理の参考です。


図18
複数のボタンを表示した場合
  • 押下ボタンは、関数の戻り値で判定

リスト9
procedure TForm1.Button5Click(Sender: TObject);
var
  StrText : String;
  iRet    : Integer;
begin
  StrText := 'ダイアログ関係関数テスト';
  iRet :=  Application.MessageBox(PChar(StrText), '情報',
                                  MB_YESNOCANCEL or MB_ICONQUESTION,);
  if iRet = IDYES then begin
    //[はい]を選択した時の処理
  end else
  if iRet = IDNO then begin
    //[いいえ]を選択した時の処理
  end else
  if iRet = IDCANCEL then begin
    //[キャンセル]を選択した時の処理
  end;
end;


複数のボタンを表示した場合、デフォルトで、どのボタンにフォーカスするかを指定できます。
ダイアログを表示した直後、[Enter] キーを押下すると、このボタンクリックと同じになります。図では [いいえ] をデフォルトのボタンにしています。


図19
デフォルトのフォーカスボタンの指定

リスト10
procedure TForm1.Button6Click(Sender: TObject);
var
  StrText : String;
  iRet    : Integer;
begin
  StrText := 'ダイアログ関係関数テスト';
  iRet := Application.MessageBox(PChar(StrText), '情報',
                         MB_YESNOCANCEL or MB_DEFBUTTON2 or MB_ICONQUESTION);
  if iRet = IDYES then begin
    //[はい]を選択した時の処理
  end else
  if iRet = IDNO then begin
    //[いいえ]を選択した時の処理
  end else
  if iRet = IDCANCEL then begin
    //[キャンセル]を選択した時の処理
  end;
end;


Windows Vista, Windows 7 であれば、Application.MessageBox 関数で表示するダイアログのフォントを変更できます。[ウィンドウの色とデザイン] のダイアログで [メッセージボックス] の色とフォントサイズを変更します。





08_Windows.MessageBox 関数


ダイアログのオーナーハンドルを指定すること以外は TApplication コンポーネントの同名の関数と機能は同じです。Application.MessageBox 関数のオーナーウィンドウは、ダイアログを表示する時にアクティブなフォームです。

以下のコードでは、オーナウィンドウのハンドルを表示元のフォームとしています。ダイアログを閉じるまでは、このフォームをアクティブにはできません。

オーナーハンドルを 0 にすると、ダイアログの表示中でもフォームにフォーカスが移動できます。フォームを閉じるとダイアログも閉じます。オーナーハンドルが 0 の場合は、オーナーを持たなウインドウです。ダイアログの表示中に他のアプリケーションのウィンドウ等を操作すると、ダイアログを閉じた時にフォームが最前面に表示される保証はありません。



リスト11
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Windows.MessageBox(Handle, PChar(StrText), '情報', MB_YESNO or MB_ICONASTERISK);
end;




09_Dialogs.MessageDlg 関数  -  自動キャプションダイアログ


表示するアイコンの種類によってキャプションが自動で決まるダイアログです。
表示するアイコンを mtCustom にするとアイコンを表示しません。その場合、キャプションはアプリケーションのタイトルとなります。
このダイアログは [すべて] のボタンを表示できます。ファイルを全て選択するとか、文字列の置換等で全てを実行する際に利用できます。

下図は、表示するアイコンを変えて実行した結果です。



図20
アイコンの指定が mtWarning の場合
図21
アイコンの指定が mtInformation の場合
図22
アイコンの指定が mtConfirmation の場合
  • 実際のアイコンが疑問符 (クエスチョンマーク) にならない理由は、後述の記事を参照
図23
アイコンの指定が mtError の場合

リスト12
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtWarning, [mbYes], 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtInformation, [mbYes], 0);
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtConfirmation, [mbYes], 0);
end;

procedure TForm1.Button4Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtError, [mbYes], 0);
end;


MessageDlg 関数も MessageBox 関数と同じく複数のボタンを表示できます。また、デフォルトでフォーカスを与えるボタンを指定できます。
どのボタンを選択したかは、下のコードのように、関数の戻り値で判定します。


図24
表示の時に [キャンセル] ボタンにフォーカスを与えた場合
  • ただし実用的には問題がある。[Enter] キーで [キャンセル] になってしまう
  • 通常 [Enter] キーは決定の意味

リスト13
procedure TForm1.Button5Click(Sender: TObject);
var
  StrText : String;
  iRet    : Integer;
begin
  StrText := 'ダイアログ関係関数テスト';
  iRet := MessageDlg(StrText, mtConfirmation, [mbYes, mbCancel], 0, mbCancel);
  if iRet = mrCancel then begin
    //キャンセルの時の処理
  end;
end;




10_Dialogs.MessageDlgPos 関数  -  位置指定


MessageDlg 関数に、表示位置を指定する引数が追加されたダイアログです。


図25
位置を指定して表示した例
  • 表示した時に [すべて] にフォーカスを与えている

リスト14
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText  : String;
  iRet     : Integer;
begin
  StrText  := 'ダイアログ関係関数テスト';
  iRet := Dialogs.MessageDlgPos(StrText,
                                mtInformation,
                                [mbYes, mbAll, mbCancel],
                                0,
                                Self.Left + 20,
                                Self.Top + 100,
                                mbAll);
  if iRet = mrYes then begin
    //[はい]を選択した時の処理
  end else
  if iRet = mrAll then begin
    //[全て]を選択した時の処理
  end else
  if iRet = mrCancel then begin
    //[キャンセル]を選択した時の処理
  end;
end;




11_Dialogs.MessageDlgPosHelp 関数  -  状況感知ヘルプ表示


MessageDlg 関数に、表示位置を指定する引数と、状況感知型ヘルプ (状況依存型ヘルプ) ファイルを指定する引数を追加したダイアログです。

下のコードでは、ヘルプファイルとして、当サイトのトップページを指定しています。ただし、当サイトの HTML ファイルは、HTML ヘルプとして作成していません。したがって、位置やサイズを変更した後に再表示しても、デフォルトの位置とサイズで表示されることになります。
HTML ヘルプファイルの表示には、uses に HTMLHelpViewer が必要です。



図26
位置を指定してダイアログを表示
図27
ダイアログを表示して [F1] を押下
  • ヘルプファイルとしてのウェブページを表示
  • 右上のウィンドウがそのヘルプ

リスト15
implementation

uses HTMLHelpViewer;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
  HelpFile : String;
begin
  HelpFile := 'http://mrxray.on.coocan.jp/index.htm';

  StrText := '[F1] 押下で状況感知型ヘルプ表示';
  Dialogs.MessageDlgPosHelp(StrText,
                            mtInformation,
                            [mbYes],
                            1,
                            Self.Left + 20,
                            Self.Top + 100,
                            HelpFile);
end;




12_Dialogs.TaskMessageDlg 関数 - タイトル付きダイアログ


Windows Vista 以降で使用可能なダイアログ関数です。機能的には MessageDlg 関数と同じですが、タイトルを指定する引数が追加されています。
MessageDlg 関数は、以下の条件の時に、この TaskMessageDlg 関数を実行します。
  • Windows Vista 以降である
  • ランテーマが有効となっている
  • UseLatestCommonDialogs 変数の値が True である
MessageDlg 関数は、タイトル文字列の指定がない TaskMessageDlg 関数と同じです。
MessageDlg 関数と同じく、デフォルトでフォーカスを与えるボタンを指定できます。


図28
TaskMessgeDlg ダイアログの表示例
  • タイトルの指定が可能

リスト16
procedure TForm1.Button1Click(Sender: TObject);
var
  StrTitle : String;
  StrText  : String;
begin
  if not CheckWin32Version(6, 0) then exit;

  StrTitle := 'ダイアログのタイトル';
  StrText  := 'ダイアログ関係関数テスト';
  Dialogs.TaskMessageDlg(StrTitle, StrText, mtInformation, [mbYes, mball], 0);
end;




13_Dialogs.TaskMessageDlgPos 関数 - 位置指定


TaskMessageDlg 関数に、表示位置を指定する引数が追加されたダイアログです。


図29
位置を指定して表示した例
  • デフォルトのフォーカスを [すべて] のボタンにしている

リスト17
procedure TForm1.Button1Click(Sender: TObject);
var
  StrTitle : String;
  StrText  : String;
  iRet     : Integer;
begin
  if not CheckWin32Version(6, 0) then exit;

  StrTitle := 'ダイアログのタイトル';
  StrText  := 'ダイアログ関係関数テスト';
  iRet := Dialogs.TaskMessageDlgPos(StrTitle,
                                    StrText,
                                    mtInformation,
                                    [mbYes, mbAll, mbCancel],
                                    0,
                                    Self.Left + 20,
                                    Self.Top + 100,
                                    mbAll);
  if iRet = mrYes then begin
    //[はい]を選択した時の処理
  end else
  if iRet = mrAll then begin
    //[全て]を選択した時の処理
  end else
  if iRet = mrCancel then begin
    //[キャンセル]を選択した時の処理
  end;
end;




14_Dialogs.TaskMessageDlgPosHelp 関数  -  状況感知ヘルプ表示


TaskMessageDlg 関数に、表示位置を指定する引数と、状況感知型ヘルプ (状況依存型ヘルプ) ファイルを指定する引数を追加したダイアログです。

下のコードでは、ヘルプファイルとして、当サイトのトップページを指定しています。ただし、当サイトの HTML ファイルは、HTML ヘルプとして作成していません。したがって、位置やサイズを変更した後に再表示しても、デフォルトの位置とサイズで表示されることになります。
HTML ヘルプファイルの表示には、uses に HTMLHelpViewer が必要です。



図30
位置を指定してダイアログを表示
図31
ダイアログを表示して [F1] を押下
  • ヘルプファイルとしてのウェブページを表示
  • 右上のウインドウがそのヘルプ

リスト18
implementation

uses HTMLHelpViewer;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  StrTitle : String;
  StrText  : String;
  HelpFile : String;
begin
  if not CheckWin32Version(6, 0) then exit;

  StrTitle := 'ダイアログのタイトル';
  StrText  := 'ダイアログ関係関数テスト';
  HelpFile := 'http://mrxray.on.coocan.jp/index.htm';

  Dialogs.TaskMessageDlgPosHelp(StrTitle,
                                StrText,
                                mtInformation,
                                [mbYes],
                                1,
                                Self.Left + 20,
                                Self.Top + 100,
                                HelpFile);
end;




15_Dialogs.InputBox 関数  -  文字列や数値の入力用


数値や文字列の入力用ダイアログです。戻り値は、入力した値で、文字列です。
入力した値が、元の値と同じかを確認する用途に向いています。この関数は InputQuery 関数 のラッパ関数です。内部で InputQuery 関数を実行しています。

InputBox ダイアログと InputQuery ダイアログをカスタマイズする場合、カスタマイズの内容にもよりますが、InputQuery のソースコードを参考にして、自前でダイアログを作成してしまった方がいいかも知れません。



図32
InputBox ダイアログ関数

リスト19
procedure TForm1.Button1Click(Sender: TObject);
var
  StrCaption : String;
  StrPrompt  : String;
  StrDefault : String;
  StrResult  : String;
begin
  StrCaption := '入力ダイアログ';
  StrPrompt  := '値の入力';
  StrDefault := '12345';

  StrResult := InputBox(StrCaption, StrPrompt, StrDefault);
  if StrResult = StrDefault then begin
    //[キャンセル]ボタンを選択した時、または入力値に変化がない場合
    //または[ESC]でダイアログを閉じた場合
  end else begin
    //[OK]ボタンを選択した時
  end;
end;




16_Dialogs.InputQuery 関数  -  文字列や数値の入力用


数値や文字列の入力用ダイアログです。戻り値は論理値です。
[OK] でダイアログを閉じた時は True、それ以外は False となります。入力した値は、最後の引数に文字列として返されてきます。ダイアログを表示した時の初期値もこの引数で指定します。
InputBox 関数では、初期値と入力した値を独立して扱えますが、この InputQuery 関数では兼用となっています。

このダイアログをカスタマイズする場合、カスタマイズの内容にもよりますが、InputQuery のソースコードを参考にして、自前でダイアログを作成してしまった方がいいかも知れません。


図33
InputQuery ダイアログ関数

リスト20
procedure TForm1.Button1Click(Sender: TObject);
var
  StrCaption : String;
  StrPrompt  : String;
  StrValue     : String;
begin
  StrCaption := '入力ダイアログ';
  StrPrompt  := '値の入力';
  StrValue   := '12345';

  if InputQuery(StrCaption, StrPrompt, StrValue) then begin
    //[OK]ボタンを選択した時
  end else begin
    //[キャンセル]ボタンを選択した時、または[ESC]で閉じた場合
  end;
end;




17_Dialogs.CreateMessageDialog 関数  -  カスタマイズ自由


ダイアログを表示する関数ではありません。ダイアログを作成する関数です。
戻り値は TForm のクラス型です。したがって、自由にカスタマイズできます。
ダイアログ上 (フォーム上) のコンポーネントは、追加したコンポーネントも含めて、コンポーネントの配列に格納されます。

このダイアログ関数も、デフォルトでフォーカスを与えるボタンを指定できます。



図34
ダイアログの表示例
  • [ヘルプ] ボタンを表示してみた
  • ダイアログのタイプ (TMsgDlgType) に mbHelp を含めると、自動的に [ヘルプ] ボタンも表示される
  • デフォルトのフォーカスを [すべて] のボタンにしている
図35
カスタマイズしたダイアログの例
  • デフォルトのフォーカスを [ALL] のボタンにしている

リスト21
procedure TForm1.Button1Click(Sender: TObject);
var
  FrmDlg  : TForm;
  StrMsg  : String;
  iRet    : Integer;
begin
  StrMsg := 'ダイアログ関係関数テスト';
  FrmDlg := CreateMessageDialog(StrMsg,
                                mtInformation,
                                [mbYes, mbAll, mbCancel],
                                mbAll);
  try
    //オーナウィンドウの中央に表示(この場合はTForm1(Form1)
    FrmDlg.Position := poOwnerFormCenter;
    //[ヘルプ]ボタンを表示してみる
    FrmDlg.BorderIcons := FrmDlg.BorderIcons + [biHelp];

    iRet := FrmDlg.ShowModal;

    if iRet = mrYes then begin
      //[はい]でダイアログを閉じた場合
    end else
    if iRet = mrAll then begin
      //[すべて]でダイアログを閉じた場合
    end else
    if iRet = mrCancel then begin
      //[キャンセル]でダイアログを閉じた場合
    end;
  finally
    FrmDlg.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  FrmDlg : TForm;
  StrMsg : String;
  iRet   : Integer;
begin
  StrMsg := 'ダイアログ関数のテスト';
  FrmDlg := CreateMessageDialog(StrMsg,
                                mtConfirmation,
                                [mbYes, mbAll, mbCancel],
                                mbAll);
  try
    //表示前にダイアログ上のコンポーネント類のプロパティを変更
    TLabel(FrmDlg.Components[1]).Width      := FrmDlg.Width;
    TLabel(FrmDlg.Components[1]).Font.Size  := 15;
    TLabel(FrmDlg.Components[1]).Font.Color := clGreen;
    TLabel(FrmDlg.Components[1]).Font.Style := [fsBold];

    TButton(FrmDlg.Components[2]).Caption := 'YES';
    TButton(FrmDlg.Components[3]).Caption := 'CANCEL';
    TButton(FrmDlg.Components[4]).Caption := 'ALL';
    FrmDlg.Height := 200;
    FrmDlg.Left   := Self.Left + 33;
    FrmDlg.Top    := Self.Top + 50;

    iRet := FrmDlg.ShowModal;

    if iRet = mrYes then begin
      //[はい]でダイアログを閉じた場合
    end else
    if iRet = mrAll then begin
      //[すべて]でダイアログを閉じた場合
    end else
    if iRet = mrCancel then begin
      //[キャンセル]でダイアログを閉じた場合
    end;
  finally
    FrmDlg.Free;
  end;
end;


下図は、CreateMessageDialog 関数で作成したダイアログのコンポーネントを調べたものです。


図36
ダイアログ上のコンポーネントの一覧
  • コンポーネントの配列として格納されている
  • コンポーネントのクラスでキャストして操作する

リスト22
procedure TForm1.Button1Click(Sender: TObject);
var
  FrmDlg : TForm;
  StrMsg : String;
begin
  StrMsg := 'ダイアログ関係関数テスト';
  FrmDlg := CreateMessageDialog(StrMsg,
                                mtInformation,
                                [mbYes, mbAll, mbCancel],
                                mbAll);
  try
    //イベントを定義してダイアログを表示
    FrmDlg.OnShow := OnFrmShow;
    FrmDlg.ShowModal;
  finally
    FrmDlg.Free;
  end;
end;

procedure TForm1.OnFrmShow(Sender: TObject);
var
  i      : Integer;
  aCompo : TComponent;
begin
  Memo1.Lines.Clear;
  for i := 0 to TForm(Sender).ComponentCount - 1 do begin
    aCompo := TForm(Sender).Components[i];
    Memo1.Lines.Add('Components[' + IntToStr(i) +'] : ' +  aCompo.ClassName);
  end;
end;


ダイアログにボタンを追加する例です。
追加したボタンの位置やサイズは自由に変更ですます。元々のボタン類の位置やサイズも、上のサンプルのように、コンポーネント配列の値を使用して、自由に変更できます。
CreateMessageDialog を常用するのであれば、元のソースコードを変更してしまうのも一つの手かもしれません。CreateMessageDialog の継承関係は次のようになっています。
Result が CreateMessageDialog 関数が生成するダイアログです。

TForm --> TMessageForm --> Result := TMessageForm.CreateNew(Application)


図37
ダイアログにボタンを追加

リスト23
procedure TForm1.Button1Click(Sender: TObject);
var
  FrmDlg : TForm;
  StrMsg : String;
  ABtn   : TButton;

begin
  StrMsg := 'ダイアログ関係関数テスト';
  FrmDlg := CreateMessageDialog(StrMsg,
                                mtInformation,
                                [mbYes, mbAll, mbCancel],
                                mbAll);
  try
    //ダイアログにボタンを追加して表示位置を設定
    ABtn := TButton.Create(FrmDlg);
    ABtn.Parent := FrmDlg;
    ABtn.Left   := TButton(FrmDlg.Components[4]).Left;
    ABtn.Top    := TButton(FrmDlg.Components[4]).top - 30;

    FrmDlg.ShowModal;
  finally
    FrmDlg.Free;
  end;
end;




18_Dialogs.PromptForFileName 関数  -  [ 開く ] [ 保存 ] ダイアログ


[ファイルを開く] または [名前を付けて保存] のダイアログを表示する関数です。
TOpenDialog または TSaveDialog コンポーネントの代わりに使用できますが、該当するダイアログコンポーネントのような詳細な設定はできません。


図38
[ファイルを開く] ダイアログの例

リスト24
procedure TForm1.Button1Click(Sender: TObject);
var
  AFileName   : String;
  AInitialDir : String;
begin
  AFileName   := '';
  AInitialDir := ExpandFileName('../');
  if PromptForFileName(AFileName,
                       'ユニットファイル (*.pas)|*.pas',
                       '',
                       '',
                       AInitialDir,
                       False)
  then begin
    //[開く]または[保存]でダイアログを閉じた場合
  end else begin
    //[キャンセル]でダイアログを閉じた場合
  end;
end;




19_フォルダの選択ダイアログ


FileCtrl ユニットにフォルダ選択用のダイアログ関数が実装されています。
この関数を使用したサンプルを、下記のリンクに掲載しています。

Delphi 2007 で実装された TFileOpenDialog ダイアログコンポーネントは、フォルダの選択ダイアログとしても使用できます。
Options プロパティに fdoPickFolders フラグがあると、下図のようにフォルダの選択ダイアログになります。ルートフォルダのような指定はないようです。このダイアログは Windows Vista 以降でないと使用できません。
デザインはファイルの選択 (ファイルを開く) ダイアログと同じです。



図39
フォルダの選択

リスト25
procedure TForm1.Button1Click(Sender: TObject);
begin
  if not CheckWin32Version(6, 0) then exit;

  FileOpenDialog1.Options := [fdoPickFolders, fdoPathMustExist, fdoForceFileSystem];
  FileOpenDialog1.Execute;
end;




20_ダイアログの疑問符アイコン表示


Windows Vista 以降で、アイコンに mtConfirmation を指定した場合の現象です。
MessageDlg 関数では、実際には感嘆符のアイコンとなります。また、TaskMessageDlg 関数でも疑問符のアイコンではなく、感嘆符のアイコンが表示されます。

TTaskDialog ダイアログコンポーネントの場合、MainIcon プロパティを 32514 にすると、疑問符になります。MainIcon プロパティは、オブジェクト・インスペクタで設定できます。TTaskDialog コンポーネントの機能は、Windows XP 以前では利用できません。

UseLatestCommonDialogs の値を False にすれば疑問符のアイコンが表示されますが、ダイアログのスタイルは Windows XP スタイルとなります。
この現象は、アイコン表示のガイドラインが変更になったことが原因のようです。詳細は、下記のリンクの記事で確認してください。



図40
UseLatestCommonDialogs = True の時
  • 疑問符を指定しても、感嘆符のアイコンが表示される
図41
UseLatestCommonDialogs = False の時
  • 指定した通り疑問符が表示される
  • ただし、Windows XP スタイル

リスト26
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtConfirmation, [mbYes], 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  Dialogs.UseLatestCommonDialogs := False;

  StrText := 'ダイアログ関係関数テスト';
  Dialogs.MessageDlg(StrText, mtConfirmation, [mbYes], 0);

  Dialogs.UseLatestCommonDialogs := True;
end;




21_ダイアログの表示位置とサイズの制御  -  CreateMessageDialog


CreateMessageDialog 関数で作成したダイアログは TForm 型です。したがって、位置の設定が自由にできます。もちろん、サイズの変更も、フォントも自由に設定できます。





22_ダイアログの表示位置とサイズの制御  -  ローカルフック


ほとんどのダイアログは、その表示位置は画面中央となっています。位置を指定できるダイアログはありますが、表示前にサイズを知ることができません。したがって、フォームの中央に表示するという設定はできません。

Windows のダイアログのクラス名は #32770 (= #$8000) です。
次のコードは、WH_CALLWNDPROC フック関数をローカルフックとして登録します。フックのコールバック関数内で、ダイアログのウィンドウが表示される時の WM_SHOWWINDOW メッセージを捕捉します。そして、クラス名が #32770 のウィンドウの位置を設定します。

フックはローカルフックとします。グローバルフックにしてしまうと、システム全体に対して有効になってしまい、クラス名が #32770 のダイアログ全てが、このコードで設定する位置に表示されてしまうことになります。

TOpenDialog, TSaveDialog 等では、ナビゲーションウィンドウの表示有無、あるいはサイズを変更した直後は、計算通りの位置に表示できない場合があります。



図42
MessageDlg 関数でダイアログを表示
  • Form1 の上下左右の中央に表示
図43
TOpenDialog でダイアログを表示
  • Form1 の上下左右の中央に表示
図44
TFindDialog でダイアログを表示
  • Form1 の上下左右の中央に表示

リスト27
implementation

{$R *.dfm}

var
  MsgHook: HHOOK;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassNameStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetClassName(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  WH_CALLWNDPROCフックのコールバック関数
//  ダイアログのWM_SHOWWINDOWメッセージを処理
//
//  ここではMsg : PMsgではなくMsg: PCWPStructにして、wParamの値を受け取る
//  WM_SHOWWINDOWのwParamは1の時表示開始、閉じる時は0
//
//  Windowsのダイアログのクラス名は#32770
//-----------------------------------------------------------------------------
function GetMsgProc(Code: Integer; WPar: WPARAM; LPar: LPARAM): LRESULT; stdcall;
var
  pMsgStruct : PCWPStruct;
  ClassName  : String;
  hWindow    : HWND;
  DlgRect    : TRect;
  DlgWidth   : Integer;
  DlgHeight  : Integer;
  DlgPosX    : Integer;
  DlgPosY    : Integer;
begin
  if (code < 0) then begin
    Result := CallNextHookEx(MsgHook, Code, WPar, LPar);
    exit;
  end;

  if (code = HC_ACTION) then begin
    pMsgStruct := PCWPStruct(LPar);
    if pMsgStruct^.message = WM_SHOWWINDOW then begin
      if pMsgStruct^.wParam = 1 then begin
        ClassName := GetWindowClassNameStr(pMsgStruct.hwnd);
        if ClassName = '#32770' then begin

          //ダイアログのハンドルを取得してサイズを取得
          hWindow := pMsgStruct^.hwnd;
          GetWindowRect(hWindow, DlgRect);
          //ダイアログの幅と高さを取得
          DlgWidth  := DlgRect.Right  - DlgRect.Left;
          DlgHeight := DlgRect.Bottom - DlgRect.Top;

          //Form1の上下左右の中央に表示する
          DlgPosX := Form1.Left + (Form1.Width  - DlgWidth)  div 2;
          DlgPosY := Form1.Top  + (Form1.Height - DlgHeight) div 2;
          SetWindowPos(hWindow,
                       0,
                       DlgPosX,
                       DlgPosY,
                       0,
                       0,
                       SWP_NOSIZE or SWP_NOZORDER);
        end;
      end;
    end;
  end;

  Result := CallNextHookEx(MsgHook, Code, WPar, LPar);
end;

//=============================================================================
//  ローカルフック開始
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  MsgHook := SetWindowsHookEx(WH_CALLWNDPROC, GetMsgProc, 0, GetCurrentThreadID);
end;

//=============================================================================
//  フック解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnHookWindowsHookEx(MsgHook);
end;

//=============================================================================
//  ダイアログを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログの表示位置の制御';
  Dialogs.MessageDlg(StrText, mtInformation, [mbYes], 0);
end;




23_ダイアログの表示位置とサイズの制御  -  PostMessage


表示したダイアログの操作は、次のようなコードでも可能です。
PostMessage でメッセージをメッセージキューにポスト (書き込み) します。PostMessage は処理の結果を待たずにに戻ってきます。その直後に何かしらの操作をすると、ポストされたメッセージの処理が行われることを利用しています。

使用するメッセージは何でも構いません。下のサンプルでは、他の動作にあまり関係がないと思われる WM_APP_XXX を使用しています。
TOpenDialog 等の Windows のコモンダイアログをカプセル化したダイアログは、この方法では制御できません。また、ダイアログ表示前にメッセージキューに対して、何かしらの操作が行われた場合も制御できません。前項のローカルフックの方が、動作は確実です。



リスト28
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  protected
    procedure WMApp(var Message: TMessage); message WM_APP;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  ダイアログを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  PostMessage(Handle, WM_APP, 0, 0);
  MessageDlg('ダイアログ位置設定', mtInformation, [mbYes], 0);
end;

//=============================================================================
//  メッセージ処理
//  ダイアログ表示直前にPosMessageでメッセージキューに書き込んでおく
//=============================================================================
procedure TForm1.WMApp(var Message: TMessage);
var
  hWindow   : HWND;
  DlgRect   : TRect;
  DlgWidth  : Integer;
  DlgHeight : Integer;
  DlgPosX   : Integer;
  DlgPosY   : Integer;
begin
  inherited;

  hWindow := FindWindow('#32770', nil);
  //ウィンドウのタイトルが分かっているなら以下の方が確実
  hWindow := FindWindow(nil, '情報');

  if hWindow <> 0 then begin
    GetWindowRect(hWindow, DlgRect);
    //ダイアログの幅と高さを取得
    DlgWidth  := DlgRect.Right  - DlgRect.Left;
    DlgHeight := DlgRect.Bottom - DlgRect.Top;

    //Form1の上下左右の中央に表示する
    DlgPosX := Form1.Left + (Form1.Width  - DlgWidth)  div 2;
    DlgPosY := Form1.Top  + (Form1.Height - DlgHeight) div 2;
    SetWindowPos(hWindow,
                 0,
                 DlgPosX,
                 DlgPosY,
                 0,
                 0,
                 SWP_NOSIZE or SWP_NOZORDER);
  end;
end;

end.




24_システム関係ダイアログの表示位置やサイズの制御


Windows のシステム関係ダイアログの表示位置を設定する例です。
システム関係のダイアログを表示する関数はありません。ShellExecute 関数等で、別のアプリのダイアログとして表示します。
システムのダイアログに関しては、専用のフックが用意されています。TApplication コンポーネントの HookMainWindow メソッドでフックを開始して、UnhookMainWindow メソッドでフックを終了します。それらを使用したサンプルです。

システムでは、クラス名が #32770 のダイアログが複数起動していることがあります。Windows やアプリケーションのアップデートの案内がダイアログを使用していることがあります。これらは、タスクバーの通知領域 (タスクトレイ) に、アイコンで表示されている場合があります。

自アプリでダイアログを表示すると、そのダイアログは、Z オーダーがトップの、最前面のウインドウとして表示されます。そこで、下のコードでは、Z オーダーがトップのダイアログを検出したら、フラグをセットします。フラグには論理型変数を使用しています。この値が False の時だけ、フックプロシージャー内の処理を実行するようにしています。
この処理をしないと、起動したダイアログを閉じても、他のダイアログにフォーカスが移動してしまい、自アプリがアクティブにできない状態になってしまうことがあります。

システムダイアログに限らず、ダイアログの中にはサイズの変更ができないようにしているものがあります。自身が開発するアプリケーションでも、フォームの位置変更や、サイズの変更ができないようにすることができます。



図45
表示位置を設定したシステムダイアログ
  • Form1 の左端から 40 ピクセル右、上端から 80 ピクセル下の位置から表示

リスト29
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    ReplaceDialog1: TReplaceDialog;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private 宣言 }
    FDialogSetPos : Boolean;
    FDialogWindow : HWND;
    function DialogHook(var Message: TMessage): Boolean;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ShellAPI;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  Windowsシステムのダイアログのフックを開始する
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //ボタンクリックで表示するダイアログだけを操作対象とするためにTrueにしておく
  FDialogSetPos := True;
  //フック開始
  Application.HookMainWindow(DialogHook);
end;

//=============================================================================
//  フォームを閉じる時の処理
//  起動したダイアログを閉じる
//  Windowsシステムのダイアログのフックを解除する
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if FDialogWindow <> 0 then begin
    PostMessage(FDialogWindow, WM_CLOSE, 0, 0);
  end;
  Application.UnhookMainWindow(DialogHook);
end;

//=============================================================================
//  Windowsのシステム関係ダイアログを表示する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  //表示のフラグをFalseにしておく
  FDialogSetPos := False;
  FDialogWindow := 0;

  //[日付と時刻]のダイアログを表示
  StrText := 'timedate.cpl';
  ShellExecute(Handle, 'open', 'control.exe', PChar(StrText), nil, SW_SHOW);
end;

//-----------------------------------------------------------------------------
//  TApplication.HookMainWindowの処理関数
//  システム関係のダイアログは非表示で複数起動している場合がある
//  最初に見つけたZオーダーがトップのダイアログを処理したらフラグをTrueにする
//-----------------------------------------------------------------------------
function TForm1.DialogHook(var Message: TMessage): Boolean;
var
  DlgRect : TRect;
  DlgPosX : Integer;
  DlgPosY : Integer;
begin
  Result := False;

  if FDialogSetPos = False then begin
    FDialogWindow := FindWindow('#32770', nil);
    //ダイアログのタイトルが分かっている場合は、以下の方が確実
    FDialogWindow := FindWindow(nil, '日付と時刻');

    if FDialogWindow <> 0 then begin
      if Message.Msg = WM_ACTIVATEAPP then begin

        FDialogSetPos := True;
        GetWindowRect(FDialogWindow, DlgRect);

        DlgPosX := Self.Left + 40;
        DlgPosY := Self.Top  + 80;

        SetWindowPos(FDialogWindow,
                     0,
                     DlgPosX,
                     DlgPosY,
                     0,
                     0,
                     SWP_NOSIZE or SWP_NOZORDER);
      end;
    end;
  end;
end;

end.




25_ダイアログのカスタマイズ例  -  InputBox


ダイアログのカスタマイズの例です。PostMessage を使用しています。
位置やサイズの変更もできますが、ここではボタンのキャプションを変更します。
InputBox のクラス名は TForm ですが、外部から TForm のインスタンスは取得できません。そこで、メッセージ処理内で TForm のインスタンスを取得して操作しています。

InputBox や InputQuery ダイアログをカスタマイズする場合、ダイアログのソースコードを参考にして、自前でダイアログを作成してしまった方がいいかも知れません。



[備考]
TOenDialog 等の Windows のコモンダイアログをカプセル化したダイアログのカスタマイズは簡単ではありません。しかし、TForm から作成すれば、自由な仕様のダイアログが作成できます。また、テンプレートを利用することもできます。
TOpenDialog や TSaveDialog、フォルダの参照ダイアログのようなデザインのダイアログも、Delphi に付属のシェルコンポーネント等を使用して作成することができます。



図46
変更前
図47
変更後
  • [OK] ボタンが [決定] になっている

リスト30
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  protected
    procedure WMApp(var Message: TMessage); message WM_APP;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  InputBoxダイアログを表示
//  メッセージ処理なし
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  InputBox('テスト','テストです','');
end;

//=============================================================================
//  InputBoxダイアログを表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  PostMessage(Handle, WM_APP, 0, 0);
  InputBox('テスト','テストです','');
end;

//=============================================================================
//  メッセージ処理
//  ダイアログ表示直前にPosMessageでメッセージキューに書き込んでおく
//=============================================================================
procedure TForm1.WMApp(var Message: TMessage);
var
  i      : Integer;
  FrmDlg : TForm;
  hOKBtn : HWND;
  OKBtn  : TWinControl;
begin
  FrmDlg := nil;
  //ダイアログのフォームを探す
  for i := 0 to Screen.FormCount-1 do begin
    if Screen.Forms[i].ClassName = 'TForm' then begin
      Frmdlg := Screen.Forms[i];
      break;
    end;
  end;
  if FrmDlg = nil then exit;

  //[OK]ボタンのハンドルを取得して、ハンドルからボタンオブジェクトを取得
  hOKBtn := FindWindowEx(FrmDlg.Handle, 0, 'TButton', 'OK');
  OKBtn  := FindControl(hOKBtn);
  //キャプションを変更する
  TButton(OKBtn).Caption := '決定';
end;

end.




26_ダイアログのカスタマイズ例  -  MessageDlg


上のコードで操作した InputBox は Delphi で作られています。したがって、コントロールのオブジェクトを取得して操作できました。
Windows が提供している MessageDlg ダイアログは、そのようなことはできません。コントロールのウインドウハンドルを取得して操作することになります。その例です。
MessageDlg 関数で表示するダイアログは、サイズの変更はできません。

このサンプルでは PostMessage メッセージを使用して、FindWindow でダイアログのウィンドウを検出していますが、既にダイアログが表示されていると誤検出することがあります。タスクトレイにある設定用の非表示ダイアログにも影響されます。ローカルフックを使用した方が動作は確実です。



図48
変更前
図49
変更後
  • ボタンのキャプションを変更
  • 表示位置も変更

リスト31
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  protected
    procedure WMApp(var Message: TMessage); message WM_APP;
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  MessageDlgダイアログを表示
//  メッセージ処理なし
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';

  MessageDlg(StrText, mtInformation, [mbYes, mbCancel, mbAll], 0);
end;

//=============================================================================
//  MessageDlgダイアログを表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト';

  PostMessage(Handle, WM_APP, 0, 0);
  MessageDlg(StrText, mtInformation, [mbYes, mbCancel, mbAll], 0);
end;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassNameStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetClassName(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  EnumChildWindowsのコールバック関数
//  ここでは、ダイアログの子ウィンドウを列挙
//  子ウインドウのクラス名がButtonだったらキャプションの文字列を変える
//-----------------------------------------------------------------------------
function EnumChildWndTestProc(hWindow: hWnd; lPar: PInteger):
  Boolean; Stdcall;
var
  ClassName : String;
  aCaption  : String;
begin
  Result := True;

  ClassName := GetWindowClassNameStr(hWindow);
  if ClassName = 'Button' then begin
    lPar^ := lPar^ + 1;
    if lPar^ = 1 then begin
      aCaption := 'YES (&Y)';
      SendMessage(hWindow, WM_SETTEXT, 0, LPARAM(PChar(aCaption)));
    end else
    if lPar^ = 2 then begin
      aCaption := 'NO';
      SendMessage(hWindow, WM_SETTEXT, 0, LPARAM(PChar(aCaption)));
    end else
    if lPar^ = 3 then begin
      aCaption := '全部 (&A)';
      SendMessage(hWindow, WM_SETTEXT, 0, LPARAM(PChar(aCaption)));
    end;
  end;
end;

//=============================================================================
//  メッセージ処理
//  ダイアログ表示直前にPosMessageでメッセージキューに書き込んでおく
//=============================================================================
procedure TForm1.WMApp(var Message: TMessage);
var
  hWindow   : HWND;
  BtnNumber : Integer;
  DlgRect   : TRect;
  DlgWidth  : Integer;
  DlgPosX   : Integer;
  DlgPosY   : Integer;
begin
  hWindow := FindWindow('#32770', nil);
  //ウィンドウのタイトルが分かっているなら以下の方が確実
  hWindow := FindWindow(nil, '情報');

  if hWindow <> 0 then begin
    BtnNumber := 0;
    //ダイアログの子ウィンドウを列挙
    EnumChildWindows(hWindow, @EnumChildWndTestProc, LPARAM(@BtnNumber));

    GetWindowRect(hWindow, DlgRect);
    //ダイアログの幅と高さを取得
    DlgWidth  := DlgRect.Right  - DlgRect.Left;

    //Form1の右の中央。Form1の上端から指定ピクセル下から表示
    DlgPosX := Form1.Left + (Form1.Width  - DlgWidth)  div 2;
    DlgPosY := Form1.Top  + 100;
    SetWindowPos(hWindow,
                 0,
                 DlgPosX,
                 DlgPosY,
                 0,
                 0,
                 SWP_NOSIZE or SWP_NOZORDER);
  end;
end;

end.




27_ダイアログのカスタマイズ例 - フォントの変更


ダイアログのフォントを変更する例です。
Windows のダイアログボックスである MessageBox であれば、ダイアログのコールバック関数内でフォントの色と背景色が変更できます。
次のコードは、コールバック関数を定義するために、ダイアログのフックで、ダイアログボックスが初期化される WM_INITDIALOG メッセージを受信したら、そのダイアログボックスのプロシージャーを設定しています。
フォントの色は、文字列を描画するコントロールのデバイスコンテキストに対して、SetTextColor 関数で設定します。



図50
文字の色を変更した結果

リスト32
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  MsgHook    : HHOOK;
  OldDlgProc : Pointer;

//-----------------------------------------------------------------------------
//  ウィンドウのクラス名を取得する関数
//-----------------------------------------------------------------------------
function GetWindowClassNameStr(hWindow: HWND): String;
var
  Buffer : array[0..MAX_PATH-1] of Char;
  Len    : Integer;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetClassName(hWindow, Buffer, Length(Buffer));
  if Len > 0 then Result := Buffer else Result := '';
end;

//-----------------------------------------------------------------------------
//  ダイアログのコールバック関数
//-----------------------------------------------------------------------------
function DialogProc(hdlg: HWND; uiMsg: UINT; wParam: 
  WPARAM; lParam:LPARAM): UINT; stdcall;
var
  LhdcStatic : HDC;
Begin
  if uiMsg = WM_CTLCOLORSTATIC then begin
    //クラス名がStaticのデバイスコンテキストを取得
    LhdcStatic := wParam;
    SetTextColor(LhdcStatic, clGreen);
    Result := GetStockObject(WHITE_BRUSH);
  end else
  if uiMsg = WM_CTLCOLORDLG then begin
    Result := GetStockObject(WHITE_BRUSH);
  end else begin
    Result := CallWindowProc(OldDlgProc, hdlg, uiMsg, wParam, lParam);
  end;
end;

//-----------------------------------------------------------------------------
//  WH_CALLWNDPROCフックのコールバック関数
//  ここではMsg : PMsgではなくMsg: PCWPStructにして、wParamの値を受け取る
//
//  Windowsのダイアログのクラス名は#32770
//-----------------------------------------------------------------------------
function GetMsgProc(Code: Integer; WPar: WPARAM; LPar: LPARAM): LRESULT; stdcall;
var
  LpMsgStruct : PCWPStruct;
  LhWindow    : HWND;
  LClassName  : String;
begin
  if (code < 0) then begin
    Result := CallNextHookEx(MsgHook, Code, WPar, LPar);
    exit;
  end;

  if (code = HC_ACTION) then begin
    //ウィンドウハンドルを取得
    LpMsgStruct := PCWPStruct(LPar);
    LhWindow := LpMsgStruct^.hwnd;

    LClassName := GetWindowClassNameStr(LhWindow);

    if LClassName = '#32770' then begin
      //ダイアログのハンドルを取得
      LhWindow := LpMsgStruct^.hwnd;

      if LpMsgStruct^.message = WM_INITDIALOG then begin
        //ダイアログボックスのプロシージャ(コールバック関数)を設定
        OldDlgProc := Pointer(GetWindowLong(LhWindow, DWL_DLGPROC));
        SetWindowLong(LhWindow, DWL_DLGPROC, Integer(@DialogProc));
      end;
    end;
  end;

  Result := CallNextHookEx(MsgHook, Code, WPar, LPar);
end;

//=============================================================================
//  ローカルフック開始
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  MsgHook := SetWindowsHookEx(WH_CALLWNDPROC, GetMsgProc, 0, GetCurrentThreadID);
end;

//=============================================================================
//  フック解除
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnHookWindowsHookEx(MsgHook);
end;

//=============================================================================
//  ダイアログを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログのフォント色変更';
  MessageBox(Handle, PChar(StrText), '情報', MB_ICONINFORMATION);
end;

end.


フォントのサイズは、コールバック関数内で、デバイスコンテキストに対しても操作できますが、以下のようにダイアログボックスが初期化される時に、WM_SETFONT メッセージで設定できます。
LOGFONT 構造体の各メンバーに設定する内容をセットします。
フォントのサイズを変更した場合は、そのテキスト (文字列) を表示するコントロールのサイズも変更しないと、下図の結果のように、文字列が描画領域でクリップされます。実際には、描画コントロールだけでなく、ダイアログボックスと、ダイアログボックスに配置されているコントロール類の再配置も必要となります。



図51
フォントサイズを変更した結果
  • 表示領域に収まらない部分はクリッピングされている

リスト33
//-----------------------------------------------------------------------------
//  WH_CALLWNDPROCフックのコールバック関数
//  ここではMsg : PMsgではなくMsg: PCWPStructにして、wParamの値を受け取る
//
//  Windowsのダイアログのクラス名は#32770
//-----------------------------------------------------------------------------
function GetMsgProc(Code: Integer; WPar: WPARAM; LPar: LPARAM): LRESULT; stdcall;
var
  LpMsgStruct : PCWPStruct;
  LhWindow    : HWND;
  LClassName  : String;
  LhDlgItem   : HWND;
  LLogFont    : TLogFont;
  LhFontOld   : HFONT;
begin
  if (code < 0) then begin
    Result := CallNextHookEx(MsgHook, Code, WPar, LPar);
    exit;
  end;

  if (code = HC_ACTION) then begin
    //ウィンドウハンドルを取得
    LpMsgStruct := PCWPStruct(LPar);
    LhWindow := LpMsgStruct^.hwnd;

    LClassName := GetWindowClassNameStr(LhWindow);

    if LClassName = '#32770' then begin
      //ダイアログのハンドルを取得
      LhWindow := LpMsgStruct^.hwnd;

      if LpMsgStruct^.message = WM_INITDIALOG then begin
        //ダイアログボックスのプロシージャ(コールバック関数)を設定
        OldDlgProc := Pointer(GetWindowLong(LhWindow, DWL_DLGPROC));
        SetWindowLong(LhWindow, DWL_DLGPROC, Integer(@DialogProc));


        LhDlgItem := GetDlgItem(LhWindow, $FFFF);
        //テキストのフォントハンドルを取得
        LhFontOld := SendMessage(LhDlgItem, WM_GETFONT, 0, 0);
        //そのテキストのTLogFontの値を取得
        GetObject(LhFontOld, SizeOf(TLogFont), @LLogFont);

        //論理フォント構造体の一部を変更
        LLogFont.lfHeight   := 25;
        LLogFont.lfFaceName := 'MS Pゴシック';
        //新しいフォントハンドルに設定したTLogFontを設定
        hFontNew := CreateFontIndirect(LLogFont);
        //フォントを設定
        SendMessage(LhDlgItem, WM_SETFONT, hFontNew, MakeLParam(1,0));
      end;
    end;
  end;

  Result := CallNextHookEx(MsgHook, Code, WPar, LPar);
end;

//=============================================================================
//  ローカルフック開始
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  MsgHook := SetWindowsHookEx(WH_CALLWNDPROC, GetMsgProc, 0, GetCurrentThreadID);
end;

//=============================================================================
//  フック解除
//  hFontNewはグローバル変数(HFONT)
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeleteObject(hFontNew);
  UnHookWindowsHookEx(MsgHook);
end;




28_メッセージのテキストの複数行表示


ダイアログのメッセージの文字列を複数行にする場合は、改行文字を使用します。ただし、改行文字を無視するダイアログもあります。
TaskMessageDlg 関数では、タイトルとして、別のテキストを表示することができます。



図52
メッセージの複数行表示

リスト34
procedure TForm1.Button1Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := 'ダイアログ関係関数テスト' + sLineBreak + '2 行目のテキスト';
  Dialogs.ShowMessage(StrText);
end;




29_アニメーションを表示するダイアログ


Windows XP までは、SHFileOperation 関数を使用してファイルのコピー、削除等を実行すると、進捗状況を表示するダイアログに、それらの操作に対応するアニメーションが表示されました。
Windows Vista 以降では、このアニメーションは表示されません。



図53
SHFileOperation によるファイル操作
  • Windows XP の場合
  • ファイル操作に応じたアニメーションが表示される
  • 下のコードはフォルダのコピー操作となっている
図54
SHFileOperation によるファイル操作
  • Windows 7 の場合
  • アニメーションの表示はない

リスト35
implementation

uses
  ShellAPI;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  LSourceDir : String;
  LDestDir   : String;
  LSHStruct  : TSHFileOpStruct;
begin
  LSourceDir := 'D:\000\100';
  LDestDir   := 'D:\000\200';

  //構造体の初期設定
  LSHStruct.wnd    := Handle;
  LSHStruct.wFunc  := FO_COPY;
  LSHStruct.pFrom  := PChar(LSourceDir);
  LSHStruct.pTo    := PChar(LDestDir);
  LSHStruct.fFlags := FOF_MULTIDESTFILES;

  //実行する
  SHFileOperation(LSHStruct);
end;


Windows Vista 以降でアニメーションを表示するには、自前でダイアログを作成します。
そのダイアログで TAnimate コンポーネントを使用して、表示するアニメーションの AVI ファイルを指定します。
TShellResources コンポーネントを使用すると、あらかじめ用意されている、ファイルのコピーや削除などの AVI ファイルを指定して表示することができます。下図はその例です。
TShellResources コンポは、Delphi 2007 以降に実装されています。



図55
設計時画面
  • 破線が TAnimate コンポーネント
  • TShellResources が必要
  • uses に ShellAnimations を追加するだけでもよい
図56
[ファイルのコピー] のアニメーション
  • CommonAVI の値は オブジェクトインスペクタからも設定可能
図57
[ゴミ箱を空にする] アニメーション

リスト36
procedure TForm1.Button1Click(Sender: TObject);
begin
  Animate1.Active    := False;
  Animate1.AutoSize  := True;
  Animate1.CommonAVI := aviCopyFiles;

  Animate1.Active := True;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LHeight : Integer;
  LWidth  : Integer;
begin
  Animate1.Active     := False;
  Animate1.CommonAVI  := aviEmptyRecycle;
  LHeight := Animate1.FrameHeight;
  LWidth  := Animate1.FrameWidth;

  Animate1.AutoSize := False;
  Animate1.Width    := LWidth;
  Animate1.Height   := LHeight;
  Animate1.Active   := True;
end;




30_ダイアログに使用するアイコン画像の取得


カスタマイズしたダイアログを TForm から自作する場合、ダイアログに使用する画像は、システムのリソースや DLL から取得することができます。
以下のリンクの記事を参考にしてください。






31_コントロールのクラス名や ID 値等を調べる方法


ダイアログに限らず、対象のウインドウにどのようなコントロールが配置されているかを調べるには、例えば、サンプルプログラム集の [645_ウィンドウの階層のツリー表示] に掲載しているサンプルプログラムで調べることができます。
このサンプルで知ることができるのは、ウィンドウハンドルを持つコントロールだけです。



[備考]
コントロールの ID 値は、ウィンドウハンドルと同じく、生成される度に新しい値となります。
ID 値を固定した値にするには、SetWindowLong 関数を使用して、実行時に設定します。
Windows のコモンダイアログの多くは、ダイアログに配置しているコントロールの ID 値が一定の値になっています。