- PR -

MDI子フォームで自分自身をアクティブにしたい

投稿者投稿内容
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2007-06-22 00:07
情報共有のためクロスサイトポストをリンクします。
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=1763542&SiteID=7

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-06-22 04:30
どのような状況でこういった要望があるのかわからないのですが、
ダッチさんのご意見が正しい方法であると思います。

この問題を正確に考えようとするなら、
WM_MDIACTIVATE、WM_SETFOCUS等のメッセージ処理の詳細と、.Netでの扱い、
およびそれらとEnter、GotFocus、Activated等のイベントとの関連を
知らなくてはいけません。

引用:

あるMDI子フォームがDeactivateイベントを拾ったときに、
その子フォームの状態応じて、自分自身を再度アクティブに
したいのですがうまくいきません。



DeactivateイベントはWM_MDIACTIVATEメッセージ処理中に呼ばれ、
その時その子ウィンドウはまだActiveです。
ですので、Activateを呼んだり、WM_MDIACTIVATEを発行しても、意味がありません。
一度Not-Activeな状態になった後、もう一度Activateしたいなら、
Activeな子ウィンドウが決定した後にしなければなりません。
イベントではMDIChildActivateイベントが相当します。
これはまどかさんのおっしゃっている通りです。

どんなイベントのとき、どの子ウィンドウをアクティブにするかは
.Net Frameworkではなく、DefWindowProcが決定します。
さまざまなメッセージがDefWindowProcに渡され、
その結果WM_MDIACTIVATEメッセージが発行されるからです。
DefWindowProcは様々なイベントでWM_MDIACTIVATEを発行しますので、

引用:

結果、該当する子フォームのDeactivateイベントが2回入ってきてしまいます。
ちなみにWindowリストから別のフォームを選択したときは1回しか入ってきません。



このように、時と場合によっていろいろになり得ます。
この場合はマウスのダウンとアップでActivateされてるんでしょう。
この振る舞いはOSの違いやウィンドウマネージャの設定によって変わる可能性があります。
ですので、原理的にはWndProcをオーバーライドして2回呼ばれたりするのを防げますが、現実的ではありません。
(MDIClientクラスを変更できるなら、WM_MDIACTIVATEを誤魔化すことも可能なのですが、.Net Frameworkでは変更できないので無理です。)

このように、MDIのDeactivate時に際ActivateはWin32レベルでできない仕様になっています。
これは、「アクティブなままにしたい」という要望の解として、
「再アクティブ化する」よりも、「非アクティブ化できない」というほうが、
パフォーマンス上も、プログラム上も、デザイン上も、都合がいいからです。

Activeと関連するものに、Focusがあります。
Focusのあるコントロールの所属するWindowがActiveになるという関係です。
FocusはコントロールレベルでのActiveである、と思えばわかりやすいと思います。

Win32で非アクティブにしたくない場合は、Focusを離さないという手法を使います。
これにより「非アクティブ化」できなくします。

このパターンはたくさん使うので、.NetやMFCでは標準で組み込まれています。
.NetではValidateがそれにあたります。
コントロールの検証とか書いてありますが、
ValidateイベントがWM_SETFOCUSメッセージ処理中に呼ばれることからもわかるように、
「Focusを離せるかどうか」を意味していると場合によっては考えられます。


つまり、どうしても非アクティブ化されたものをすぐにアクティブ化したいなら、
.Net使わずにWin32だけで書いたり、MDIClientを強引に変更したり、
Timerつかって時間差でやったり、ヘンテコな事をしないと無理。
もう一つの戦略、「非アクティブ化」しない、というのをとるなら、
別に自分で実装してもいいけど、Validatingイベントが実装されてるので、
それを使えば簡単、ということです。

まどかさん、ダッチさんのご意見となんら変わりませんが。

郷に入りては、という奴で、
WindowsFormを使うなら使うなりの、行儀があって、逆らうと痛いですよ。

pine
会議室デビュー日: 2004/11/10
投稿数: 10
投稿日時: 2007-06-22 11:31
ダッチさんれいさんありがとうございます。

引用:

どのような状況でこういった要望があるのかわからないのですが



状況としては、このアプリはある機器との接続を行っており、その機器からの
パラメータを読み出して設定する画面がこの画面です。設定を変更して、送信
ボタンを押さずにこの画面から別の画面に移るときに、変更したデータを
破棄するかどうかを聞く様にしています。破棄しない場合は、非アクティブ化
を防ぎたいわけです。

確かにValidatingが正しいように思い、DeactivateでなくてValidatingで
条件が成立したときにe.Cancel=Trueとしたのですが、非アクティブになって
しまいます。ValidatingでCancelしても非アクティブ化は防げないようです。

ただ、ValidatingでMe.Activate()を呼び出すことはできました。しかし、
もう一度Validatingが入ってきてしまいます。おそらく、アクティブにしようとした
別のウィンドウのアクティブ化が終わっていないためでしょうね。
まあ、親ウィンドウで変な処理をしなくて済むようにはなりましたが…。
まどか
ぬし
会議室デビュー日: 2005/09/06
投稿数: 372
お住まい・勤務地: ますのすし管区
投稿日時: 2007-06-22 13:38
引用:

状況としては、このアプリはある機器との接続を行っており、その機器からの
パラメータを読み出して設定する画面がこの画面です。設定を変更して、送信
ボタンを押さずにこの画面から別の画面に移るときに、変更したデータを
破棄するかどうかを聞く様にしています。破棄しない場合は、非アクティブ化
を防ぎたいわけです。


処理の性質がわかりませんが、上記が一つの一連の操作であればモーダルダイアログにできないのでしょうか?

MDIは一つのジャンルになっているように意味があり、MDI子フォームはユーザーが自由に操作でき
基本的に親フォームおよび他のMDI子ウィンドウ間で処理は独立しているべきです。
#一つのきっかけ(基データなど)により「連動」はしても、「連携」はすべきかどうかを検討する必要があるという意味
#それぞれが開くのも閉じるのも自由だから
そのような点から今回の対象フォームがMDI子フォームである必要があるかどうかを検討されてはと思います。
それがだめでも、書かれた内容を基に考えると
タイミングが重要でないなら、YesNoを応答することなくアプリが終了しなければよいことを満たせばどうかとも思います。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-06-22 14:44

引用:

確かにValidatingが正しいように思い、DeactivateでなくてValidatingで
条件が成立したときにe.Cancel=Trueとしたのですが、非アクティブになって
しまいます。ValidatingでCancelしても非アクティブ化は防げないようです。



おかしいですね。
MDIの子ウィンドウでValidatingイベントで
e.Cancel=Trueとすると
フォーカス遷移を防げる=非アクティブ化を防げるはずです。

MDI子フォームのAutoValidateプロパティは
EnablePreventFocusChangeになっていますか?
それともなにか特殊なことをしていますか?

pine
会議室デビュー日: 2004/11/10
投稿数: 10
投稿日時: 2007-06-22 15:15
引用:

おかしいですね。
MDIの子ウィンドウでValidatingイベントで
e.Cancel=Trueとすると
フォーカス遷移を防げる=非アクティブ化を防げるはずです。

MDI子フォームのAutoValidateプロパティは
EnablePreventFocusChangeになっていますか?
それともなにか特殊なことをしていますか?




EnablePreventFocusChangeになっています。
ヘルプを見てもキャンセルしたらフォーカスは移らないと書いてありますね。
何か条件でもあるのでしょうか?MDI子フォームは別とか?

追記:-------------------------

「Visual Basic .NET 逆引き大全 500の極意」についていたMDIのサンプルで
子フォームのValidating処理を常にe.Cancel=Trueとしてみましたが同じでした。
Validatingイベントには飛んできています。

ただ、親フォームを閉じようとすると当然閉じられないのですが、その後、
子フォームのフォーカスは一時的に移らないようになりました。一時的にというのは
最大化とか最小化とかするとフォーカスはまた移るようになってしまいます。

何かバグっぽい感じがするのですが…。

[ メッセージ編集済み 編集者: pine 編集日時 2007-06-22 16:00 ]
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-06-22 15:46

>何か条件でもあるのでしょうか?MDI子フォームは別とか?

いえいえ。
MDI子フォームで、確認しています。
普通はそれでうまく動作します。
しなかったという話は知りません。

pineさんのプログラムでなにかやっているのではないかと思います。

むしろ私が知りたいです。
なにをしていますか?
いったい何をしたらCancelをCancelできるんですか?
pine
会議室デビュー日: 2004/11/10
投稿数: 10
投稿日時: 2007-06-22 16:15
引用:

MDI子フォームで、確認しています。
普通はそれでうまく動作します。
しなかったという話は知りません。

pineさんのプログラムでなにかやっているのではないかと思います。



ほかに特殊なことはしていません。というか、先の返信に修正で書き加えたのですが
「逆引き500」についていたMDIサンプルの子ウィンドウにValidatingイベント処理を
追加して、無条件にe.Cancel=Trueとしてもだめでした。こんなことすると親も閉じれ
なくなりますが、親を閉じようとした後、フォーカスの動きが一時的に正常になりま
した。でも、子フォームを最大化したりするとまた同じ状態に戻ってしまいます。

VS2005はSP1を当ててるんですが、そういうこととか関係してるんでしょうか?

スキルアップ/キャリアアップ(JOB@IT)