none
System.Diagnostics.Processについて RRS feed

  • 質問

  • 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

    2011年10月20日 6:19

回答

  • 色々と、EnabledやTopMost,DoEvents()の処理位置を試してみて、以下のようにしたら、上手く動きました。

    ------------------------------------------------------------------
    Private Sub Form1_Load
        Me.TopMost = True
        Me.TopMost = False
    End Sub

    Private Sub Button1_DropDown
        Me.Enabled = False
        Process1.StartInfo.FileName = "xxx.exe"
        Process1.StartInfo.Arguments = "frm_yyy"
        Process1.Start()
    End Sub

    Private 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
    2011年10月21日 0:48

すべての返信

  • 改善策の前に原因を明確にするべきです。

    質問タイトルのSystem.Diagnostics.Processは無関係ですよね? ウィンドウの重なり順に依存している問題のように思います。この点に注意してどういう状況の時に背面に移動するのかを確認しましょう。

    # TopMostとか変な小細工をやっているので裏目に出て背面に移動してしまっているのだと思いますが…。

    2011年10月20日 6:33
  • 返信ありがとうございます。

    ですが、この現象は、System.Diagnostics.Processと無関係なのでしょうか?

    確かにウインドウの重なり順の問題だとは思いますが、System.Diagnostics.Processを使用してBを呼び出しており、
    Bの呼び出し直前はAが最前面なのですから、Bが更にその前面に出たとしても、Bが閉じればAが最前面になるものなの
    ではないでしょうか?
    同一のexeでフォーム間の移動であれば、この現象は発生しないように思います。

    TopMostにつきましても、これを設定する前の時点で背面に移動するという現象になっていますので、
    裏目に出ているわけではないと思います。

    私の認識に誤りがあれば、教えてください。

    2011年10月20日 6:44
  • Bを呼び出した後のAの状態維持に問題があると思いますよ。

    呼び出しそのものは、Process.Start だと思いますが、そのあとどうしていますか?Bが終了するまでの間のAの待機状況によっては、最初にあるようにBが動いている最中にボタンを押したらBの終了後にボタンが押された状態が発生した。ということが起こりえます。

    ただ、それ自体は、System.Diagnostics.Process クラスの問題ではありませんが。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2011年10月20日 7:02
  • 返信ありがとうございます。

    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

    で対処しました。
    (呼び出しボタンのみを使用不可にしても改善されませんでした)

    これしか思い付かなかったのですが、他の方法があれば教えてください。

    2011年10月20日 7:12
  • どちらにしても、B(xxx.exe)を実行している間、呼び出し元のAが「応答なし」状態になっています。

    いくつかパターンはありますが、一番変更範囲が少なそうなのは、Process.Exited イベントを利用する方法だと思います。

    幸いにもProcessクラスをメンバーオブジェクトとして持っていますので、WaitForExit でその場待機ではなく、非同期で Exited イベントを受け取れば、うまくいくと思います。

     

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2011年10月20日 7:28
  • ご指摘の通り、非同期であれば問題は無いのですが、Bを呼び出し中は、Aを操作出来ないようにしておく必要があり、
    WaitForExitを使っています。

    同期をとりながら上手く制御する方法はありませんか?

    2011年10月20日 7:45
  • 非表示にしてしまうとか…?

    そもそもなぜ別プロセスなのかよくわかりません…。

    2011年10月20日 7:57
  • 操作できなければいいのであれば、Me.Enabled = False ではなく、Form1(アプリケーションのメインウィンドウ) のインスタンスに対して Enabled = False 行うなどで対応可能です。

    ポイントは

    1. UIを処理するスレッドは、いかなる場合も止めない(かならず、メソッドやプロパティは作業を終えたら終了する)
    2. 別のスレッドやプロセスを呼び出した場合、その終了処理は非同期に行い、同一メソッド内で完結させようとしない

    です。

    では、どうすればいいのか?今回の場合「プロセスBが動いている間は、自分自身はユーザーからの入力は受け付けない」ということを実現すればいいわけです。そのためには「アプリケーションA全体をユーザーからの入力を受け付けない状態にすればいい」のです。

    試してみてください。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 編集済み とっちゃんMVP 2011年10月20日 8:01 タグがおかしくなっていたので修正
    2011年10月20日 8:00
  • Me.Enabled は、Aのメインフォームに対してのプロパティ設定だと思っていたのですが、間違ってますか?

    この指定で呼び出しボタンの問題点は解消出来ているのですが、そうすると、ウインドウの表示順の問題が発生して
    しまい、悩んでいます。

    一旦 Enabled = False を設定した事によって、発生している問題だと思うのですが、したい事を実現するには、
    しないわけにはいかないのですよね?

    何とか、両方の問題をクリアにする方法はないものでしょうか。

    2011年10月20日 8:44
  • 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/
    2011年10月20日 9:13
  • たぶん、Me.Enabled = false をしている時点で、アクティブにならない(背面に回る)不具合は回避不可能では。
    WaitForExit をやめたとしても、タイミング問題で以下のような挙動をしそうです。(確証はありませんが)

    B が終わったときに A をアクティブにしようとする動きが生じるが、A は Enabled = false なのでアクティブにできない。なので、次のウィンドウをアクティブにするという動きをしているように見受けられます。
    もし、この挙動をしているのなら、Me.Enabled = false じゃなくて、 Button1.Enabled = false; のように、フォーム自体を無効化するのではなく、配置しているコントロールを無効化する方向で考えた方がよいと思います。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2011年10月20日 13:17
    モデレータ
  • 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

    こんな感じです。

    ただし、この方法をおすすめしているわけではありません。

    2011年10月20日 14:16
  • 色々と、EnabledやTopMost,DoEvents()の処理位置を試してみて、以下のようにしたら、上手く動きました。

    ------------------------------------------------------------------
    Private Sub Form1_Load
        Me.TopMost = True
        Me.TopMost = False
    End Sub

    Private Sub Button1_DropDown
        Me.Enabled = False
        Process1.StartInfo.FileName = "xxx.exe"
        Process1.StartInfo.Arguments = "frm_yyy"
        Process1.Start()
    End Sub

    Private 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
    2011年10月21日 0:48