Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2009年12月27日] [更新 2015年09月24日] Delphi サンプルプログラム集
   
266_位置とサイズを指定してアプリを起動
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
266_App_CreateOpen.zip [4,297 KB] 2015年09月24日版 (EXE 同梱)



  • 2009年12月30日
  • クラス名を使用しないサンプルののコードを修正
  • 2013年09月15日
  • クラス名とウィンドウハンドルを使用する両方のサンプルを掲載していたが,クラス名を使用するのは WH_CBT フック による方法だけにした.
  • 選択用のラジオグループの使用をやめ,全てボタンとした.
  • アプリケーションマニフェストの使用を止めた (Windows 7 ではなくても動作する).
  • Windows フォトビューア関係の記事を追加




位置とサイズを指定してアプリを起動


他のアプリケーションを起動する際,その位置とサイズを指定して起動するサンプルです.
多くのアプリケーションは,終了時に位置とサイズを記憶して,次回起動時にその位置とサイズで表示するようになっています.したがって,正確には,起動したアプリケーションの位置とサイズを設定することになりますが,全てのアプリケーションで使用可能な汎用的な方法はありません.

最初の 2 つのサンプルでは CreateProcess 関数と ShellExecuteEx 関数を使用しています.アプリケーションのウィンドウがアクティブになったのを検出して,そのウィンドウハンドルの値を使用して位置とサイズを設定します.アプリケーションが生成するウィンドウは 1 つとは限りません.検出したウィンドウが必ずしもそのアプリケーションの位置とサイズを決定するとは限りません.
そのため,アプリケーションによっては細かい修正が必要な場合があります.

また,アプリケーションによっては,他から位置やサイズの設定を禁止するようなコーディングをしている場合もあります.その処理内容にもよりますが,変更できないこともあります.
重要なことは,ある動作に関して,「何々したい」と人もいれば,同じことを「させたくない」という人もいることを忘れないことです.

3 つ目は WH_CBTフックを使用する方法です.
このフックは,ウィンドウがアクティブになる前のメッセードを処理することができます.その時に位置とサイズを設定します.この方法も上記同様,そのアクティブになろうとしているウィンドウが必ずしもそのアプリケーションの位置とサイズを決定しているとは限りません.
WH_CBTフックでは,起動するアプリケーションを区別するために,対象アプリケーションのクラス名を使用します.

本ページで使用している,EnumTopWindow (プロセス ID からウィンドウハンドルを調べるためのコールバック関数),SetPositionSize,ExecAndGetWindow,RunExeFile 関数は,関数名が同じであれば,全て同じコードです.
当サイトのサンプルプログラムには,同じ関数名でも,目的によって処理コードが違うのがあります.注意してください.



[備考 1]
当サイトには,起動したアプリが起動するまで待つ,という記述のサンプルがありますが,正確には「プロセスが生成されるまで待つ」ということになります.一方,本ページのサンプルはアプリのウィンドウハンドルが取得できるまで待ちますから,アプリの起動完了まで待つということになります.
対象のアプリのウィンドウハンドルが取得できなければ,一般的にはそのアプリに対する操作はできません (操作内容によっては可能なものもあります).


[備考 2]
エクスプローラは,ウィンドウズのシェルの機能が提供しています.そのため,本ページのサンプルコードでは,起動したエクスプローラの制御や情報の取得ができないことがあります.以下のサンプルを参考にしてください.


[備考 3]
他のアプリのウインドウの位置やサイズを制御したい要望がある一方で,他のアプリからは制御させたくないという要望もあります.アプリケーションの動作仕様は,ある要望がある一方で,逆の動作仕様の要求もあることを認識しておく必要があると思われます.


[備考 4]
起動中のウインドウの位置やサイズを変更するには,SetWindowPos 関数を使用します.





01_位置とサイズを指定してアプリを起動 - CreateProcess 使用


CareteProcess 関数を使用して,指定したアプリケーションを起動します.
CreateProcess 関数では TStartupInfo レコード型に,起動時のアプリのウィンドウの位置とサイズを指定するパラメータがありますが,これは,対象となるアプリケーションが,それらの値を用いる様に作成されていなければ有効とはなりません.

このサンプルでは該当アプリを最小化の状態で起動し,ウィンドウハンドルが取得できたらそのハンドルを操作して位置とサイズを指定して表示するようにしています.
最小化の状態から位置とサイズを直接変更してしまうと,一瞬,元の位置とサイズで表示されてしまうためチラつくことになります.そこで,最小化の状態のままで,SetWindowPlacement 関数で位置をサイズを設定してから実際に表示するようにしています.

CreateProcess 関数では,原則として,起動するアプリケーションの EXE 名をフルパスで指定しなければなりません.そこで,[480_拡張子関連付けの実行ファイル名] のコードを利用して,起動するアプリケーションの EXE のフルパスを取得しています.



図1
設計時画面

ウィンドウハンドルの取得のコードでは,ループ処理をしています.Microsoft Word (マイクロソフトワード) のように,起動時の動作が重たいアプリケーションは,プロセスが生成されてから,実際にウィンドウが生成されるまで時間がかかります.そのための対策です.特にコンピュータを起動してから初めてワードを起動する時に時間がかかります.

また,ワードを終了しても,終了直後は,下図のような一時ファイル (テンポラリファイル) がディスク上に残っています.このファイルが存在すると起動に失敗します.
メモ帳等の軽いアプリでは,ウィンドウハンドル取得用の処理ループを 1 回しか通過しません.



図2
ワードを起動すると出来るテンポラリファイル

以下に,筆者の環境での実行結果の図を示しておきます.
これらの画像は,各々のアプリを起動した後,本サンプルプログラムのフォームをクリックして,起動したアプリを含めた矩形範囲をキャプチャーしたものです.


図3
メモ帳を起動
  • 表示ファイルの指定なしで起動
  • メモ帳はパスが通っているのでフルパスでの指定は必要ない
図4
ワードの文書をワードで表示
  • 最小化状態で起動すると正常に起動しないので,SW_SHOW で起動している
  • 起動したのは Word 2010
図5
エクセルのブックをエクセルで表示
  • 起動したのは Excel 2010
図6
IE でウェブページ表示
  • 起動したのは Internet Explorer 9
図7
パワーポイントの ppt を表示
  • 最小化で表示すると,正常に表示できないので SW_SHOW で表示
  • 起動オプションに /S を指定するとスライドショーで表示できる.この時は最小化で起動可能
  • 起動したのは PowerPoint 2010
図8
パワーポイントの pps を表示
  • 起動したのは PowerPoint 2010
図9
任意の EXE ファイルを起動
  • 添付の TesForm.exe を起動した例
  • 最小化で表示すると,正常に表示できないので SW_SHOW で表示

リスト1
CreateProcess 関数でアプリを起動して,位置とサイズを設定
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button3: TButton;
    Button4: TButton;
    Button2: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
    procedure SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ShLwApi;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  以下の条件の時に終了とし,引数のlpにその時のウインドウハンドルを返す
//  (1) 引数のlpとウィンドウハンドルが同じ
//  (2) オーナーを持たないウィンドウ
//  (3) 可視状態のウィンドウである
//-----------------------------------------------------------------------------
function EnumTopWindow(hTopWnd: HWND; lp: LPARAM): BOOL; stdcall;
var
  p         : LPDWORD;
  ProcessId : DWORD;
begin
  Result := True;

  GetWindowThreadProcessId(hTopWnd, @ProcessId);
  p := LPDWORD(lp);
  if (ProcessId = p^) and (GetWindow(hTopWnd, GW_OWNER) = 0)
                      and IsWindowVisible(hTopWnd) then begin
    p^     := hTopWnd;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  CreaateProcessで指定アプリを起動
//
//  ExeAppStr : コマンドライン文字列
//  vShow     : SW_SHOW等の表示フラグ
//
//  アプリの起動完了まで待つ
//  起動完了の判定はウインドウハンドルで行う
//  ウィンドウハンドルはEnumWindowsのコールバック関数で取得する
//-----------------------------------------------------------------------------
function ExecAndGetWindow(ExeAppStr: String; vShow: WORD):HWND;
var
  SI         : TStartupInfo;
  PI         : TProcessInformation;
  ret        : DWORD;
  dw         : DWORD;
  dwIDOrigin : DWORD;
  WaitCount  : integer;
begin
  Result     := 0;
  dw         := 0;
  dwIDOrigin := 0;

  FillChar(SI,SizeOf(SI), #0);
  SI.cb          := SizeOf(TStartupInfo);
  SI.dwFlags     := STARTF_USESHOWWINDOW;
  SI.wShowWindow := vShow;
  SetLength(ExeAppStr, Length(ExeAppStr));

  //参照カウンタ対策
  UniqueString(ExeAppStr);
  if not CreateProcess(nil,
                       PChar(ExeAppStr),
                       nil,
                       nil,
                       False,
                       0,
                       nil,
                       nil,
                       SI,
                       PI) then begin
    exit;
  end else begin
    repeat
      ret := WaitForInputIdle(PI.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

    //EnumWindowsにプロセスIDを渡してウィンドウを検索
    //EnumWindowsの第2引数にウィンドウハンドルが戻ってくる
    //アプリの起動完了までは時間がかかることがあるのでループ
    if ret = 0 then begin
      WaitCount := 0;
      dwIDOrigin := PI.dwProcessId;
      repeat
        Sleep(100);
        dw := dwIDOrigin;
        EnumWindows(@EnumTopWindow, LPARAM(@dw));
        if dw <> dwIDOrigin then begin
          Result := dw;
          break;
        end;
        Inc(WaitCount);
      until IsWindow(dw) or (WaitCount > 100);
    end;

    //プロセスの起動に失敗したら強制終了させる
    if (ret <> 0) or (dw = dwIDOrigin) then begin
      Result := 0;
      TerminateProcess(PI.hProcess, 0);
    end;
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

//-----------------------------------------------------------------------------
//  指定した拡張子に関連づけた実行ファイルのフルパス名を取得する関数
//  取得するのは拡張子の関連付けがレジストリに登録されているものの最初のもの
//
//  AssocQueryStringの使用にはusesにShLwApiが必要
//  OleCheckやWin32Checkを入れておくと例外を検出してくれる
//  OleCheckやWin32Checkの使用にはuuseにComObjが必要
//-----------------------------------------------------------------------------
function GetRelatedFullExePath(ExtName: String;
  DoubleQuote: Boolean=True; CmdParams: Boolean=False):string;
var
  Buf   : array [0..MAX_PAtH-1] of Char;
  dwOut : DWORD;
begin
  Result := '';

  FillChar(Buf, SizeOf(Buf), #0);
  dwOut := MAX_PATH;

  try
    //第2引数をASSOCSTR_EXECUTABLEにすれば"を含まないパス文字列となる
    if DoubleQuote then begin
      OleCheck(AssocQueryString(ASSOCF_NOTRUNCATE,
                                ASSOCSTR_COMMAND,
                                PChar(ExtName),
                                'open',
                                Buf,
                                @dwOut));
    end else begin
      OleCheck(AssocQueryString(ASSOCF_NOTRUNCATE,
                                ASSOCSTR_EXECUTABLE,
                                PChar(ExtName),
                                'open',
                                Buf,
                                @dwOut));
    end;
    Result := Buf;

    if Length(Result) = 0 then begin
      Result := '';
    end else begin
      if not CmdParams then begin
        //"(ダブルコーテーション)で囲まれている場合
        if Pos('" ', Result) > 0 then begin
          Result := Copy(Result, 1, Pos('" ', Result) + 1);
        end;
      end;
    end;
  except
  end;
end;

//=============================================================================
//  メモ帳を起動する
//  表示するファイルの指定なし
//  メモ帳はパスが通っているのでフルパスは不要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  //メモ帳の起動
  AppStr := 'Notepad.exe';

  AppWnd := ExecAndGetWindow(AppStr, SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  ワードの文書をワードで開く
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  AppStr := GetRelatedFullExePath('.doc', True, False);
  AppStr := Appstr + ' /q ' + ExpandFileName('..\テスト文書.doc');

  AppWnd := ExecAndGetWindow(AppStr, SW_SHOW);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  エクセルのブックをエクセルで開く
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  AppStr := GetRelatedFullExePath('.xls', True, False);
  AppStr := Appstr + ' ' + ExpandFileName('..\テストブック.xls');

  AppWnd := ExecAndGetWindow(AppStr, SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  ウェブページをIEで開く
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  AppStr := '"C:\Program Files\Internet Explorer\IEXPLORE.EXE"';
  AppStr := AppStr + ' ' + 'http://mrxray.on.coocan.jp/';

  AppWnd := ExecAndGetWindow(AppStr, SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  パワーポイントのpptファイルを表示
//  起動オプションに/Sを指定するとスライドショーで表示する
//  /Sを指定した場合はSW_MINIMIZEにできる
//  /Sを指定しない場合はSW_SHOWにしないと最小化のままで起動する
//
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  AppStr := GetRelatedFullExePath('.ppt', True, False);
  AppStr := Appstr + ' ' + ExpandFileName('..\プロジェクトの概要.ppt');

  AppWnd := ExecAndGetWindow(AppStr, SW_SHOW);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  パワーポイントのppsファイルを表示
//  起動オプションに/Sを指定するとスライドショーで表示する
//  /Sを指定した場合はSW_MINIMIZEにできる
//  /Sを指定しない場合はSW_SHOWにしないと最小化のままで起動する
//
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button6Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  AppStr := GetRelatedFullExePath('.pps', True, False);
  AppStr := Appstr + ' /S ' + ExpandFileName('..\プロジェクトの概要.pps');

  AppWnd := ExecAndGetWindow(AppStr, SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  任意のEXEを起動
//  指定した位置とサイズで正常に表示するかは起動するEXEによる
//  添付のTesForm.exeの場合,SW_MINIMIZEだと正常起動しない
//=============================================================================
procedure TForm1.Button7Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  if InitDir = '' then begin
    OpenDialog1.InitialDir := ExpandFileName('..\Test App');
  end;
  OpenDialog1.Filter := '実行形式ファイル (*.exe)|*.exe';

  if OpenDialog1.Execute then begin
    InitDir := ExtractFileDir(OpenDialog1.FileName);

    AppStr := OpenDialog1.FileName;
    AppWnd := ExecAndGetWindow(AppStr, SW_SHOW);
    SetPositionSize(AppWnd, 0, 0, 800, 600);
  end;
end;

//=============================================================================
//  ウィンドウの位置とサイズを指定して表示
//
//  本サンプルでは,ウィンドウを最小化して起動しているのがある
//  最小化から表示する際,そのまま表示して位置とサイズを設定すると,現在の位置
//  とサイズで表示されてしまう
//  そこで,SetWindowPlacementで内部的に位置とサイズを設定してから表示する
//  TWindowPlacement.rcNormalPositionは位置だけの指定も可能
//=============================================================================
procedure TForm1.SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
var
  ARect       : TRect;
  OriginPlace : TWindowPlacement;
  OpenPlace   : TWindowPlacement;
begin
  if AWnd > 0 then begin
    //表示位置とサイズの値
    ARect := Rect(X, Y, XSize, YSize);

    //元の表示状態の構造体の値を取得して新しい構造体にコピー
    OriginPlace.length := SizeOf(TWindowPlacement);
    OpenPlace.length   := SizeOf(TWindowPlacement);
    GetWindowPlacement(AWnd, OriginPlace);
    OpenPlace := OriginPlace;

    //新しい構造体の値(ここでは表示位置とサイズ)を設定
    OpenPlace.rcNormalPosition := ARect;
    OpenPlace.showCmd          := SW_SHOWNORMAL;
    SetWindowPlacement(AWnd, Addr(OpenPlace));
    //下のコードで再描画が行われ,指定した位置とサイズで表示される
    SetForegroundWindow(AWnd);
  end else begin
    ShowMessage('アプリケーションの起動に失敗しました. ');
  end;
end;

end.


ウィンドウハンドルの検出ですが,下のコードのようにすることもできます.
CreateProcess 関数,あるいは ShellExecuteEx 関数が成功すれば,間違いなくアプリケーションは起動していることになります.しかもたった今起動したばかりです.
そこで,起動アプリケーションのクラス名でウィンドウの生成を検出することができます.これなら起動に時間のかかるアプリケーションでもウィンドウハンドルは確実に検出できます.

サンプルプログラム集には,この方法でウィンドウハンドルを検出しているサンプルがあります.
この方法は,(ShellExecuteEx 関数ではなく) ShellExecute 関数でアプリケーションを起動する場合でも利用できます.



リスト2
起動したアプリの起動完了を待つ別の方法

//=============================================================================
//  ワードの文書をワードで開く
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AppStr : String;
  AppWnd : HWND;
begin
  AppStr := GetRelatedFullExePath('.doc', True, False);
  AppStr := Appstr + ' /q ' + ExpandFileName('..\テスト文書.doc');

  AppWnd := 0;
  if ExecAndGetWindow(AppStr, SW_SHOW) > 0 then begin
    while True do begin
      AppWnd := FindWindow('OpusApp', nil);
      if (AppWnd > 0) and IsWindowVisible(AppWnd) then break;
      Application.ProcessMessages;
      Sleep(100);
    end;
  end;
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;




02_位置とサイズを指定してアプリを起動 - ShellExecuteEx 使用


ShellExecuteEx 関数を使用した例です.
ShellExecuteEx 関数は,拡張子の関連付けでアプリケーションを起動し,指定したファイルを表示することができます.アプリケーションのフルパスを指定すれば (パスが通っていればディレクトリ部分は不要) そのアプリケーションだけの起動もできます.

このサンプルも,該当アプリを最小化の状態で起動し,ウィンドウハンドルが取得できたらそのハンドルを操作して位置とサイズを指定して表示するようにしています.

最小化の状態から位置とサイズを直接変更してしまうと,一瞬,元の位置とサイズで表示されてしまうためチラつくことになります.そこで,最小化の状態のままで,SetWindowPlacement 関数で位置をサイズを設定してから実際に表示するようにしています.



図10
設計時画面
  • 以下のファイルを開いて表示
  • メモ帳
  • ワードの文書
  • エクセルのブック
  • IE で指定のウェブページ
  • パワーポイント (PowerPoint) の ppt ファイル
  • パワーポイント (PowerPoint) の pps ファイル
  • 任意の EXE を起動

リスト3
ShellExecuteEx 関数を使用して,アプリケーションを起動またはファイルを表示
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
    procedure SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  以下の条件の時に終了とし,引数のlpにその時のウインドウハンドルを返す
//  (1) 引数のlpとウィンドウハンドルが同じ
//  (2) オーナーを持たないウィンドウ
//  (3) 可視状態のウィンドウである
//-----------------------------------------------------------------------------
function EnumTopWindow(hTopWnd: HWND; lp: LPARAM): BOOL; stdcall;
var
  p         : LPDWORD;
  ProcessId : DWORD;
begin
  Result := True;

  GetWindowThreadProcessId(hTopWnd, @ProcessId);
  p := LPDWORD(lp);
  if (ProcessId = p^) and (GetWindow(hTopWnd, GW_OWNER) = 0)
                      and IsWindowVisible(hTopWnd) then begin
    p^     := hTopWnd;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  ShellExecuteExでファイル表示またはアプリを起動
//  戻り値は対象のウィンドウハンドル.起動に失敗すると0
//
//  AFile       : 起動プログラムのフルパスまたはファイルのフルパス
//  AParameters : コマンドラインオプション.表示するファイル等
//  Verb        : Open,Show,Print等の動作内容
//  vShow       : SW_SHOW等の表示フラグ
//
//  Windows Vistaでの注意
//  Windows Vistaではファイルを開く際に管理者権限が必要なことがある
//-----------------------------------------------------------------------------
function RunExeFile(hWnd: HWND; AFile: String; AParameters: String;
   Verb: String; vShow: Integer): Cardinal;
var
  AInfo      : TShellExecuteInfo;
  hProcess   : THandle;
  ret        : DWORD;
  dwIDOrigin : DWORD;
  dw         : DWORD;
  WaitCount  : integer;
begin
  Result     := 0;
  dw         := 0;
  dwIDOrigin := 0;

  FillChar(AInfo, SizeOf(AInfo), 0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := hWnd;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
  AInfo.lpVerb := PChar(Verb);

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpParameters := PChar(AParameters);
  AInfo.nShow        := vShow;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

    //EnumWindowsにプロセスIDを渡してウィンドウを検索
    //EnumWindowsの第2引数にウィンドウハンドルが戻ってくる
    //ウィンドウの起動完了までは時間がかかるのでループ
    if ret = 0 then begin
      WaitCount  := 0;
      dwIDOrigin := GetProcessId(AInfo.hProcess);
      repeat
        Sleep(100);
        dw := dwIDOrigin;
        EnumWindows(@EnumTopWindow, LPARAM(@dw));
        if dw <> dwIDOrigin then begin
          Result := dw;
          break;
        end;
        Inc(WaitCount);
      until IsWindow(dw) or (WaitCount > 100);
    end;

    //プロセスの起動に失敗したらプロセスを閉じる
    if (ret <> 0) or (dw = dwIDOrigin) then begin
      hProcess := AInfo.hProcess;
      if hProcess > 0 then begin
        TerminateProcess(hProcess, 0);
        CloseHandle(hProcess);
      end;
    end;
  end;
end;

//=============================================================================
//  メモ帳を起動する
//  表示するファイルの指定なし
//  メモ帳はパスが通っているのでフルパスは不要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  AppStr := 'Notepad.exe';
  AppCmd := '';

  AppWnd := RunExeFile(Handle, AppStr, AppCmd, '', SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  ワードでワードの文書を開く
//  ワードの場合,明示的にWINWORD.EXEを指定すれば指定位置とサイズで表示する
//
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  AppStr := 'WINWORD.EXE';
  AppCmd := '/q ' + ExpandFileName('..\テスト文書.doc');

  AppWnd := RunExeFile(0, AppStr, AppCmd, '', SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  エクセルのブックをで開く
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  AppStr := ExpandFileName('..\テストブック.xls');
  AppCmd := '';

  AppWnd := RunExeFile(0, AppStr, AppCmd, '', SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  ウェブページをIEで開く
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  AppStr := 'IEXPLORE.EXE';
  AppCmd := 'http://mrxray.on.coocan.jp/';

  AppWnd := RunExeFile(0, AppStr, AppCmd, 'Open', SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  パワーポイントのpptファイルをスライドショーで表示
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  AppStr := ExpandFileName('..\プロジェクトの概要.ppt');
  AppCmd := '';

  AppWnd := RunExeFile(0, AppStr, AppCmd, 'Show', SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  パワーポイントのppsファイルを表示
//=============================================================================
procedure TForm1.Button6Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  AppStr := ExpandFileName('..\プロジェクトの概要.pps');
  AppCmd := '';

  AppWnd := RunExeFile(0, AppStr, AppCmd, '', SW_MINIMIZE);
  SetPositionSize(AppWnd, 0, 0, 800, 600);
end;

//=============================================================================
//  任意のEXEを起動
//  添付のTesForm.exeの場合,SW_MINIMIZEだと正常起動しない
//=============================================================================
procedure TForm1.Button7Click(Sender: TObject);
var
  AppStr : String;
  AppCmd : String;
  AppWnd : HWND;
begin
  if InitDir = '' then begin
    OpenDialog1.InitialDir := ExpandFileName('..\Test App');
  end;
  OpenDialog1.Filter := '実行形式ファイル (*.exe)|*.exe';

  if OpenDialog1.Execute then begin
    InitDir := ExtractFileDir(OpenDialog1.FileName);

    AppStr := OpenDialog1.FileName;
    AppCmd := '';

    AppWnd := RunExeFile(Handle, AppStr, AppCmd, '', SW_SHOW);
    SetPositionSize(AppWnd, 0, 0, 800, 600);
  end;
end;

//=============================================================================
//  ウィンドウの位置とサイズを指定して表示
//
//  本サンプルでは,ウィンドウを最小化して起動しているのがある
//  最小化から表示する際,そのまま表示して位置とサイズを設定すると,現在の位置
//  とサイズで表示されてしまう
//  そこで,SetWindowPlacementで内部的に位置とサイズを設定してから表示する
//  TWindowPlacement.rcNormalPositionは位置だけの指定も可能
//=============================================================================
procedure TForm1.SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
var
  ARect       : TRect;
  OriginPlace : TWindowPlacement;
  OpenPlace   : TWindowPlacement;
begin
  if AWnd > 0 then begin
    //表示位置とサイズの値
    ARect := Rect(X, Y, XSize, YSize);

    //元の表示状態の構造体の値を取得して新しい構造体にコピー
    OriginPlace.length := SizeOf(TWindowPlacement);
    OpenPlace.length   := SizeOf(TWindowPlacement);
    GetWindowPlacement(AWnd, OriginPlace);
    OpenPlace := OriginPlace;

    //新しい構造体の値(ここでは表示位置とサイズ)を設定
    OpenPlace.rcNormalPosition := ARect;
    OpenPlace.showCmd := SW_SHOWNORMAL;
    SetWindowPlacement(AWnd, Addr(OpenPlace));
    //下のコードで再描画が行われ,指定した位置とサイズで表示される
    SetForegroundWindow(AWnd);
  end else begin
    ShowMessage('アプリケーションの起動に失敗しました. ');
  end;
end;

end.




03_位置とサイズを指定してアプリを起動 - WH_CBT フック使用


グローバルフック WH_CBT を使用する例です.このフックを使用すると,ウィンドウが Active になる前に位置とサイズを設定できます.
前の 2 つのサンプルは,アプリが起動してから位置とサイズを設定するものでしたが,この WH_CBT フックを使用する方法は,アプリが起動する時に位置とサイズを設定しますから,「位置とサイズを設定してアプリを起動」により近い動作となります.

WH_CBT フックは DLL 内で処理しないと機能しません.そこで,本サンプル専用の DLL を作成して利用しています.DLL 内でアクティブになるウィンドウを判定するため,起動するアプリケーションのクラス名を使用します.フックを開始する時に,位置とサイズと,このクラス名を DLL 側に渡します.

このサンプルは,前の 2 つのサンプルで,最小化の状態では正常に起動できなかったパワーポイントのファイルと,添付の EXE ファイルの場合だけをテストしています.
ウィンドウがアクティブになる前に位置とサイズを設定したこの方法では,パワーポイントのファイルを表示する際,最初から指定の位置とサイズで表示されるため,チラつきません.

パワーポイントの ppt ファイルは,TShellExecuteInfo レコード型のメンバーの値を変更すればスライドショーとして起動することもできます.



図11
設計時画面
  • 以下のファイルを開いて表示
  • パワーポイント (PowerPoint) の ppt ファイル
  • パワーポイント (PowerPoint) の pps ファイル
  • 添付の EXE を起動

リスト4
WH_CBT フックを使用して,アプリケーションの位置とサイズを設定
unit Unit1;

interface

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

type
 TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    OpenDialog1: TOpenDialog;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure Button3Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 Form1: TForm1;

implementation

uses ShellAPI;

{$R *.DFM}

function StartCBTHook(Wnd: HWND; X, Y, AWidth, AHeight: Integer;
  AClassName: String): Boolean; stdcall; external 'PLHOOKCBTAPPOPEN.DLL';
procedure StopCBTHook; stdcall; external 'PLHOOKCBTAPPOPEN.DLL';
function  GetHookMsgString:PChar;stdcall; external 'PLHOOKCBTAPPOPEN.DLL';

var
  hHookLib    : THANDLE = 0;
  HookMessage : String;
  MsgHooking  : WORD;

//=============================================================================
//  フォームを表示する時の処理
//  DLLをロードしておく
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  if hHookLib = 0 then begin
    hHookLib    := LoadLibrary('PLHOOKCBTAPPOPEN.DLL');
    HookMessage := GetHookMsgString;
    MsgHooking  := RegisterWindowMessage(PChar(HookMessage));
  end;
end;

//=============================================================================
//  本Form破棄の時にはフックを解除
//  DLLを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  StopCBTHook;
  FreeLibrary(hHookLib);
end;

//=============================================================================
//  パワーポイントのpptファイルを開く
//
//  TShellExecuteInfo.lpVerbの値をShowにすると,全画面のライドショーで表示される
//  更に検出するクラス名をscreenClassにすると,指定した位置とサイズで表示される
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AFile      : String;
  AppCmd     : String;
  AClassName : String;
  AInfo      : TShellExecuteInfo;
  ret        : DWORD;
begin
  AFile      := ExpandFileName('..\プロジェクトの概要.ppt');
  AppCmd     := '';
  AClassName := 'PPTFrameClass';

  //フック開始
  StopCBTHook;
  StartCBTHook(Handle, 0, 100, 800, 650, AClassName);

  FillChar(AInfo, SizeOf(AInfo), #0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := 0;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
  AInfo.lpVerb := 'Open';

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpParameters := PChar(AppCmd);
  AInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;
  end;

  StopCBTHook;
end;

//=============================================================================
//  パワーポイントのppsファイルを開く
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AFile      : String;
  AppCmd     : String;
  AClassName : String;
  AInfo      : TShellExecuteInfo;
  ret        : DWORD;
begin
  AFile      := ExpandFileName('..\プロジェクトの概要.pps');
  AppCmd     := '';
  AClassName := 'screenClass';

  //フック開始
  StopCBTHook;
  StartCBTHook(Handle, 0, 100, 800, 650, AClassName);

  FillChar(AInfo, SizeOf(AInfo), #0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := 0;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
  AInfo.lpVerb := '';

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpParameters := PChar(AppCmd);
  AInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;
  end;

  StopCBTHook;
end;

//=============================================================================
//  添付のEXEを起動
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  AFile      : String;
  AppCmd     : String;
  AClassName : String;
  AInfo      : TShellExecuteInfo;
  ret        : DWORD;
  TargetWnd  : HWND;
begin
  AFile      := ExpandFileName('..\Test App\TesForm.exe');
  AppCmd     := '';
  AClassName := 'TTestForm';

  //フック開始
  StopCBTHook;
  StartCBTHook(Handle, 0, 100, 800, 650, AClassName);

  FillChar(AInfo, SizeOf(AInfo), #0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := 0;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
  AInfo.lpVerb := '';

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpParameters := PChar(AppCmd);
  AInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;
  end;

  Sleep(100);
  StopCBTHook;

  //添付のTesForm.exeの場合,一度フォーカスを本フォームに移動して
  //フォーカス移動しないと,フォーカスが移動しない
  //この現象は起動するアプリの仕様によるようである
  TargetWnd := FindWindow(PChar(AClassName), nil);
  SetForegroundWindow(Handle);
  SetForegroundWindow(TargetWnd);
end;

end.



以下は,サンプルプログラムで使用した DLL のプロジェクトファイルのコードです.


リスト5
使用した DLL のプロジェクトのソースコード
library plHookCBTAppOpen;

uses
  plHookCBTAppOpenUnit in 'plHookCBTAppOpenUnit.pas';

//=============================================================================
//  外部からDLL内のメソッドを利用可能にするためのオマジナイ
//=============================================================================
exports
  StartCBTHook,
  StopCBTHook,
  GetHookMsgString;
end.


リスト6
DLL の処理部分のコード
unit plHookCBTAppOpenUnit;

interface

uses
  Windows, Messages, SysUtils;

//外部のアプリケーションから使用する関数類
function StartCBTHook(Wnd: HWND; X, Y, AWidth, AHeight: Integer;
  AClassName: String): Boolean; stdcall;
procedure StopCBTHook; stdcall;
function  GetHookMsgString:PChar; stdcall;

implementation

//共有メモリの内容の構造体
type
  PHookInfo = ^THookInfo;
  THookInfo = packed record
  HookHandle    : HHOOK;
  HostWinHandle : HWND;
  MsgID         : WORD;
  X      : Integer;
  Y      : Integer;
  Width  : Integer;
  Height : Integer;
  AName  : String[100];
end;

var
  hMapFile : THandle; //メモリマップドファイルのハンドル

const
  HookStr     = 'plHookCBT hook';  //Hostへの通知メッセージ定義に使用
  MapFileName = 'HookCBTAppOpen';  //メモリマップドファイル名

//=============================================================================
//  ウィンドウハンドルからウィンドウクラス名を取得
//  文字列のサイズ設定にStrAllocを使用した場合
//=============================================================================
function GetClassNameStr(hWindow: HWND): string;
var
  Len    : Integer;
  Buffer : array[0..MAX_PATH-1] of Char;
begin
  FillChar(Buffer, SizeOf(Buffer), #0);
  Len := GetClassName(hWindow, Buffer, Length(Buffer));
  if Len > 0 then begin
    SetString(Result, Buffer, Len);
  end else begin
    Result := '';
  end;
end;

//=============================================================================
//  Application側でメッセージ通知を受取るための関数
//=============================================================================
function GetHookMsgString:PChar; stdcall;
begin
  Result := HookStr;
end;

//=============================================================================
//  共有メモリアクセスのための準備
//
//  成功すると0を返す.失敗すると負数を返す.
//  負の値で処理を分岐できるようになっているが,このコードでは未使用.
//=============================================================================
function MapFileMemory(var h:THandle; var p:pointer):integer;
begin
  //メモリマップドファイルを開く
  h := OpenFileMapping(FILE_MAP_ALL_ACCESS,False,PChar(MapFileName));
  if h=0 then begin
    Result := -1;
    exit;
  end;

  //メモリマップドファイルの割り当て
  p := MapViewOfFile(h,FILE_MAP_ALL_ACCESS,0,0,0);
  if p=nil then begin
    Result := -2;
    CloseHandle(h);
    exit;
  end;
  Result := 0;
end;

//=============================================================================
//  共有メモリアクセスの後始末
//  割当てたメモリがあればビューを解除
//  ハンドルがあればクローズ
//=============================================================================
procedure UnMapFileMemory(h:THandle; p:Pointer);
begin
  if p <> nil then UnmapViewOfFile(p);
  if h <> 0 then CloseHandle(h);
end;

//=============================================================================
//  フックのコールバック関数
//  メッセージIDはこのDLL内で設定した文字列から生成したもの
//  必要であれば別途定義する.
//  サンプルプログラムHookCBTCreateとはこのコールバックのコードだけが違ってい
//  るだけで他は全て同じ.
//=============================================================================
function MyHookProc(nCode:integer;wParam:Integer;lParam:Integer):
         Integer; stdcall;
var
  p         : Pointer;
  AWnd      : THandle;
  TargetWnd : HWND;
  AClass    : String;
  Flag      : UINT;
  AName     : String;
begin
  Result := 0;
  if MapFileMemory(AWnd,p) <> 0 then exit;

  if nCode < 0 then begin
    Result := CallNextHookEx(pHookInfo(p)^.HookHandle, nCode, wParam, lParam);
    exit;
  end else begin
    //操作対象のウィンドウハンドルとクラス名取得
    TargetWnd := wParam;
    AClass    := GetClassNameStr(TargetWnd);
    case nCode of
    HCBT_ACTIVATE:
      begin
        //ActiveになるのがANameで指定するクラスのウィンドウだったら
        //位置とサイズを設定
        AName := pHookInfo(p)^.AName;
        if UpperCase(AClass) = UpperCase(AName) then begin
          Flag := SWP_NOZORDER  or SWP_SHOWWINDOW;
          SetWindowPos(TargetWnd,
                       0,
                       pHookInfo(p)^.X,
                       pHookInfo(p)^.Y,
                       pHookInfo(p)^.Width,
                       pHookInfo(p)^.Height,
                       Flag);
        end;
      end;
    end;

    if Result <> 1 then begin
      Result := CallNextHookEx(pHookInfo(p)^.HookHandle, nCode, wParam, lParam);
    end;
  end;
  UnMapFileMemory(AWnd, p);
end;

//=============================================================================
//  フック関数の登録
//  登録するフック関数はMyHookProc
//=============================================================================
function StartCBTHook(Wnd: HWND; X, Y, AWidth, AHeight: Integer;
  AClassName: String): Boolean; stdcall;
var
  AHWnd : THandle;
  p     : Pointer;
begin
  Result := False;

  //メモリマップドファイル使用準備
  MapFileMemory(AHwnd,p);
  if p = nil then begin
    AHWnd := 0;
    exit;
  end;

  //フック情報構造体初期化とフック関数の登録
  pHookInfo(p)^.HostWinHandle := Wnd;
  //フックをインストール}
  pHookInfo(p)^.HookHandle := SetWindowsHookEx(WH_CBT,
                                               Addr(MyHookProc),
                                               hInstance,0);

  //アプリケーションへ通知するメッセージを登録
  //このサンプルではMsgIDは使用していない
  pHookInfo(p)^.MsgID := RegisterWindowMessage(HookStr);
  if pHookInfo(p)^.MsgID = 0 then exit;

  //フック成功
  if pHookInfo(p)^.HookHandle > 0 then begin
    pHookInfo(p)^.X      := X;
    pHookInfo(p)^.Y      := Y;
    pHookInfo(p)^.Width  := AWidth;
    pHookInfo(p)^.Height := AHeight;
    pHookInfo(p)^.AName  := AClassName;
    Result := True;
  end;
  //メモリマップドファイル使用終了処理
  UnMapFileMemory(AHWnd,p);
end;

//=============================================================================
//  フックの解除
//=============================================================================
procedure StopCBTHook; stdcall;
var
  AHWnd : THandle;
  p     : Pointer;
begin
  //メモリマップドファイル使用準備
  MapFileMemory(AHWnd,p);
  if p = nil then begin
    AHWnd := 0;
    exit;
  end;
  //フック解除
  if pHookInfo(p)^.HookHandle > 0 then begin
    UnhookWindowsHookEx(pHookInfo(p)^.HookHandle);
  end;

  //メモリマップドファイル使用終了処理
  UnMapFileMemory(AHWnd,p);
end;

//=============================================================================
//  ユニット初期化部
//
//  High(NativeUInt)の値は
//  32ビットアプリでは$FFFFFFFF
//  64ヒットアプリでは$FFFFFFFFFFFFFFFF
//=============================================================================
initialization
begin
  hMapFile := CreateFileMapping(High(NativeUInt),
                                nil,
                                PAGE_READWRITE,
                                0,
                                SizeOf(THookInfo),
                                PChar(MapFileName));
end;
//=============================================================================
//  ユニット終了処理部
//  メモリマップドファイルのクローズ
//=============================================================================
finalization
begin
  if hMapFile <> 0 then CloseHandle(hMapFile);
end;

end.




04_Windows フォトビューアを位置とサイズを設定して起動


エクスプローラで,JPEG 画像ファイルをダブルクリックすると,Windows 7 では Windows フォトビューアが起動して画像を表示します (デフォルトの設定の場合).
このデフォルトの Windows の環境で,ShellExecuteEx 関数で画像ファイルを指定すると,その画像を表示することができます.しかし,実際には,Windows フォトビューアが起動して,その Windows フォトビューアが画像を表示します.

ShellExecute 関数あるいは ShellExecuteEx 関数は,起動するプログラムを指定しなくても,ファイルを表示することができますが,実際には,そのファイルの拡張子に関連付けされているプログラムが起動して,そのプログラムがファイルを表示します.
したがって,画像の場合,画像を表示するプログラムである Windows フォトビューアのウィンドウハンドルを取得しなければ,位置とサイズは設定できません.



[備考]
Windows XP, Windows Vista には Windows フォトビューアはありません.




05_Windows フォトビューアのフルパスを調べる


Windows フォトビューアのフルパスを求めるために,拡張子 JPG に関連付けされたアプリケーションの実行ファイル名を調べた結果が下図です.このパス名は DLL となっています.DLL を実行ファイル名に指定しても Windows フォトビューアは起動できません.
その下のコードが調査に使用したコードです.



図12
パス名

この DLL 内の関数を調べたのが下図です.複数の関数があります.
この中に ImageView_Fullscreen という関数があります.JPEG 等の画像は,DLL 内のこの関数が表示しています.つまり,Windows フォトビューアは独立したアプリケーションではありません.

DLL 内のこの関数を使用するには,Rundll32.exe というプログラムを,DLL とその DLL 内の関数名を起動パラメータにして起動します.そうすると,このプログラムのウィンドウハンドルが取得でき,そのウィンドウハンドルを使用して位置とサイズが設定できるようになります.



図13
DLL 内の関数類

リスト7
Windows フォトビューアのフルパスを調べる
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

uses ComObj, ShLwApi;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  指定した拡張子に関連づけた実行ファイルのフルパス名を取得する関数
//  取得するのは拡張子の関連付けがレジストリに登録されているものの最初のもの
//
//  AssocQueryStringの使用にはusesにShLwApiが必要
//  OleCheckやWin32Checkを入れておくと例外を検出してくれる
//  OleCheckやWin32Checkの使用にはuuseにComObjが必要
//-----------------------------------------------------------------------------
function GetRelatedFullExePath(ExtName: String;
  DoubleQuote: Boolean=True; CmdParams: Boolean=False):string;
var
  Buf   : array [0..MAX_PAtH-1] of Char;
  dwOut : DWORD;
begin
  Result := '';

  FillChar(Buf, SizeOf(Buf), #0);
  dwOut := MAX_PATH;

  try
    //第2引数をASSOCSTR_EXECUTABLEにすれば"を含まないパス文字列となる
    if DoubleQuote then begin
      OleCheck(AssocQueryString(ASSOCF_NOTRUNCATE,
                                ASSOCSTR_COMMAND,
                                PChar(ExtName),
                                'open',
                                Buf,
                                @dwOut));
    end else begin
      OleCheck(AssocQueryString(ASSOCF_NOTRUNCATE,
                                ASSOCSTR_EXECUTABLE,
                                PChar(ExtName),
                                'open',
                                Buf,
                                @dwOut));
    end;
    Result := Buf;

    if Length(Result) = 0 then begin
      Result := '';
    end else begin
      if not CmdParams then begin
        //"(ダブルコーテーション)で囲まれている場合
        if Pos('" ', Result) > 0 then begin
          Result := Copy(Result, 1, Pos('" ', Result) + 1);
        end;
      end;
    end;
  except
  end;
end;

//=============================================================================
//  Windowsフォトビューアのフルパスを取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  FullPath : String;
begin
  FullPath := GetRelatedFullExePath('.JPG', False, True);
  MessageBox(Handle, PChar(FullPath), '情報', MB_ICONINFORMATION);
end;

end.





06_Windows フォトビューアを CreateProcess 関数で起動


上記で調べた起動パス名を使用し,CreateProcess 関数で画像を表示するサンプルです.
CreateProcess 関数は元々起動アプリケーションのフルパスを指定することになっています.
Windows フォトビューアの位置とサイズが変更できることが確認できます.
このサンプルも,これまでのサンプルと同じように,最小化の状態で起動し,ウィンドウハンドルが取得できたら,そのウィンドウの位置とサイズを変更しています.

また,Windows フォトビューアが起動していたら閉じて,新たに起動するようにしています.自分が起動した Windows フォトビューアだけを閉じたい場合は,起動時の ProcessID またはウィンドウハンドルの値を記憶しておき,その値と同じウィンドウを閉じます.
Windows フォトビューアのクラス名は Photo_Lightweight_Viewer です.



図14
実行結果

リスト8
CreateProcess 関数で,位置とサイズを指定して Windows フォトビューアを起動
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  以下の条件の時に終了とし,引数のlpにその時のウインドウハンドルを返す
//  (1) 引数のlpとウィンドウハンドルが同じ
//  (2) オーナーを持たないウィンドウ
//  (3) 可視状態のウィンドウである
//-----------------------------------------------------------------------------
function EnumTopWindow(hTopWnd: HWND; lp: LPARAM): BOOL; stdcall;
var
  p         : LPDWORD;
  ProcessId : DWORD;
begin
  Result := True;

  GetWindowThreadProcessId(hTopWnd, @ProcessId);
  p := LPDWORD(lp);
  if (ProcessId = p^) and (GetWindow(hTopWnd, GW_OWNER) = 0)
                      and IsWindowVisible(hTopWnd) then begin
    p^     := hTopWnd;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  CreaateProcessで指定アプリを起動
//
//  ExeAppStr : コマンドライン文字列
//  vShow     : SW_SHOW等の表示フラグ
//
//  アプリの起動完了まで待つ
//  起動完了の判定はウインドウハンドルで行う
//  ウィンドウハンドルはEnumWindowsのコールバック関数で取得する
//-----------------------------------------------------------------------------
function ExecAndGetWindow(ExeAppStr: String; vShow: WORD):HWND;
var
  SI         : TStartupInfo;
  PI         : TProcessInformation;
  ret        : DWORD;
  dw         : DWORD;
  dwIDOrigin : DWORD;
  WaitCount  : integer;
begin
  Result     := 0;
  dw         := 0;
  dwIDOrigin := 0;

  FillChar(SI,SizeOf(SI), #0);
  SI.cb          := SizeOf(TStartupInfo);
  SI.dwFlags     := STARTF_USESHOWWINDOW;
  SI.wShowWindow := vShow;
  SetLength(ExeAppStr, Length(ExeAppStr));

  //参照カウンタ対策
  UniqueString(ExeAppStr);
  if not CreateProcess(nil,
                       PChar(ExeAppStr),
                       nil,
                       nil,
                       False,
                       0,
                       nil,
                       nil,
                       SI,
                       PI) then begin
    exit;
  end else begin
    repeat
      ret := WaitForInputIdle(PI.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

    //EnumWindowsにプロセスIDを渡してウィンドウを検索
    //EnumWindowsの第2引数にウィンドウハンドルが戻ってくる
    //アプリの起動完了までは時間がかかることがあるのでループ
    if ret = 0 then begin
      WaitCount := 0;
      dwIDOrigin := PI.dwProcessId;
      repeat
        Sleep(100);
        dw := dwIDOrigin;
        EnumWindows(@EnumTopWindow, LPARAM(@dw));
        if dw <> dwIDOrigin then begin
          Result := dw;
          break;
        end;
        Inc(WaitCount);
      until IsWindow(dw) or (WaitCount > 100);
    end;

    //プロセスの起動に失敗したら強制終了させる
    if (ret <> 0) or (dw = dwIDOrigin) then begin
      Result := 0;
      TerminateProcess(PI.hProcess, 0);
    end;
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

  //---------------------------------------------------------------------------
  //  環境変数を含む(%等の文字を含む)を実際のパス名に変換
  //---------------------------------------------------------------------------
  function ExpandEnvironmentString(S: String): String;
  var
    LDstChar : array [0..MAX_PATH - 1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
    Result := LDstChar;
  end;
  //---------------------------------------------------------------------------

//=============================================================================
//  Windows フォトビューアの位置とサイズを設定
//  CreateProcessを使用した例
//
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LAppStr       : string;
  LPhotViewPath : string;
  LAppWnd       : HWND;
  LTargetWnd    : HWND;
begin
  //Windows フォトビューアが起動していたら閉じる
  repeat
    LTargetWnd := FindWindow('Photo_Lightweight_Viewer', nil);
    if LTargetWnd > 0 then begin
      PostMessage(LTargetWnd, WM_CLOSE, 0, 0);
    end;
  until LTargetWnd = 0;

  LAppStr       := 'Rundll32.exe';
  LPhotViewPath := '"%ProgramFiles%\Windows Photo Viewer\PhotoViewer.dll", '
                 + 'ImageView_Fullscreen';
  LPhotViewPath := ExpandEnvironmentString(LPhotViewPath);
  LAppStr := LAppStr + ' '
           + LPhotViewPath + ' '
           + ExpandFileName('..\PenguinsB.jpg');

  LAppWnd := ExecAndGetWindow(LAppStr, SW_MINIMIZE);
  SetPositionSize(LAppWnd, 0, 0, 500, 400);

  //対象アプリの起動が完了したので,このフォームをアクティブにしてみる
  SetForegroundWindow(Handle);
end;

//=============================================================================
//  ウィンドウの位置とサイズを指定して表示
//
//  本サンプルでは,ウィンドウを最小化して起動しているのがある
//  最小化から表示する際,そのまま表示して位置とサイズを設定すると,現在の位置
//  とサイズで表示されてしまう
//  そこで,SetWindowPlacementで内部的に位置とサイズを設定してから表示する
//  TWindowPlacement.rcNormalPositionは位置だけの指定も可能
//=============================================================================
procedure TForm1.SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
var
  LRect        : TRect;
  LOriginPlace : TWindowPlacement;
  LOpenPlace   : TWindowPlacement;
begin
  if AWnd > 0 then begin
    //表示位置とサイズの値
    LRect := Rect(X, Y, XSize, YSize);

    //元の表示状態の構造体の値を取得して新しい構造体にコピー
    LOriginPlace.length := SizeOf(TWindowPlacement);
    LOpenPlace.length   := SizeOf(TWindowPlacement);
    GetWindowPlacement(AWnd, LOriginPlace);
    LOpenPlace := LOriginPlace;

    //新しい構造体の値(ここでは表示位置とサイズ)を設定
    LOpenPlace.rcNormalPosition := LRect;
    LOpenPlace.showCmd := SW_SHOWNORMAL;
    SetWindowPlacement(AWnd, Addr(LOpenPlace));
    //下のコードで再描画が行われ,指定した位置とサイズで表示される
    SetForegroundWindow(AWnd);
  end else begin
    ShowMessage('アプリケーションの起動に失敗しました. ');
  end;
end;

end.




07_Windows フォトビューアを ShellExecuteEx 関数で起動


同じく,ShellExecuteEx 関数を使用した例です.
ShellExecuteEx 関数はファイル名を指定すれば,そのファイルの拡張子に関連付けされたプログラムが起動しますが,このサンプルのように,起動するプログラム (アプリケーション) を指定すれば,そのプログラムを起動することができます.

こちらも Windows フォトビューアの位置とサイズが変更できることが確認できます.
このサンプルも,これまでのサンプルと同じように,最小化の状態で起動し,ウィンドウハンドルが取得できたら,そのウィンドウの位置とサイズを変更しています.

また,Windows フォトビューアが起動していたら閉じて,新たに起動するようにしています.自分が起動した Windows フォトビューアだけを閉じたい場合は,起動時の ProcessID かウィンドウハンドルの値を記憶しておき,その値と同じウィンドウを閉じます.
Windows フォトビューアのクラス名は Photo_Lightweight_Viewer です.



図15
実行結果

リスト9
ShellExecuteEx 関数で,位置とサイズを指定して Windows フォトビューアを起動
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  EnumWindowsのコールバック関数
//  以下の条件の時に終了とし,引数のlpにその時のウインドウハンドルを返す
//  (1) 引数のlpとウィンドウハンドルが同じ
//  (2) オーナーを持たないウィンドウ
//  (3) 可視状態のウィンドウである
//-----------------------------------------------------------------------------
function EnumTopWindow(hTopWnd: HWND; lp: LPARAM): BOOL; stdcall;
var
  LpWindow   : LPDWORD;
  LProcessId : DWORD;
begin
  Result := True;

  GetWindowThreadProcessId(hTopWnd, @LProcessId);
  LpWindow := LPDWORD(lp);
  if (LProcessId = LpWindow^) and (GetWindow(hTopWnd, GW_OWNER) = 0)
                      and IsWindowVisible(hTopWnd) then begin
    LpWindow^ := hTopWnd;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  ShellExecuteExでファイル表示またはアプリを起動
//  戻り値は対象のウィンドウハンドル.起動に失敗すると0
//
//  AFile       : 起動プログラムのフルパスまたはファイルのフルパス
//  AParameters : コマンドラインオプション.表示するファイル等
//  Verb        : Open,Show,Print等の動作内容
//  vShow       : SW_SHOW等の表示フラグ
//
//  Windows Vistaでの注意
//  Windows Vistaではファイルを開く際に管理者権限が必要なことがある
//-----------------------------------------------------------------------------
function RunExeFile(hWnd: HWND; AFile: String; AParameters: String;
   Verb: String; vShow: Integer): Cardinal;
var
  AInfo      : TShellExecuteInfo;
  hProcess   : THandle;
  ret        : DWORD;
  dwIDOrigin : DWORD;
  dw         : DWORD;
  WaitCount  : integer;
begin
  Result     := 0;
  dw         := 0;
  dwIDOrigin := 0;

  FillChar(AInfo, SizeOf(AInfo), 0);
  AInfo.cbSize := SizeOf(AInfo);
  AInfo.Wnd    := hWnd;
  AInfo.fMask  := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_NO_UI;
  AInfo.lpVerb := PChar(Verb);

  AInfo.lpFile       := PChar(AFile);
  AInfo.lpParameters := PChar(AParameters);
  AInfo.nShow        := vShow;

  ShellExecuteEx(@AInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if AInfo.hInstApp >= 33 then begin
    repeat
      ret := WaitForInputIdle(AInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

    //EnumWindowsにプロセスIDを渡してウィンドウを検索
    //EnumWindowsの第2引数にウィンドウハンドルが戻ってくる
    //ウィンドウの起動完了までは時間がかかるのでループ
    if ret = 0 then begin
      WaitCount  := 0;
      dwIDOrigin := GetProcessId(AInfo.hProcess);
      repeat
        Sleep(100);
        dw := dwIDOrigin;
        EnumWindows(@EnumTopWindow, LPARAM(@dw));
        if dw <> dwIDOrigin then begin
          Result := dw;
          break;
        end;
        Inc(WaitCount);
      until IsWindow(dw) or (WaitCount > 100);
    end;

    //プロセスの起動に失敗したらプロセスを閉じる
    if (ret <> 0) or (dw = dwIDOrigin) then begin
      hProcess := AInfo.hProcess;
      if hProcess > 0 then begin
        TerminateProcess(hProcess, 0);
        CloseHandle(hProcess);
      end;
    end;
  end;
end;

  //---------------------------------------------------------------------------
  //  環境変数を含む(%等の文字を含む)を実際のパス名に変換
  //---------------------------------------------------------------------------
  function ExpandEnvironmentString(S: String): String;
  var
    LDstChar : array [0..MAX_PATH - 1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
    Result := LDstChar;
  end;
  //---------------------------------------------------------------------------

//=============================================================================
//  Windows フォトビューアの位置とサイズを設定
//  ShellExecuteExを使用した例
//
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LAppStr       : string;
  LPhotViewPath : string;
  LAppCmd       : string;
  LAppWnd       : HWND;
  LTargetWnd    : HWND;
begin
  //Windows フォトビューアが起動していたら閉じる
  repeat
    LTargetWnd := FindWindow('Photo_Lightweight_Viewer', nil);
    if LTargetWnd > 0 then begin
      PostMessage(LTargetWnd, WM_CLOSE, 0, 0);
    end;
  until LTargetWnd = 0;

  LAppStr       := 'Rundll32.exe';
  LPhotViewPath := '"%ProgramFiles%\Windows Photo Viewer\PhotoViewer.dll", '
                 + 'ImageView_Fullscreen';
  LPhotViewPath := ExpandEnvironmentString(LPhotViewPath);
  LAppCmd := LPhotViewPath + ' ' + ExpandFileName('..\PenguinsB.jpg');

  LAppWnd := RunExeFile(0, LAppStr, LAppCmd, 'Open', SW_MINIMIZE);
  SetPositionSize(LAppWnd, 0, 0, 500, 400);

  //対象アプリの起動が完了したので,このフォームをアクティブにしてみる
  SetForegroundWindow(Handle);
end;

//=============================================================================
//  ウィンドウの位置とサイズを指定して表示
//
//  本サンプルでは,ウィンドウを最小化して起動しているのがある
//  最小化から表示する際,そのまま表示して位置とサイズを設定すると,現在の位置
//  とサイズで表示されてしまう
//  そこで,SetWindowPlacementで内部的に位置とサイズを設定してから表示する
//  TWindowPlacement.rcNormalPositionは位置だけの指定も可能
//=============================================================================
procedure TForm1.SetPositionSize(AWnd: HWND; X, Y, XSize, YSize: Integer);
var
  LRect        : TRect;
  LOriginPlace : TWindowPlacement;
  LOpenPlace   : TWindowPlacement;
begin
  if AWnd > 0 then begin
    //表示位置とサイズの値
    LRect := Rect(X, Y, XSize, YSize);

    //元の表示状態の構造体の値を取得して新しい構造体にコピー
    LOriginPlace.length := SizeOf(TWindowPlacement);
    LOpenPlace.length   := SizeOf(TWindowPlacement);
    GetWindowPlacement(AWnd, LOriginPlace);
    LOpenPlace := LOriginPlace;

    //新しい構造体の値(ここでは表示位置とサイズ)を設定
    LOpenPlace.rcNormalPosition := LRect;
    LOpenPlace.showCmd := SW_SHOWNORMAL;
    SetWindowPlacement(AWnd, Addr(LOpenPlace));
    //下のコードで再描画が行われ,指定した位置とサイズで表示される
    SetForegroundWindow(AWnd);
  end else begin
    ShowMessage('アプリケーションの起動に失敗しました. ');
  end;
end;

end.




08_ファイルが開いているか確認する方法


本ページのサンプルコードでは,メモ帳はファイルを指定していませんから,いくつでも起動できます.ワードやエクセルは文書やブックを指定していますから,同じファイル名の文書やブックを開く時は読み取り専用ということになります.
既に同じ名前のファイルを開いている時は,新たにそのファイルを開かないようにするには,例えば次のように,FileOpen 関数でチェックすることができます.FileOpen 関数の戻り値は THandle 型とします.そうしないと HFILE_ERROR の値を検出できません.


リスト10
指定したファイルが開いているかをチェックする方法
GetRelatedFullExePath, ExecAndGetWindow は最初のサンプルで使用している関数
//=============================================================================
//  ワードの文書をワードで開く
//  起動オプション(起動パラメータ)の区切りには空白が必要なことに注意
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  FilePath : String;
  hFile    : THandle;
  AppStr   : String;
begin
  FilePath := ExpandFileName('..\テスト文書.doc');

  hFile := FileOpen(FilePath, fmOpenReadWrite);
  if hFile <> HFILE_ERROR then begin
    FileClose(hFile);

    //指定拡張子に関連づけた実行ファイルのフルパス名を取得
    AppStr := GetRelatedFullExePath('.doc', True, False);
    AppStr := Appstr + ' /q ' + FilePath;

    //指定したプログラムでファイルを表示する(開く)
    ExecAndGetWindow(AppStr, SW_SHOW);
  end else begin
    MessageBox(Handle, '文書は既に開いています', '情報', MB_ICONINFORMATION);
  end;
end;