トップ回答者
System.Diagnostics.Processについて

質問
-
System.Diagnostics.Processを使用して、AというプログラムからAを表示させたままBを起動させ、
Bを閉じたらAに制御が戻るような仕組みを作っています。Aを表示させたままBを起動させるのは問題なくできたのですが、Bを表示している時に、AにあるBを呼び出すボタンをクリック
すると、Bを閉じた後すぐに、またBが起動してしまいます。(画面上ではAにあるボタンは動作してないように見えています)この動作を制御しようとして、AのフォームをBの呼び出し直前にEnabled=Falseと設定し、Bから復帰後にEnabled=Trueと
設定してみて、これも上手く制御出来たようですが、何度か繰り返してテストしていると、Aに復帰した際に、他のウインドウの背面
に移動してしまう場合があるという事が発覚しましいた。これも制御出来ないかと思い、Bからの復帰直後のEnabled=Trueの後に、一旦TopMost=Trueを設定してから、
直ぐにFalseを設定し直すようにしてみましたが、改善されませんでした。何か良い改善策はないでしょうか?
<環境>
Windows XP SP3
Visual Basic 2010 SP1
.NET Framework 4
回答
-
色々と、EnabledやTopMost,DoEvents()の処理位置を試してみて、以下のようにしたら、上手く動きました。
------------------------------------------------------------------
Private Sub Form1_Load
Me.TopMost = True
Me.TopMost = False
End SubPrivate Sub Button1_DropDown
Me.Enabled = False
Process1.StartInfo.FileName = "xxx.exe"
Process1.StartInfo.Arguments = "frm_yyy"
Process1.Start()
End SubPrivate Sub Process1_Exited
Me.TopMost = True
Application.DoEvents()
Me.Enabled = True
~~~ (他の処理) ~~~
Me.TopMost = False
End Sub
------------------------------------------------------------------LoadイベントにTopMostを入れたのは、それを入れない場合、何故か初回のBの呼び出し時だけ、
Aが最前面にいるにも関わらず、他のウインドウにコントロールが移っていたからです。(何故なんでしょう?)
皆さんのご意見ご指摘では、お薦めではないとの事ですが、これで良しとします。
ありがとうございましいた。- 回答としてマーク zippo38 2011年10月21日 0:48
すべての返信
-
返信ありがとうございます。
ですが、この現象は、System.Diagnostics.Processと無関係なのでしょうか?
確かにウインドウの重なり順の問題だとは思いますが、System.Diagnostics.Processを使用してBを呼び出しており、
Bの呼び出し直前はAが最前面なのですから、Bが更にその前面に出たとしても、Bが閉じればAが最前面になるものなの
ではないでしょうか?
同一のexeでフォーム間の移動であれば、この現象は発生しないように思います。TopMostにつきましても、これを設定する前の時点で背面に移動するという現象になっていますので、
裏目に出ているわけではないと思います。私の認識に誤りがあれば、教えてください。
-
Bを呼び出した後のAの状態維持に問題があると思いますよ。
呼び出しそのものは、Process.Start だと思いますが、そのあとどうしていますか?Bが終了するまでの間のAの待機状況によっては、最初にあるようにBが動いている最中にボタンを押したらBの終了後にボタンが押された状態が発生した。ということが起こりえます。
ただ、それ自体は、System.Diagnostics.Process クラスの問題ではありませんが。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/ -
返信ありがとうございます。
Bの呼び出しには、以下のコードを記述しています。
Process1.StartInfo.FileName = "xxx.exe"
Process1.StartInfo.Arguments = "frm_yyy"
Process1.Start()
Process1.WaitForExit()
ただ、このままだと、ご指摘の通りの現象が発生しましたので、
Me.Enabled = False
Process1.StartInfo.FileName = "xxx.exe"
Process1.StartInfo.Arguments = "frm_yyy"
Process1.Start()
Process1.WaitForExit()
Me.Enabled = True
で対処しました。
(呼び出しボタンのみを使用不可にしても改善されませんでした)
これしか思い付かなかったのですが、他の方法があれば教えてください。 -
どちらにしても、B(xxx.exe)を実行している間、呼び出し元のAが「応答なし」状態になっています。
いくつかパターンはありますが、一番変更範囲が少なそうなのは、Process.Exited イベントを利用する方法だと思います。
幸いにもProcessクラスをメンバーオブジェクトとして持っていますので、WaitForExit でその場待機ではなく、非同期で Exited イベントを受け取れば、うまくいくと思います。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/ -
操作できなければいいのであれば、Me.Enabled = False ではなく、Form1(アプリケーションのメインウィンドウ) のインスタンスに対して Enabled = False 行うなどで対応可能です。
ポイントは
- UIを処理するスレッドは、いかなる場合も止めない(かならず、メソッドやプロパティは作業を終えたら終了する)
- 別のスレッドやプロセスを呼び出した場合、その終了処理は非同期に行い、同一メソッド内で完結させようとしない
です。
では、どうすればいいのか?今回の場合「プロセスBが動いている間は、自分自身はユーザーからの入力は受け付けない」ということを実現すればいいわけです。そのためには「アプリケーションA全体をユーザーからの入力を受け付けない状態にすればいい」のです。
試してみてください。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/- 編集済み とっちゃんMVP 2011年10月20日 8:01 タグがおかしくなっていたので修正
-
Me がだれなのかはこれまで一度も触れていないと思いますが?
Enabled プロパティはSystem.Windows.Control クラスから派生してるウィンドウオブジェクトなら全部持っているので、それがメインフォームかどうかはソースを見ていない外部の人間にはわかりかねます。
ま、それはともかくとして、Me がメインフォームだったとしても、WaitForExit メソッドは、UIを止めてしまいます。
ですので、その呼び出し処理の前後でEnabledを呼び出しても意味を成しません。
形としては、プロセスを実行するメソッド(今回はイベントハンドラのようですが)で、Me.Enabled = False として一度メソッドを終了し、
しかるべきタイミング(Exitedイベント)で、Me.Enabled = True とすることで、ユーザーにはあたかも待機しているかの如くに見えるようにするという形をとります。
騙されたと思って試してみてください。すっきり行くかどうかはプログラム次第な部分はありますが、マシンに負荷をかけずに確実に待機状態を表現してくれますよ。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/ -
たぶん、Me.Enabled = false をしている時点で、アクティブにならない(背面に回る)不具合は回避不可能では。
WaitForExit をやめたとしても、タイミング問題で以下のような挙動をしそうです。(確証はありませんが)B が終わったときに A をアクティブにしようとする動きが生じるが、A は Enabled = false なのでアクティブにできない。なので、次のウィンドウをアクティブにするという動きをしているように見受けられます。
もし、この挙動をしているのなら、Me.Enabled = false じゃなくて、 Button1.Enabled = false; のように、フォーム自体を無効化するのではなく、配置しているコントロールを無効化する方向で考えた方がよいと思います。
質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。 -
UIスレッドをブロックしてしまうやり方はそもそも望ましくないので別の方法にする方がよいですが、とりあえずEnabled=Falseが効かない原因だけ書いておきます。
Me.Enabled = False
Process1.StartInfo.FileName = "xxx.exe"
Process1.StartInfo.Arguments = "frm_yyy"
Process1.Start()
Process1.WaitForExit()
Me.Enabled = True
UIスレッドをブロックしている状態のため、この間にボタンを押す操作をすると、次にUIスレッドがメッセージポンプを処理した時点でボタンを押したメッセージが処理されます。
これは通常、このイベントハンドラから抜けたタイミングであり、その時点ではEnabled=Trueに戻された後ですから、ボタンを押した処理は普通に実行されてしまいます。最後の
Me.Enabled = True
の前に、Application.DoEvents() を実行すると、Enabled=Falseの状態の時点でボタンを押したメッセージが処理されるため、ボタンを押した処理は無効になり実行されません。Me.Enabled = False
Process1.StartInfo.FileName = "xxx.exe"
Process1.StartInfo.Arguments = "frm_yyy"
Process1.Start()
Process1.WaitForExit()
Application.DoEvents()
Me.Enabled = Trueこんな感じです。
ただし、この方法をおすすめしているわけではありません。
-
色々と、EnabledやTopMost,DoEvents()の処理位置を試してみて、以下のようにしたら、上手く動きました。
------------------------------------------------------------------
Private Sub Form1_Load
Me.TopMost = True
Me.TopMost = False
End SubPrivate Sub Button1_DropDown
Me.Enabled = False
Process1.StartInfo.FileName = "xxx.exe"
Process1.StartInfo.Arguments = "frm_yyy"
Process1.Start()
End SubPrivate Sub Process1_Exited
Me.TopMost = True
Application.DoEvents()
Me.Enabled = True
~~~ (他の処理) ~~~
Me.TopMost = False
End Sub
------------------------------------------------------------------LoadイベントにTopMostを入れたのは、それを入れない場合、何故か初回のBの呼び出し時だけ、
Aが最前面にいるにも関わらず、他のウインドウにコントロールが移っていたからです。(何故なんでしょう?)
皆さんのご意見ご指摘では、お薦めではないとの事ですが、これで良しとします。
ありがとうございましいた。- 回答としてマーク zippo38 2011年10月21日 0:48