CreateProcessでウィンドウの位置と大きさを指定

解決


たま  2009-12-26 07:33:47  No: 36554

お世話になります

CreateProcessで他アプリを起動させているのですが、
StartupInfoの設定が反映されていないようでウインドウの位置やサイズが
思い通りになりません。

下記サイトを参考にしました
http://www.eva.hi-ho.ne.jp/taketani/delphi/system.shtml
ここのコードをそのまま使用してもやはり駄目でした

なぜ?・・・
お分かりになる方 宜しくお願い致します

D7 WinXP


Mr.XRAY  2009-12-26 07:59:59  No: 36555

Mr.XRAYです.

>なぜ?・・・

具体的にどのようなコードで,どんなアプリやウインドウが対象なのかは不明ですが,
そのコードの機能に問題がないとしても,アプリやウィンドウの起動時に必ずしも期待通り
になるとは限りません.

「自作のアプリを,外部からサイズや位置を変更できないようにするにはどうしたらいいですか」

という質問があったとします.
これに対応したコーディングをしたプログラムではできないことになります.
(当然,コーディングにもよりますが)

たとえば,パワーポイント2007のスライドショーでは,表示開始時には位置とサイズは
変更できません(表示終了後であれば可能です).

いろいろな他のアプリやウィンドウでテストしてみてはいかがでしょうか.


Mr.XRAY  2009-12-26 08:06:55  No: 36556

例えば以下のリスト1では,メモ帳の位置とサイズを固定して,
外部から変更できないようにしています.

アプリケーションの起動を知る・起動阻止
http://mrxray.on.coocan.jp/Delphi/plSamples/280_HookCBTActivate.htm#Activate


たま  2009-12-26 08:14:02  No: 36557

Mr.XRAY 様 ありがとうございます

>具体的にどのようなコードで,どんなアプリやウインドウが対象なのかは不明ですが,
>そのコードの機能に問題がないとしても,アプリやウィンドウの起動時に必ずしも期待通り
>になるとは限りません.

先に挙げたURLのサンプルコードをそのままコピペで対象はメモ帳です
そのサンプルのタイトルが「ウィンドウの位置と大きさを指定して実行」だったので出来るもの思い込んでいました

ありがとうございました


Mr.XRAY  2009-12-26 08:14:47  No: 36558

>いろいろな他のアプリやウィンドウでテストしてみてはいかがでしょうか.

もっとも簡単なテストは,フォーム1つだけのアプリを作成し,
それを制御してみることでしょう.
すでにテスト済みでしたらご容赦ください.


Mr.XRAY  2009-12-26 08:39:54  No: 36559

>対象はメモ帳です

メモ帳,つまりNotepad.exeですね.メモ帳の場合,CreateProcessで起動しても
メモ帳のインスタンスができますが,トップレベルの(外部から操作可能な)ウィンドウ
ハンドルが取得できません.

したがって,起動後にメモ帳のハンドルを取得して,そのハンドルで位置とサイズを
指定すれば可能と思われます.
具体的なコードは提示できませんが,以下を参考にしてください.

起動したプロセスのトップレベルウィンドウのハンドルを取得
http://mrxray.on.coocan.jp/Halbow/Notes/N002.html


Mr.XRAY  2009-12-26 08:49:05  No: 36560

>したがって,起動後にメモ帳のハンドルを取得して,そのハンドルで位置とサイズを
>指定すれば可能と思われます.

http://mrxray.on.coocan.jp/Halbow/Notes/N002.html

例えば,上のコードのEnumTopWindow関数で,ウィンドウハンドルが見つかったら
MoveWindow関数でセットするとかですね.
もちろん,他にも方法はいろいろあります.
どんなアプリに組み込むかで検討してください.

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;
    MoveWindow(hTopWnd,0,0,400,500,True);   //ここを追加
    result := false;
  end;
end;

もしうまく動作したら,この掲示板を見ている方にも参考になるように,
テストプログラムを作成して,コードを見せてください.


Mr.XRAY  2009-12-26 08:53:32  No: 36561

Mr.XRAYです.
上のコードの動作確認環境は,

Windows XP(SP3) + Delphi 7 Proです.


たま  2009-12-26 10:13:24  No: 36562

Mr.XRAY 様 何から何までありがとうございます

結局 私の環境では StartupInfo を使ったウインドウ位置の設定は出来ませんでした。
色々なアプリで試しました。(手抜きはしていませんよ)

・・・で
Mr.XRAY 様の示してくださったコードを実行したところ、メモ帳ではうまく動作しました。(成功率100%)
しかし、他のアプリ(具体的にはEmEditor)を起動すると成功率は50%以下といったところです。

>もしうまく動作したら,この掲示板を見ている方にも参考になるように,
>テストプログラムを作成して,コードを見せてください.

なんかハードルが上がってしまった気がしますが・・・
しばらくお待ち下さい。


Mr.XRAY  2009-12-26 10:55:49  No: 36563

Mr.XRAYです.今日は夜更かしの予定なので...

>起動すると成功率は50%以下といったところです。

そうですか.ハンドル取得のタイミングがよくないのかも知れません.
また,この方法では,一旦デフォルトの位置に表示されてしまうので,
体裁はあまりよくないですね.

>テストプログラムを作成して,コードを見せてください.

ハハハッ.無理強いはしません.ただここは情報共有の場ですから.
決してサポートセンターではありません.

それと,CrettePrecessで位置とサイズを指定して起動可能にするためには,
その,位置とサイズを指定したいアプリ自身が,ウインドウを生成する時に,
SW_SHOWDEFAULT というフラグを指定していないとできません.
メモ帳などはこのフラグを指定していないということでしょうね.
以下を参考にしてください.

1−3−4 ウィンドウの表示
http://mrxray.on.coocan.jp/Halbow/Chap01.html#Chap1-3-4

どうしても位置とサイズを指定して起動したければ,前に提示したページのコードの
ように,フックをかけるしかありません.Window XPであれば使用可能です.
(Vistaではできません)

アプリケーションの起動を知る・起動阻止
http://mrxray.on.coocan.jp/Delphi/plSamples/280_HookCBTActivate.htm#Activate


Mr.XRAY  2009-12-26 20:27:45  No: 36564

>ハンドル取得のタイミングがよくないのかも知れません.

コードはあくまで参考です.いろいろ工夫してみてください.

procedure TForm1.Button1Click(Sender: TObject);
var
  hMainWnd : HWND;
begin
  hMainWnd := ExecAndGetWindow('Notepad.exe');
  MoveWindow(hMainWnd,0,0,400,500,True);
end;

とか.

参考リンク
http://mrxray.on.coocan.jp/Halbow/Notes/N002.html


Mr.XRAY  2009-12-27 00:14:45  No: 36565

Mr.XRAYです.

大変失礼しました.先のリンクのページの EnumTopWindowは,見えるウィンドウハンドルだけ
検索するようになっていますね.
そこで,メモ帳を非表示でCreateProcessし,ハンドルを取得したら表示するようにすれば
体裁いいですね.もちろん,その前に位置を指定しておきます.

参考リンク
http://mrxray.on.coocan.jp/Halbow/Notes/N002.html

を参考にしたコードです(そのままですが...).
動作確認環境は前と同じです.

unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

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) then
  begin
    p^ := hTopWnd;
    result := false;
  end;
end;

function ExecAndGetWindow(ExeAppStr:string):HWND;
var
  SI:TStartupInfo;
  PI:TProcessInformation;
  ret,dw: DWORD;
  WaitCount:integer;
begin
  result := 0;
  FillChar(SI,SizeOf(SI),#0);
  SI.cb          := SizeOf(TStartupInfo);
  //以下の4行とdwFlagsにSTARTF_USERPOSITION,STARTF_USESIZEを追加
  SI.dwX         := 0;
  SI.dwY         := 0;
  SI.dwXSize     :=500;
  SI.dwYSize     :=600;
  SI.dwFlags     := STARTF_USESHOWWINDOW or
                    STARTF_USEPOSITION or
                    STARTF_USESIZE;

  SI.wShowWindow := SW_HIDE;
  if not CreateProcess(nil,
                       PChar(ExeAppStr),
                       nil,
                       nil,
                       false,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       SI,
                       PI) then
    exit
  else begin
    repeat
      ret := WaitForInputIdle(PI.hProcess,50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

    WaitCount := 0;
    repeat
      Sleep(100);
      if ret = 0 then begin
        dw := PI.dwProcessId;
        EnumWindows(@EnumTopWindow, LPARAM(@dw));
        if dw <> PI.dwProcessId then result := dw;
      end;
      Inc(WaitCount);
    until IsWindow(dw) or (WaitCount > 5);

    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  hMainWnd : HWND;
begin
  hMainWnd := ExecAndGetWindow('Notepad.exe');
  MoveWindow(hMainWnd,0,0,400,500,True);
  ShowWindow(hMainWnd,SW_SHOW);
end;

end.


Mr.XRAY  2009-12-27 00:18:53  No: 36566

またまた失礼しました.
CreateProcessで位置とサイズを指定しても無効なので,その部分のコードは不要でした.

unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

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) then
  begin
    p^ := hTopWnd;
    result := false;
  end;
end;

function ExecAndGetWindow(ExeAppStr:string):HWND;
var
  SI:TStartupInfo;
  PI:TProcessInformation;
  ret,dw: DWORD;
  WaitCount:integer;
begin
  result := 0;
  FillChar(SI,SizeOf(SI),#0);
  SI.cb          := SizeOf(TStartupInfo);
  SI.dwFlags     := STARTF_USESHOWWINDOW;
  SI.wShowWindow := SW_HIDE;
  if not CreateProcess(nil,PChar(ExeAppStr),nil,nil,false,0,nil,nil,SI,PI) then
    exit
  else begin
    repeat
      ret := WaitForInputIdle(PI.hProcess,50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

    WaitCount := 0;
    repeat
      Sleep(100);
      if ret = 0 then begin
        dw := PI.dwProcessId;
        EnumWindows(@EnumTopWindow, LPARAM(@dw));
        if dw <> PI.dwProcessId then result := dw;
      end;
      Inc(WaitCount);
    until IsWindow(dw) or (WaitCount > 5);

    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  hMainWnd : HWND;
begin
  hMainWnd := ExecAndGetWindow('Notepad.exe');
  MoveWindow(hMainWnd,0,0,400,500,True);
  ShowWindow(hMainWnd,SW_SHOW);
end;

end.


たま  2009-12-27 06:50:31  No: 36567

Mr.XRAY 様 ありがとうございます

完璧な動作になりました。

参考リンクのコードと比較しましたが・・・
なるほど といえば なるほど なのですが
非表示にしただけで全て解決してしまった
というか
ハンドル取得のタイミングの問題は「見えてなくてもいいよ」としただけで解決のようですね。

本当にありがとうございました


Mr.XRAY  2009-12-27 07:16:22  No: 36568

Mr.XRAYです.たまさんの環境でも動作したようでよかったのですが,

Notepad.exeならうまくいくのですが,他のアプリではうまくいくとか限りません.
あしからず.
というのは,アプリのメインウィンドウかそれとも他のウィンドウの操作が必要か.
にもよります.
また,表示されている状態でないとうまくいかないアプリもあると思います.

いずれにしても思考錯誤が必要と思われます.
他のアプリの操作関係は,プログラムとしては難しい方だと思います.少なくても私には.
がんばってください.


Mr.XRAY  2009-12-28 08:11:15  No: 36569

>いずれにしても思考錯誤が必要と思われます.

少し試行錯誤してみました.参考になれば.

位置とサイズを指定してアプリを起動
http://mrxray.on.coocan.jp/Delphi/plSamples/266_App_CreateOpen.htm


たま  2009-12-29 06:36:58  No: 36570

Mr.XRAY 様 すばらしいサンプルをありがとうございます

今後参考にさせていただきます
ただ今回は・・・
クラス名での判定は無理そうです
今回の対象はエディタなのですが ユーザーによってさまざまなエディタを使用されると思います
ですのでクラス名の決め打ちは出来ないです・・・多分

数種類 登録しておいて この中から選んで使いなさい
とか
自分でクラス名を調べて設定しなさい
などとは言えないでしょうから

・・・と 書いてるうちに
クラス名を取得する機能を付けとけば可能かな・・・と思い始めてます

やはり今回 参考にさせて頂くかもしれません

ありがとうございました


Mr.XRAY  2009-12-31 01:10:16  No: 36571

Mr.XRAYです.
大変,うまい方法を考えつきました.

>クラス名を取得する機能を付けとけば可能かな・・・と思い始めてます

クラス名がわかれば確実ですが,
前のサンプルでは,SW_HIDEで実行した場合,ウインドウハンドルが正常に取得できない
ことがあります.

こちらをご覧ください.

http://mrxray.on.coocan.jp/Delphi/plSamples/266_App_CreateOpen.htm#04

これは,SW_HIDEではなく,ウインドウを最小化して起動する方法です.
これですと,IsWindowVisible(hTopWnd)で検出可能です.

もちろん,これでも,最初から最大化して開くようなもの,例えばパワーポイントビューワ
では,一度最大化してから,設定した位置とサイズになります.そのようなアプリでは,

http://mrxray.on.coocan.jp/Delphi/plSamples/266_App_CreateOpen.htm#03

を使用せざるをえないと思います.

今回は,いろいろ勉強になりました.
ありがとうございます.
まさか不特定のアプリ(エディタでしたったけ)を制御したいなんて要望があるなんて
思いもしませんでした.いい機会ですので挑戦してみました.
まだテストが十分でないと思いますので,いろいろ不具合が出るかも知れません.


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加