none
すべての表示中の画面を閉じたい(VB2008) RRS feed

  • 質問

  •  

    VB2008で開発しています。
    Windowsアプリケーションを開発しています。

    23時になったら、すべてのフォームを閉じて、
    アプリケーションを終了してほしいという要望がありまして、
    どのように実現しようかと悩んでおりました。

    フォームは複数あり、すべてShowDialogで起動されています。
    すべてのフォームで時間を監視して、閉じる処理を記載すると、
    今後の保守性も悪いのではないかと思い、
    何か良い方法がないものかと検討しておりました。

    そこで、メイン画面にBackgroundWorkerコントロールを作成して、
    そのスレッドで監視して、23時になったらすべてのフォームを閉じようと
    考えました。

    以下のようなソースになっております。

    1 Public Class Form1  
    2  
    3     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load  
    4         Me.BackgroundWorker1.RunWorkerAsync()  
    5     End Sub  
    6  
    7     Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork  
    8         Do Until False  
    9             If Now.Hour = 23 Then  
    10                 For Each frm As Form In My.Application.OpenForms  
    11                     'ここですべての画面を閉じたいと思っております。  
    12                     Call frm.Close()  
    13                 Next  
    14             End If  
    15         Loop  
    16     End Sub  
    17  
    18     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click  
    19         '子画面を起動しています。  
    20         Dim frm As New Form2  
    21         Call frm.ShowDialog()  
    22     End Sub  
    23  
    24 End Class  
    25  



    しかし、このプログラムですと、12行目で、以下の例外が発生してしまいます。

    System.InvalidOperationException はユーザー コードによってハンドルされませんでした。
      Message="有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'Form1' がアクセスされました。"

    何か良い方法はないものでしょうか?
    私なりに考えてみたのですが、良い解決策が見つかりませんでした。

    私が考えた方法と違う方法でも構いません。
    何か解決する方法がございましたら、ご教示頂けませんか?
    ぜひよろしくお願いいたします。

    2009年2月19日 8:11

回答

  • コンドル の発言:

     

    VB2008で開発しています。
    Windowsアプリケーションを開発しています。

    23時になったら、すべてのフォームを閉じて、
    アプリケーションを終了してほしいという要望がありまして、
    どのように実現しようかと悩んでおりました。

    私が考えた方法と違う方法でも構いません。
    何か解決する方法がございましたら、ご教示頂けませんか?
    ぜひよろしくお願いいたします。



     そのアプリケーションは強制終了しても構わないものでしょうか?(何らかの終了処理を必要とするとか)
     もし強制終了しても構わないのであれば、Windowsタスクに特定のプロセスを終了するタスクを作成してはどうでしょうか?

     プロセスを終了するためのコマンドですがPowerShellやSysinternalsに含まれていますので、それらでプロセスを終了させることができます。
     ちなみにPowerShellだとkill、Sysinternalsだとpskill.exeです。
    (プロセスIDを取得する必要があるので、その処理も組み込んでください)

     タスクへの登録ですが、当然ですが管理者権限が必要ですので、そのタスクの実行ユーザーは管理者権限を持ったユーザーを登録してください。

    ※何らかの終了処理が必要なのであれば、別の方法を考えてみます。

    • 回答としてマーク コンドル 2009年2月20日 10:16
    2009年2月19日 9:58
  • コンドルさん の発言:

    私が考えた方法と違う方法でも構いません。
    何か解決する方法がございましたら、ご教示頂けませんか?


    Do Untilで回すのは負担がかかると思いますので、BackgroundWorkerを使わずに別スレッドを起こし、そこでTimerクラスを使って一定間隔毎に時刻をチェックし、23時になればUIスレッド、つまりフォームが動いているスレッドの全フォームを閉じて終了するメソッドを書いておいて、それを呼び出すようにすればいいんじゃないでしょうか?
    もしくは別スレッドにせず、UIスレッドでTimerクラスを使って時刻をチェックするだけでも実用に耐えるかもしれません。

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月20日 10:16
    2009年2月19日 13:40
    モデレータ
  • コンドルさん の発言:

    BackgroundWorker以外でもスレッドを扱えるのですね。
    勉強になりました。
    具体的にはThreadクラスなどを使用するのでしょうか。

    その通りで、Threadクラスを使います。元々はThreadクラスを使用してマルチスレッドのアプリケーションを行っていましたが、少し複雑なので、イベントを使ってもっと簡単に実現できるように、BackgroundWorkerが.NET Framework 2.0で追加されました。
    既に調べていらっしゃるかとは思いますが、例えば以下が参考になります。

    Windowsフォームで別スレッドからコントロールを操作するには?
    http://www.atmarkit.co.jp/fdotnet/dotnettips/312ctrlinvoke/ctrlinvoke.html

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月20日 10:15
    2009年2月20日 1:13
    モデレータ
  •  ちょっとうっかりしていたんですが、スレッドタイマーを使えば良いと思います。すみません。
    以下、テストで書いたコードです。テキストボックスに入力した秒になったら閉じます。ThreadTimerクラスはフォームです。

    Imports System.Threading  
     
    Public Class ThreadTimer  
        Dim tm As Timer     '参照を保持していないとガーベージコレクションの対象になるので注意。  
     
        Private Sub cbtn_startTimer_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles cbtn_startTimer.Click  
            Dim timerDelegate As TimerCallback = New TimerCallback(AddressOf MyTimer)  
            tm = New Timer(timerDelegate, Nothing, 0, 1000)  
        End Sub 
     
        'ラムダ式やActionデリゲートを使ったけど、もちろん普通にデリゲートを書いてもOK。  
        Public Sub MyTimer(ByVal o As Object)  
            If Invoke(Function() Me.TextBox1.Text) = DateTime.Now.Second.ToString() Then 
                Invoke(New Action(AddressOf Me.Owner.Close))  
            End If 
     
            System.Diagnostics.Debug.WriteLine(DateTime.Now)  
        End Sub 
     
    End Class 


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月21日 6:20
    2009年2月20日 16:07
    モデレータ
  • すみません。先の質問の部分について答えていませんでした。

    コンドルさん の発言:

    親画面を閉じることで、GCの機能で子画面が破棄されて終了しているのかと
    推測しているのですが、この方法は危険でしょうか?
    終了時の処理が、少し遅いなと感じているのですが・・・。

    GCの機能というわけではなくて、Application.Runの引数として与えたフォームが閉じるため、ExitThreadメソッドが呼び出され、アプリケーションが終了します。

    コンドルさん の発言:

    '今までの終了処理
    For Each frm As Form In My.Application.OpenForms
    Invoke(New FormClose(AddressOf frm.Close))
    Next
    '発生する例外
    System.InvalidOperationException はハンドルされませんでした。
    Message="コレクションが変更されました。列挙操作は実行されない可能性があります。"
    Source="mscorlib"

    もしよろしければ、こちらも何かアドバイス頂けますと、
    とても嬉しいです。

    この例外は、列挙している最中にMy.Application.OpenFormsの中身が削除されるために、My.Application.OpenForms の内容が変わってしまうためです。以下のようなコードを書けば良いでしょう。これだとFormClosingイベントやFormClosedイベントが発生し、アプリケーションが終了するはずです。

    While Application.OpenForms.Count > 0      
        Application.OpenForms(0).Close()      
    End While 
     
    Application.ExitThread(); 

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月23日 4:22
    2009年2月22日 6:23
    モデレータ

すべての返信

  • コンドル の発言:

     

    VB2008で開発しています。
    Windowsアプリケーションを開発しています。

    23時になったら、すべてのフォームを閉じて、
    アプリケーションを終了してほしいという要望がありまして、
    どのように実現しようかと悩んでおりました。

    私が考えた方法と違う方法でも構いません。
    何か解決する方法がございましたら、ご教示頂けませんか?
    ぜひよろしくお願いいたします。



     そのアプリケーションは強制終了しても構わないものでしょうか?(何らかの終了処理を必要とするとか)
     もし強制終了しても構わないのであれば、Windowsタスクに特定のプロセスを終了するタスクを作成してはどうでしょうか?

     プロセスを終了するためのコマンドですがPowerShellやSysinternalsに含まれていますので、それらでプロセスを終了させることができます。
     ちなみにPowerShellだとkill、Sysinternalsだとpskill.exeです。
    (プロセスIDを取得する必要があるので、その処理も組み込んでください)

     タスクへの登録ですが、当然ですが管理者権限が必要ですので、そのタスクの実行ユーザーは管理者権限を持ったユーザーを登録してください。

    ※何らかの終了処理が必要なのであれば、別の方法を考えてみます。

    • 回答としてマーク コンドル 2009年2月20日 10:16
    2009年2月19日 9:58
  • CatTailさん の発言:


     そのアプリケーションは強制終了しても構わないものでしょうか?(何らかの終了処理を必要とするとか)
     もし強制終了しても構わないのであれば、Windowsタスクに特定のプロセスを終了するタスクを作成してはどうでしょうか?

     プロセスを終了するためのコマンドですがPowerShellやSysinternalsに含まれていますので、それらでプロセスを終了させることができます。
     ちなみにPowerShellだとkill、Sysinternalsだとpskill.exeです。
    (プロセスIDを取得する必要があるので、その処理も組み込んでください)

     タスクへの登録ですが、当然ですが管理者権限が必要ですので、そのタスクの実行ユーザーは管理者権限を持ったユーザーを登録してください。

    ※何らかの終了処理が必要なのであれば、別の方法を考えてみます。

     

    ご回答ありがとうございます。

    終了処理は必要となります。
    要件を明確にしたいと思い記載しませんでしたが、
    画面終了時に、DBへの登録処理が必要となります。
    DBへの登録処理が完了後、すべての画面の終了をしたいと思っております。
    そのため、強制終了では要件を満たせないかもしれません。

    ご検討頂きありがとうございます。
    別な方法がございましたら、また教えて頂けますと
    とても助かります。

    よろしくお願いいたします。
    2009年2月19日 10:44
  • コンドルさん の発言:

    私が考えた方法と違う方法でも構いません。
    何か解決する方法がございましたら、ご教示頂けませんか?


    Do Untilで回すのは負担がかかると思いますので、BackgroundWorkerを使わずに別スレッドを起こし、そこでTimerクラスを使って一定間隔毎に時刻をチェックし、23時になればUIスレッド、つまりフォームが動いているスレッドの全フォームを閉じて終了するメソッドを書いておいて、それを呼び出すようにすればいいんじゃないでしょうか?
    もしくは別スレッドにせず、UIスレッドでTimerクラスを使って時刻をチェックするだけでも実用に耐えるかもしれません。

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月20日 10:16
    2009年2月19日 13:40
    モデレータ
  • trapemiyaさん の発言:

    Do Untilで回すのは負担がかかると思いますので、BackgroundWorkerを使わずに別スレッドを起こし、そこでTimerクラスを使って一定間隔毎に時刻をチェックし、23時になればUIスレッド、つまりフォームが動いているスレッドの全フォームを閉じて終了するメソッドを書いておいて、それを呼び出すようにすればいいんじゃないでしょうか?
    もしくは別スレッドにせず、UIスレッドでTimerクラスを使って時刻をチェックするだけでも実用に耐えるかもしれません。

     

    ご回答ありがとうございます。

    BackgroundWorker以外でもスレッドを扱えるのですね。
    勉強になりました。
    具体的にはThreadクラスなどを使用するのでしょうか。
    ざっと調べています。

    まだ調べている最中で解決には至っておりませんが、
    時間がかかりそうでしたので、お礼だけ先に述べさせて頂きます。

    ご教示ありがとうございます。
    頑張って調べてみます。
    2009年2月20日 0:50
  • コンドルさん の発言:

    BackgroundWorker以外でもスレッドを扱えるのですね。
    勉強になりました。
    具体的にはThreadクラスなどを使用するのでしょうか。

    その通りで、Threadクラスを使います。元々はThreadクラスを使用してマルチスレッドのアプリケーションを行っていましたが、少し複雑なので、イベントを使ってもっと簡単に実現できるように、BackgroundWorkerが.NET Framework 2.0で追加されました。
    既に調べていらっしゃるかとは思いますが、例えば以下が参考になります。

    Windowsフォームで別スレッドからコントロールを操作するには?
    http://www.atmarkit.co.jp/fdotnet/dotnettips/312ctrlinvoke/ctrlinvoke.html

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月20日 10:15
    2009年2月20日 1:13
    モデレータ
  • trapemiyaさん の発言:

    その通りで、Threadクラスを使います。元々はThreadクラスを使用してマルチスレッドのアプリケーションを行っていましたが、少し複雑なので、イベントを使ってもっと簡単に実現できるように、BackgroundWorkerが.NET Framework 2.0で追加されました。
    既に調べていらっしゃるかとは思いますが、例えば以下が参考になります。

    Windowsフォームで別スレッドからコントロールを操作するには?
    http://www.atmarkit.co.jp/fdotnet/dotnettips/312ctrlinvoke/ctrlinvoke.html

     

    とても参考になりページをご紹介頂き、
    ありがとうございます。

    とりあえず解決できました。
    Imports System.Threading  
     
    Public Class Form1  
        Delegate Sub FormClose()  
     
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load  
            Me.Timer1.Interval = 10000 
            Me.Timer1.Enabled = True 
        End Sub  
     
        Sub worker()  
            If Now.Hour = 23 Then  
                '親画面を閉じることで、子画面も閉じています。
                Invoke(New FormClose(AddressOf Me.Close))  
            End If  
        End Sub  
     
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click  
            '子画面を起動しています。     
            Dim frm As New Form2  
            Call frm.ShowDialog()  
        End Sub  
     
        Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick  
            Dim t As New Thread(New ThreadStart(AddressOf worker))  
            t.Start()  
        End Sub  
    End Class  
     

    ”とりあえず”と書いているのには、理由がありまして、
    緑字の部分が本当は良くないのだろうと思っております。
    親画面を閉じることで、GCの機能で子画面が破棄されて終了しているのかと
    推測しているのですが、この方法は危険でしょうか?
    終了時の処理が、少し遅いなと感じているのですが・・・。

    今までの考えていた終了処理をしようと思うと、
    以下の例外が発生するため、使用できません。
    '今までの終了処理  
    For Each frm As Form In My.Application.OpenForms  
        Invoke(New FormClose(AddressOf frm.Close))  
    Next  
     
    '発生する例外  
    System.InvalidOperationException はハンドルされませんでした。  
    Message="コレクションが変更されました。列挙操作は実行されない可能性があります。" 
    Source="mscorlib" 
     

    もしよろしければ、こちらも何かアドバイス頂けますと、
    とても嬉しいです。


    スレッドを使用する処理は、理解することができました。
    とても勉強になりました。
    ありがとうございました。

    2009年2月20日 3:47
  •  ちょっとうっかりしていたんですが、スレッドタイマーを使えば良いと思います。すみません。
    以下、テストで書いたコードです。テキストボックスに入力した秒になったら閉じます。ThreadTimerクラスはフォームです。

    Imports System.Threading  
     
    Public Class ThreadTimer  
        Dim tm As Timer     '参照を保持していないとガーベージコレクションの対象になるので注意。  
     
        Private Sub cbtn_startTimer_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles cbtn_startTimer.Click  
            Dim timerDelegate As TimerCallback = New TimerCallback(AddressOf MyTimer)  
            tm = New Timer(timerDelegate, Nothing, 0, 1000)  
        End Sub 
     
        'ラムダ式やActionデリゲートを使ったけど、もちろん普通にデリゲートを書いてもOK。  
        Public Sub MyTimer(ByVal o As Object)  
            If Invoke(Function() Me.TextBox1.Text) = DateTime.Now.Second.ToString() Then 
                Invoke(New Action(AddressOf Me.Owner.Close))  
            End If 
     
            System.Diagnostics.Debug.WriteLine(DateTime.Now)  
        End Sub 
     
    End Class 


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月21日 6:20
    2009年2月20日 16:07
    モデレータ
  • TimerCallback デリゲートなんて機能もあるのですね。
    勉強になりました。
    私には少し難しかったですが、
    以下の記事を参考に概ね理解できたと思います。
    http://www.atmarkit.co.jp/fdotnet/mthread/mthread02/mthread02_04.html

    とても勉強になりました。
    ありがとうございます。
    2009年2月21日 6:08
  • すみません。先の質問の部分について答えていませんでした。

    コンドルさん の発言:

    親画面を閉じることで、GCの機能で子画面が破棄されて終了しているのかと
    推測しているのですが、この方法は危険でしょうか?
    終了時の処理が、少し遅いなと感じているのですが・・・。

    GCの機能というわけではなくて、Application.Runの引数として与えたフォームが閉じるため、ExitThreadメソッドが呼び出され、アプリケーションが終了します。

    コンドルさん の発言:

    '今までの終了処理
    For Each frm As Form In My.Application.OpenForms
    Invoke(New FormClose(AddressOf frm.Close))
    Next
    '発生する例外
    System.InvalidOperationException はハンドルされませんでした。
    Message="コレクションが変更されました。列挙操作は実行されない可能性があります。"
    Source="mscorlib"

    もしよろしければ、こちらも何かアドバイス頂けますと、
    とても嬉しいです。

    この例外は、列挙している最中にMy.Application.OpenFormsの中身が削除されるために、My.Application.OpenForms の内容が変わってしまうためです。以下のようなコードを書けば良いでしょう。これだとFormClosingイベントやFormClosedイベントが発生し、アプリケーションが終了するはずです。

    While Application.OpenForms.Count > 0      
        Application.OpenForms(0).Close()      
    End While 
     
    Application.ExitThread(); 

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    • 回答としてマーク コンドル 2009年2月23日 4:22
    2009年2月22日 6:23
    モデレータ
  • trapemiyaさん の発言:
    GCの機能というわけではなくて、Application.Runの引数として与えたフォームが閉じるため、ExitThreadメソッドが呼び出され、アプリケーションが終了します。

    この例外は、列挙している最中にMy.Application.OpenFormsの中身が削除されるために、My.Application.OpenForms の内容が変わってしまうためです。以下のようなコードを書けば良いでしょう。これだとFormClosingイベントやFormClosedイベントが発生し、アプリケーションが終了するはずです。

    While Application.OpenForms.Count > 0      
        Application.OpenForms(0).Close()      
    End While 
     
    Application.ExitThread(); 

     

    Application.ExitThreadメソッドというものがあるのですね。
    とても勉強になりました。

    他にも教えて頂いたキーワードを元に検索して、勉強させて頂きました。
    画面終了時の仕組みが少し理解できたと思います。
    ありがとうございました。
    2009年2月23日 4:29