[Delphi:91145] ユーザーコントロールのResizeイベントで外部アプリのウインドウサイズを変更するには?

598 views
Skip to first unread message

Fumio Kawamata

unread,
Mar 5, 2009, 8:08:19 AM3/5/09
to Del...@ml.users.gr.jp
川又です。こんばんは。

自前のフォーム中のコントロール(TPanel)に、外部プログラム(Explorer.exe)を
貼り付けて、自前のコントロールをリサイズしたときに、外部プログラムのサイズ
も変えるようにコーディングしてみたのですが、自前のフォーム自体が、リサイズ
不能になってしまいます。(厳密には、ほんの数ピクセルだけリサイズできてます。)

Windows.SetParent(hMainWnd, PanelExplorer.Handle);
でExplorerを貼り付けて、

procedure TFormMain.FormResize(Sender: TObject);
begin
SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, 0);
end;
でリサイズしようと試みました。

ネットで検索したら、Microsoftのページ
http://support.microsoft.com/?scid=kb%3Ben-us%3B187740&x=5&y=9
を見つけました。

引用
SYMPTOMS
The Resize Event of a UserControl will not fire if you use the
MoveWindow API function to resize the window of the UserControl.
NOTE: Other API calls that manipulate the UserControl window,
such as SetWindowPos, may also show this behavior.

CAUSE
When you manipulate the UserControl window through API calls,
you bypass the UserControl Object. So any of the events that
would normally get raised from a COM call to the UserControl
may fail when you bypass the UserControl object and manipulate
the UserControl's window directly.

実際、書いてあるようなことが起きてるように思います。

Microsoftのページでは、解決策として、Visual Basicの Move メソッドを
使うように書いてあるのですが、仮にDelphiにMoveメソッドがあったとして
も、外部プログラムのウインドウハンドルを指定して Moveすることまでは
出来ないだろうのではないかと思います。(この時点で、勘違いしてますか?)

思いつきとして、フォームにTTimerを配置して、

var
PanelWidth: Integer;
PanelHeight: Integer;

procedure TFormMain.FormResize(Sender: TObject);
begin
if TimerResized.Enabled=False then TimerResized.Enabled:=True;
end;

procedure TFormMain.TimerResizedTimer(Sender: TObject);
begin
TimerResized.Enabled:=False;
(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, 0);
end;

としてみたのですが、ゆっくりと、TTimerのIntervalで指定した時間を超えて
フォームをリサイズすると、リサイズできていたフォームのサイズが、リサイズ前
のサイズに戻ってしまいます。TTimerのIntervalを2000(msec)にすると、まぁ、
とりあえず動くと言えるかも...と思えますが、Intervalを50(msec)にすると、
リサイズ不能(厳密には、冒頭で書いたように、ほんの数ピクセルだけリサイズ
可能)になります。

OnResizeイベントには何も記述せず、
procedure TFormMain.ToolButton5Click(Sender: TObject);
begin
SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, SWP_NOOWNERZORDER);
end;
のように指定して、外部プログラムのウインドウサイズをTPanelのサイズに
合わせることは可能です。

何とか、TControl の OnResize で外部プログラムのウインドウをリサイズ
したいのですが、実現方法はあるのでしょうか?

環境は、
Windows XP SP3
Delphi 6 Pro. Update pack 2
です。

よろしくお願いします。

--
Fumio Kawamata <fu...@my.email.ne.jp>


高木太郎

unread,
Mar 5, 2009, 8:36:18 AM3/5/09
to Del...@ml.users.gr.jp
 こんばんは、イマジオムの高木です。

川又さん:


> 自前のフォーム中のコントロール(TPanel)に、外部プログラム(Explorer.
> exe)を貼り付けて、自前のコントロールをリサイズしたときに、外部

> プログラムのサイズも変えるようにコーディングしてみたのですが、自前の


> フォーム自体が、リサイズ不能になってしまいます。(厳密には、ほんの
> 数ピクセルだけリサイズできてます。)
>
> Windows.SetParent(hMainWnd, PanelExplorer.Handle);
> でExplorerを貼り付けて、
>
> procedure TFormMain.FormResize(Sender: TObject);
> begin
> SetWindowPos(hMainWnd, 0, 0, 0,
> PanelExplorer.Width, PanelExplorer.Height, 0);
> end;
> でリサイズしようと試みました。

 追試をせず、お書きの現象だけから判断しますが、TFormMain.FormResize が
再帰的に呼び出されたりはしていませんか?

procedure TFormMain.FormResize(Sender: TObject);
begin
WriteLn('Enter'); {<======= 追加 }


SetWindowPos(hMainWnd, 0, 0, 0,
PanelExplorer.Width, PanelExplorer.Height, 0);

WriteLn('Exit'); {<======= 追加 }
end;

などとすると、簡単にわかると思います。

 もし再帰的に呼び出されているのであれば、子ウィンドウの SetWindowPos の
呼び出しによって OnResize が呼び出される仕組みになっているということ
ですから、OnResize から SetWindowPos を呼ぶのはあきらめた方がいいと
思います。 この場合、短周期のタイマイベントか何かで SetWindowPos を
呼ぶようにし、その発火条件を OnResize イベントの中で判定するように
するのがいいと思います。

 再帰的に呼び出されていないのであれば──今すぐには解決法が思い
浮かびません。 ごめんなさい。
――――――――――――――――――――――――――――――――――――
株式会社イマジオム 代表取締役 高木太郎
〒316-0024 茨城県 日立市 水木町 1-11-10
電話:0294-28-0147
ファクシミリ:0294-28-0148
電子メール:tarou_...@imageom.co.jp
ホームページ:http://www.imageom.co.jp/

Fumio Kawamata

unread,
Mar 5, 2009, 9:21:40 AM3/5/09
to Del...@ml.users.gr.jp
高木さん こんばんは。川又です。

On Thu, 05 Mar 2009 22:36:18 +0900
aaa1...@pop06.odn.ne.jp (高木太郎) wrote:

>  追試をせず、お書きの現象だけから判断しますが、TFormMain.FormResize が
> 再帰的に呼び出されたりはしていませんか?
>
> procedure TFormMain.FormResize(Sender: TObject);
> begin
> WriteLn('Enter'); {<======= 追加 }
> SetWindowPos(hMainWnd, 0, 0, 0,
> PanelExplorer.Width, PanelExplorer.Height, 0);
> WriteLn('Exit'); {<======= 追加 }
> end;
>
> などとすると、簡単にわかると思います。

Writeln('Enter'); だと、EInOutErrorクラスの例外を生成しました。 'I/O エラー (105)'
となってしまったので、

procedure TFormMain.FormResize(Sender: TObject);
begin
ShowMessage('Enter');


SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, 0);

ShowMessage('Exit');
end;

としました。
プログラムを起動すると、Enter と Exit が表示されました。その後、

procedure TFormMain.ToolButton1Click(Sender: TObject);
begin
SetWindowLong(hMainWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, SWP_NOOWNERZORDER);
Windows.SetParent(hMainWnd, PanelExplorer.Handle);
ShowWindow(hMainWnd, SW_SHOW);
end;
(↑は抜粋です)

procedure TFormMain.ToolButton3Click(Sender: TObject);
begin
SetWindowPos(hMainWnd, 0, 0, 0, 200, 100, SWP_NOOWNERZORDER);
end;

が実行される状況にしてみましたが、Enter と Exit は表示されませんでした。
これは、再帰していないと判断して良いのでしょうか?
(なお、フォームをリサイズすると、当然ながら、Enter と Exit が表示されます。)

>  再帰的に呼び出されていないのであれば──今すぐには解決法が思い
> 浮かびません。 ごめんなさい。

いえいえいえ。おとといあたりからこれで格闘しているので、どんなヒント、
サジェスチョンでも歓迎致します。

--
Fumio Kawamata <fu...@my.email.ne.jp>


高木太郎

unread,
Mar 5, 2009, 10:05:26 AM3/5/09
to Del...@ml.users.gr.jp
 こんばんは。 イマジオムの高木です。

>>  追試をせず、お書きの現象だけから判断しますが、TFormMain.Form
>> Resize が再帰的に呼び出されたりはしていませんか?
>>
>> procedure TFormMain.FormResize(Sender: TObject);
>> begin
>> WriteLn('Enter'); {<======= 追加 }
>> SetWindowPos(hMainWnd, 0, 0, 0,
>> PanelExplorer.Width, PanelExplorer.Height, 0);
>> WriteLn('Exit'); {<======= 追加 }
>> end;
>>
>> などとすると、簡単にわかると思います。
>
> Writeln('Enter'); だと、EInOutErrorクラスの例外を生成しました。

 プロジェクトファイル(*.DPr)の2行目に、{$APPTYPE CONSOLE} の
1行を追加してください。 コンソール画面が現れ、I/Oエラー例外は
発生しなくなります。 これで調べてみてくださいませんか?

 メッセージボックスを表示させる方法だと、だいぶメッセージの流れが
変わってしまうので、当初の現象の追跡になりません。

Fumio Kawamata

unread,
Mar 5, 2009, 11:12:03 AM3/5/09
to Del...@ml.users.gr.jp
遅くまでつきあっていただいてありがとうございます。

On Fri, 06 Mar 2009 00:05:26 +0900
aaa1...@pop06.odn.ne.jp (高木太郎) wrote:

>  プロジェクトファイル(*.DPr)の2行目に、{$APPTYPE CONSOLE} の
> 1行を追加してください。 コンソール画面が現れ、I/Oエラー例外は
> 発生しなくなります。 これで調べてみてくださいませんか?

こんな記述が出来たんですね。GUIアプリに、{$APPTYPE CONSOLE} を書くという
発想は全然なかったです。

実験結果なのですが、CreateProcess で新たに作成されたExplorerのウインドウ
ハンドルを検出できずにエラーになってしまい、SetWindowPosによりリサイズ
イベントが発生するかどうかのテストまで至りませんでした
(コンソールは表示され、Enter と Exitは表示されました。)

クラス名が"ExploreWClass"のウインドウハンドルを列挙してから、CreateProcessで
Explorerのウインドウを作成し、その後、改めて、クラス名が"ExploreWClass"のウイ
ンドウハンドルを列挙して、ウインドウハンドルの数が1個増えてれば、ウインドウが
出来たと判断しているのですが、1個増えたことを検出できずに、エラーになっている
ようです。

コンソールを表示するようにしたことで、上記ロジックが機能しなくなったようです。
コードを見直してみます。

川又

--
Fumio Kawamata <fu...@my.email.ne.jp>


高木太郎

unread,
Mar 5, 2009, 8:06:56 PM3/5/09
to Del...@ml.users.gr.jp
 川又さん、こんにちは、イマジオムの高木です。

>>  プロジェクトファイル(*.DPr)の2行目に、{$APPTYPE CONSOLE} の
>> 1行を追加してください。 コンソール画面が現れ、I/Oエラー例外は
>> 発生しなくなります。 これで調べてみてくださいませんか?
>
> こんな記述が出来たんですね。GUIアプリに、{$APPTYPE CONSOLE} を書くと
> いう発想は全然なかったです。

 デバッグに便利なテクニックです。 今後お役立てください。


> 実験結果なのですが、CreateProcess で新たに作成されたExplorerの
> ウインドウハンドルを検出できずにエラーになってしまい、SetWindowPosに
> よりリサイズイベントが発生するかどうかのテストまで至りませんでした
> (コンソールは表示され、Enter と Exitは表示されました。)
>
> クラス名が"ExploreWClass"のウインドウハンドルを列挙してから、
> CreateProcessでExplorerのウインドウを作成し、その後、改めて、
> クラス名が"ExploreWClass"のウインドウハンドルを列挙して、ウインドウ

> ハンドルの数が1個増えてれば、ウインドウが出来たと判断しているのですが、
> 1個増えたことを検出できずに、エラーになっているようです。

 この方法ですと、たまたまユーザが同時にエクスプローラウィンドウを
閉じたり開いたりすると、勝手が悪くなってしまいますよね。

 CreateProcess で子プロセスを作ると、ProcessInformation 引数の中の
dwProcessIdフィールドに子プロセスのプロセス番号が入ります。 そこで
ウィンドウハンドルを列挙したら、ExploreWClass または CabinetWClass
クラスのものを取り出し、さらに GetWindowThreadProcessId で対応する
プロセス番号を割り出して、先の dwProcessId フィールドと一致する
ものを取り出すようにすれば確実です。

 なお再起がかかっていれば、コンソール画面に──

Enter
Enter
Enter
Enter

──のように Enter が複数、あるいは無限に表示されることになります。

Fumio Kawamata

unread,
Mar 6, 2009, 11:19:06 AM3/6/09
to Del...@ml.users.gr.jp
こんばんは。川又です。

長文ですいません。

On Fri, 06 Mar 2009 10:06:56 +0900
aaa1...@pop06.odn.ne.jp (高木太郎) wrote:

> > クラス名が"ExploreWClass"のウインドウハンドルを列挙してから、
> > CreateProcessでExplorerのウインドウを作成し、その後、改めて、
> > クラス名が"ExploreWClass"のウインドウハンドルを列挙して、ウインドウ
> > ハンドルの数が1個増えてれば、ウインドウが出来たと判断しているのですが、
> > 1個増えたことを検出できずに、エラーになっているようです。
>
>  この方法ですと、たまたまユーザが同時にエクスプローラウィンドウを
> 閉じたり開いたりすると、勝手が悪くなってしまいますよね。

>  CreateProcess で子プロセスを作ると、ProcessInformation 引数の中の
> dwProcessIdフィールドに子プロセスのプロセス番号が入ります。 そこで
> ウィンドウハンドルを列挙したら、ExploreWClass または CabinetWClass
> クラスのものを取り出し、さらに GetWindowThreadProcessId で対応する
> プロセス番号を割り出して、先の dwProcessId フィールドと一致する
> ものを取り出すようにすれば確実です。

どうやってCreateProcessしてるかまで書いてなかったですが、実は、Mr. XRAY
さんのページ
http://mrxray.on.coocan.jp/Halbow/Notes/N002.html
に掲載されている、Halbowさんの ExecAndGetWindow 関数(追記[2003.9.24]版)
を使用してます。高木さんが書いてらっしゃることが実装されているように思い
ます。ただし、ExeAppStr:='Notepad.exe'; にして、
hMainWnd:=ExecAndGetWindow(ExeAppStr); を実行した場合は、hMainWndにウイ
ンドウハンドルが代入されるのに対し、ExeAppStr:='Explorer.exe'; とした場合は、
hMainWndの値は0でした。そこで、同じくHalbowさんの、
EnumWindowsOfClassNameAsZOrder を使って、列挙データを取得する方法を考え
た次第です。

procedure TFormMain.ToolButton1Click(Sender: TObject);
var
WA1: THWndArray;
WA2: THWndArray;
Num1: Integer;
Num2: Integer;
ExeAppStr: string;
begin
Num1:=EnumWindowsOfClassNameAsZOrder( WA1, 'ExploreWClass');
(* ↑ CreateProcess実行前に、Explorer.exeのウインドウハンドルを列挙 *)
ExeAppStr:='Explorer.exe /e,/root,C:\';
hMainWnd:=ExecAndGetWindow(ExeAppStr);
(* ↑ CreateProcessして、ウインドウハンドルを取得 *)
sleep(1); (* 実験の残骸 *)
Num2:=EnumWindowsOfClassNameAsZOrder( WA2, 'ExploreWClass');
(* ↑ 改めて、Explorer.exeのウインドウハンドルを列挙 *)
sleep(1); (* 実験の残骸 *)
if Num2=Num1 then begin
ShowMessage('Could not find new instance of the Explorer.');
exit;
end;
hMainWnd:=GetNewHandle(WA1, Num1, WA2, Num2);
(* ↑ 新たに生成されたウインドウハンドルの取得 *)


SetWindowLong(hMainWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, SWP_NOOWNERZORDER);
Windows.SetParent(hMainWnd, PanelExplorer.Handle);
ShowWindow(hMainWnd, SW_SHOW);
end;

function EnumWindowsOfClassNameAsZOrder(var WA:THWndArray; ClassName:string):integer;
var
hWindow:HWND;
begin
WA[0] := 0;
hWindow := GetWindow(Application.Handle,GW_HWNDFIRST);
while (hWindow <> 0) do begin
if (not IsOwnedWindow(hWindow)) and
(UpperCase(GetClassNameStr(hWindow)) = UpperCase(ClassName)) then
(* Original codes were
* if (not IsOwnedWindow(hWindow)) and
* IsWindowVisible(hWindow) and
* (UpperCase(GetClassNameStr(hWindow)) = UpperCase(ClassName)) then
*)
begin
WA[0] := WA[0]+1;
WA[WA[0]] := hWindow
end;
hWindow := GetWindow(hWindow,GW_HWNDNEXT);
end;
result := WA[0];
end;

実は、新たな問題が発生して、再帰しているかどうかの確認につまづいてます。

プロジェクトに、{$APPTYPE CONSOLE} を追加すると、Explorerを複数起動しているにも
関わらず、それを検出できずに、EnumWindowsOfClassNameAsZOrder関数が0を返します。

これは、なぜなのでしょうか?

--
Fumio Kawamata <fu...@my.email.ne.jp>


Fumio Kawamata

unread,
Mar 8, 2009, 8:10:32 AM3/8/09
to Del...@ml.users.gr.jp
川又です。

On Sat, 07 Mar 2009 01:19:06 +0900
Fumio Kawamata <fu...@my.email.ne.jp> wrote:

> 実は、新たな問題が発生して、再帰しているかどうかの確認につまづいてます。
>
> プロジェクトに、{$APPTYPE CONSOLE} を追加すると、Explorerを複数起動しているにも
> 関わらず、それを検出できずに、EnumWindowsOfClassNameAsZOrder関数が0を返します。
>
> これは、なぜなのでしょうか?

これは後回しにして、

>  もし再帰的に呼び出されているのであれば、子ウィンドウの SetWindowPos の
> 呼び出しによって OnResize が呼び出される仕組みになっているということ
> ですから、OnResize から SetWindowPos を呼ぶのはあきらめた方がいいと
> 思います。 この場合、短周期のタイマイベントか何かで SetWindowPos を
> 呼ぶようにし、その発火条件を OnResize イベントの中で判定するように
> するのがいいと思います。

これを試すために、以下のコードを書いてみました。

var
PanelWidth: Integer;
PanelHeight: Integer;

Resized: Boolean;

procedure TFormMain.FormCreate(Sender: TObject);
begin
Resized:=False;
TimerInterval.Enabled:=True;
end;

procedure TFormMain.FormResize(Sender: TObject);
begin
PanelWidth:=PanelExplorer.Width;
PanelHeight:=PanelExplorer.Height;
Resized:=True;
end;

procedure TFormMain.TimerIntervalTimer(Sender: TObject);
begin
if Resized=True then begin
Resized:=False;
SetWindowPos(hMainWnd, 0, 0, 0, PanelWidth, PanelHeight, 0);
end;
end;

結果は、フォームの端をドラッグしてリサイズしている途中に、OnTimer
イベントが発生すると、ドラッグする前のフォームサイズに戻ってしまい
ました。

OnResizeイベントを一切利用しないようにしたらどうなるか、マウスポインター
がExplorerのウインドウ上にある場合で、なおかつ、Explorerのサイズと、
Explorerの親ウインドウであるTPanerlのサイズが異なっている場合に、
Explorerのサイズを変更するようにしてみたのですが、これは、意図した
動作はしました。
(本当にやりたいこととは違いますが、最悪はこれで妥協するしかないかと
思ってます。)

procedure TFormMain.FormCreate(Sender: TObject);
begin
TimerInterval.Enabled:=True;
(* Intervalは 100msに設定 *)
end;

procedure TFormMain.TimerIntervalTimer(Sender: TObject);
var
P: TPoint;
R: TRect;
PointedWnd: HWnd;
MostTopLevelWnd: HWnd;
begin
GetCursorPos(P);
PointedWnd:=WindowFromPoint(P);
MostTopLevelWnd:=GetMostTopLevelWindow(PointedWnd);
(* GetMostTopLevelWindow は、Halbowさん作の、最上位のトップレベルウィンドウを取得する関数
* http://mrxray.on.coocan.jp/Halbow/VCL06.html
*)
if (MostTopLevelWnd=hMainWnd) or (PointedWnd=PanelExplorer.Handle) then begin
GetWindowRect(hMainWnd, R);
if (PanelExplorer.Width<>(R.Right-R.Left+1))
or(PanelExplorer.Height<>(R.Bottom-R.Top+1)) then begin


SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, SWP_NOOWNERZORDER);

end;
end;
end;

--
Fumio Kawamata <fu...@my.email.ne.jp>


高木太郎

unread,
Mar 8, 2009, 9:08:32 AM3/8/09
to Del...@ml.users.gr.jp
 川又さん、こんばんは。 イマジオムの高木です。

 TFormMain.OnResize が実行されてから、子ウィンドウである Panel-
Explorer のサイズが変わるまでの間に、タイマメッセージが処理される
機会があるからですね。 それにしても Explorer だけでなくメイン
フォームのリサイズまで中断されるというのは、やはり SetWindowPos を
呼び出すと、メインフォームのリサイズがなされるメカニズムになっている
(つまりリサイズの再帰呼び出しがある)ような気がします。 

 FormResize を TFormMain.OnResize のハンドラとして設定するのでなく、
PanelExplorer.OnResize のハンドラにしてみてはいかがですか? また
Resize 変数を使わなくても、TTimer.Enabled だけで目的は果たせます。

procedure TFormMain.PanelExplorerResize(Sender: TObject);
begin
TimerInterval.Enabled:=True;
end;

procedure TFormMain.TimerIntervalTimer(Sender: TObject);
begin
TimerInterval.Enabled:=False;
SetWindowPos(hMainWnd, 0, 0, 0,
PanelExplorer.Width, PanelExplorer.Height, 0);
end;

 このような「手が空いたら何かやらせる」という処理は、PostMessage
APIと WndProc オーバライド手続きを使っても実現できますが、TTimer を
使うとこのように大変お手軽に作れます。 TTimer.Interval=1 とすると、
性能上もほとんど引けを取りません。 よく使うテクニックですので、ぜひ
覚えてご活用ください。

Fumio Kawamata

unread,
Mar 10, 2009, 8:32:25 AM3/10/09
to Del...@ml.users.gr.jp
こんばんは。川又です。

On Sun, 08 Mar 2009 22:08:32 +0900
aaa1...@pop06.odn.ne.jp (高木太郎) wrote:

> 機会があるからですね。 それにしても Explorer だけでなくメイン
> フォームのリサイズまで中断されるというのは、やはり SetWindowPos を
> 呼び出すと、メインフォームのリサイズがなされるメカニズムになっている
> (つまりリサイズの再帰呼び出しがある)ような気がします。 

再帰の場合、リサイズが繰り返し発生して、ハングアップしたようにならない
ものなのでしょうか? (現状、ハングアップはしていません。)

>  FormResize を TFormMain.OnResize のハンドラとして設定するのでなく、
> PanelExplorer.OnResize のハンドラにしてみてはいかがですか? また
> Resize 変数を使わなくても、TTimer.Enabled だけで目的は果たせます。

これを試したところ、以下のようになりました。
TTimer.Intervalは、2000に設定しました。
(TTimer.Intevalを1とか、100とかにすると、動作検証はできませんでした。)
PanelExplorerはのAlignプロパティは、alClientに設定してあります。

1. 自前フォームを縮めるようにドラッグしてリサイズした場合
(1)自前のフォームがExplorerのフォームを覆うようになる。まだ、
Explorerのサイズは変わっていない。(正常)
(2)ドラッグの最中、TTimer.Intervalの設定値とおぼしき秒数経過後、
自前フォームがリサイズ前の大きさに戻るが、Explorerのサイズは
縮まったサイズに変わる。TPanelが見える状態になる。Explorerは、
ちゃんとリサイズされている。
(3)TTimer.Intervalの設定時間経過後、Explorerのフォームサイズが、
自前フォームのサイズになる。(onResizeイベント処理で
TTimer.Enabled:=True; を実行しているのでこれは正常)

2. 自前フォームを広げるようにドラッグしてリサイズした場合。
(11)Explorerのサイズはそのままなので、TPanelが見える状態になる。(正常)
(12)ドラッグの最中、TTimer.Intervalの設定値経過後、自前フォームが
リサイズ前の状態に戻る。ただし、Explorerの一部は隠れた状態になり、
Explorerは、どうやら、ちゃんと、自前フォームが元のサイズに戻る前の
値に合わせて拡大された模様。
(13)TTimer.Intervalの設定時間経過後、Explorerのフォームサイズが、
自前フォームのサイズになる。(onResizeイベント処理で
TTimer.Enabled:=True; を実行しているのでこれは正常)

TPanel.onResizeイベントでTTimerがEnabledになって、所定秒数経過後、
SetWinodwsPos()は実行されて成功しているが、TPanelのリサイズはキャンセル
されているように思います。

マイクロソフトは、以下のように書いてますが、Resize Eventはちゃんと
fire しているような挙動を示しています。(もっとも、自分がリサイズしよ
うとしているのはUserControlではなく、他のアプリケーションのウインドウ
なので、厳密には、以下は当てはまらないのかもしれません。)

> 引用
> SYMPTOMS
> The Resize Event of a UserControl will not fire if you use the
> MoveWindow API function to resize the window of the UserControl.
> NOTE: Other API calls that manipulate the UserControl window,
> such as SetWindowPos, may also show this behavior.

今のところ、onResize に関連して、SetWindowPos を実行するのはことごとく
失敗しています。

実現不可能であることが分かれば、それはそれで諦めがつくのですが...。

# Delphiというより、Windows APIの話になってしまってすみません。

--
Fumio Kawamata <fu...@my.email.ne.jp>


高木太郎

unread,
Mar 10, 2009, 9:28:31 AM3/10/09
to Del...@ml.users.gr.jp
 川又さん、こんばんは。 イマジオムの高木です。

> 再帰の場合、リサイズが繰り返し発生して、ハングアップしたように
> ならないものなのでしょうか? (現状、ハングアップはしていません。)

 メッセージによる再帰の場合、ハングアップしないこともあります。
関数呼び出しの再帰だと、確実にハングアップしますが。


>>  FormResize を TFormMain.OnResize のハンドラとして設定するのでなく、
>> PanelExplorer.OnResize のハンドラにしてみてはいかがですか?
>

> これを試したところ、以下のようになりました。
> TTimer.Intervalは、2000に設定しました。
> (TTimer.Intevalを1とか、100とかにすると、動作検証はできませんでした。)
> PanelExplorerはのAlignプロパティは、alClientに設定してあります。
>
> 1. 自前フォームを縮めるようにドラッグしてリサイズした場合
> (1)自前のフォームがExplorerのフォームを覆うようになる。まだ、
> Explorerのサイズは変わっていない。(正常)
> (2)ドラッグの最中、TTimer.Intervalの設定値とおぼしき秒数経過後、
> 自前フォームがリサイズ前の大きさに戻るが、Explorerのサイズは
> 縮まったサイズに変わる。TPanelが見える状態になる。Explorerは、
> ちゃんとリサイズされている。
> (3)TTimer.Intervalの設定時間経過後、Explorerのフォームサイズが、
> 自前フォームのサイズになる。(onResizeイベント処理で
> TTimer.Enabled:=True; を実行しているのでこれは正常)
>
> 2. 自前フォームを広げるようにドラッグしてリサイズした場合。
> (11)Explorerのサイズはそのままなので、TPanelが見える状態になる。
>    (正常)
> (12)ドラッグの最中、TTimer.Intervalの設定値経過後、自前フォームが
> リサイズ前の状態に戻る。ただし、Explorerの一部は隠れた状態に

>    なり、Explorerは、どうやら、ちゃんと、自前フォームが元の


>    サイズに戻る前の値に合わせて拡大された模様。
> (13)TTimer.Intervalの設定時間経過後、Explorerのフォームサイズが、
> 自前フォームのサイズになる。(onResizeイベント処理で
> TTimer.Enabled:=True; を実行しているのでこれは正常)
>
> TPanel.onResizeイベントでTTimerがEnabledになって、所定秒数経過後、
> SetWinodwsPos()は実行されて成功しているが、TPanelのリサイズは
> キャンセルされているように思います。

 そうですね。 「ドラッグ中に SetWindowPos が実行されることによって、
TPanel のリサイズがキャンセルされている」というのが、最も的確に
事象を表しているようです。 そうだとすると、Windows APIの問題と
いうより、やはり Delphi の話題ですね。

 乗りかかった船なので、私の方でも実験してみようと思います。 少し
時間をいただけますか。

Fumio Kawamata

unread,
Mar 10, 2009, 10:31:04 AM3/10/09
to Del...@ml.users.gr.jp
高木さん、こんばんは。川又です。

On Tue, 10 Mar 2009 22:28:31 +0900
aaa1...@pop06.odn.ne.jp (高木太郎) wrote:

>  メッセージによる再帰の場合、ハングアップしないこともあります。
> 関数呼び出しの再帰だと、確実にハングアップしますが。

そうなんですか。再帰だとしたら、なぜ、ハングアップしないのか
不思議に思ってました。

>  そうですね。 「ドラッグ中に SetWindowPos が実行されることによって、
> TPanel のリサイズがキャンセルされている」というのが、最も的確に
> 事象を表しているようです。 そうだとすると、Windows APIの問題と
> いうより、やはり Delphi の話題ですね。
>
>  乗りかかった船なので、私の方でも実験してみようと思います。 少し
> 時間をいただけますか。

ありがたいお言葉です。急かすつもりは毛頭ございません。時間があるときで
結構ですし、実験中止となってもお気になさらないでください。

最終的に、「出来ません」となっても、それはそれで、当方にとっては有益な
情報です。(今は、自分の能力不足なのか、そもそも実現不能なのか区別がつか
ず、悶々としている状況ですので。)

高木太郎

unread,
Mar 11, 2009, 5:25:11 AM3/11/09
to Del...@ml.users.gr.jp
 川又さん、こんにちは。 イマジオムの高木です。

>>  乗りかかった船なので、私の方でも実験してみようと思います。 少し
>> 時間をいただけますか。

 やってみました。 その結果、次のようにしたら、うまいこと Panel に
Internet Explorer のウィンドウがはまってくれました。

作成時
Windows.SetParent(WindowHandle,Panel.Handle);
SetWindowLong(WindowHandle,GWL_STYLE,WS_CHILD or WS_VISIBLE);
MoveWindow(WindowHandle,0,0,Panel.Width,Panel.Height,True);

OnResize イベント時
MoveWindow(WindowHandle,0,0,Panel.Width,Panel.Height,True);

最小化、最大化、リサイズとも問題なさそうです。

 どうも SetWindowLong で WS_CHILD を指定しないと、座標計算がデスク
トップに対してなされるような挙動でした。 今回は Panel のローカル
座標で位置を指定したいわけなので、それではまずいわけです。

 テストプログラムをご希望でしたら、お送りいたします。 川又さん
以外の方も、ご興味がありましたらどうぞ。

 時々IEのウィンドウが正しくない位置に表示されることがありましたが、
対処はそんなに難しくなさそうです。 Resize を呼び出してやると、すぐ
正しい位置に戻りますので。

 IEの代わりにメモ帳やペイントでもやってみました。 ペイントは問題
なく動作しましたが、メモ帳だと最小化がうまくいかず、またリサイズ後に
入力を受け付けなくなってしまいました。 もうちょっと工夫が必要な
ようです。

Fumio Kawamata

unread,
Mar 11, 2009, 8:51:02 AM3/11/09
to Del...@ml.users.gr.jp
高木さん、こんばんは。川又です。

出来ました!出来ました!

On Wed, 11 Mar 2009 18:25:11 +0900
aaa1...@pop06.odn.ne.jp (高木太郎) wrote:

>  やってみました。 その結果、次のようにしたら、うまいこと Panel に
> Internet Explorer のウィンドウがはまってくれました。
>
> 作成時
> Windows.SetParent(WindowHandle,Panel.Handle);
> SetWindowLong(WindowHandle,GWL_STYLE,WS_CHILD or WS_VISIBLE);
> MoveWindow(WindowHandle,0,0,Panel.Width,Panel.Height,True);
>
> OnResize イベント時
> MoveWindow(WindowHandle,0,0,Panel.Width,Panel.Height,True);

何通りか試してみましたが、

SetWindowLong(hMainWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, SWP_NOOWNERZORDER);
Windows.SetParent(hMainWnd, PanelExplorer.Handle);
ShowWindow(hMainWnd, SW_SHOW);

のように、WS_CHILDを指定しなくてもOKでした。WS_CHILDの要・不要が、
命令の実行順序に依存するのかも...と思って、

Windows.SetParent(hMainWnd, PanelExplorer.Handle);
SetWindowLong(hMainWnd, GWL_STYLE, WS_POPUP);
SetWindowPos(hMainWnd, 0, 0, 0, PanelExplorer.Width, PanelExplorer.Height, SWP_NOOWNERZORDER);
ShowWindow(hMainWnd, SW_SHOW);

としてみましたが、これもOKでした。

何日もあがいていたので、自前のフォームの中のExplorerが、自前のフォーム
のリサイズに応じてサイズ変更されるのを見て、実に感激です。意味もなく、
ずりずりリサイズしています。

お忙しいなか、実験していただいて、本当にありがとうございました。

--
Fumio Kawamata <fu...@my.email.ne.jp>


Reply all
Reply to author
Forward
0 new messages