処理中ダイアログのメッセージを表示するには?


はく  2007-12-14 11:58:22  No: 144173  IP: 192.*.*.*

VS2005 Professional  で開発しています。

フォーム1:通常画面
            ボタン押下で処理が実行されます。
フォーム2:処理中ダイアログ画面
            メッセージとプログレスバーを表示します。
            ただし、処理に応じてプログレスバーを表示する場合と
            しない場合があります。
としています。

フォーム1のボタン押下で、フォーム2を処理中ダイアログとして表示
させたいのですが、フォーム2自身は表示されても、メッセージがうまく
表示されず、メッセージラベルを貼り付けている部分が透過したような
感じになります。

フォーム1より
    Call フォーム2.SetDialog("実行中...")

フォーム2より
    Public Sub SetDialog(ByVal Kind As Integer, ByVal Msg As String)
        m_Kind = Kind
        m_Msg = Msg
        Me.show()
    End Sub

    Private Sub Form_Load(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
    Handles MyBase.Load
        lblMessage.Text = m_Msg
        lblCount.Text = ""
        lblTotal.Text = ""
        prgBar.Value = 0
        If m_Kind = 1 Then
            '表示種別:メッセージの場合
            pnlStatus.Visible = False      
        Else
            '表示種別:進行状況の場合
            pnlStatus.Visible = True
        End If
  
        Me.Refresh()
    End Sub

    ※pnlStatusはプログレスバー一連を貼り付けたパネルになります。

lblMessage.Text にはメッセージが設定されているのですが、
どうしても表示されません。
どうにかして解決できませんでしょうか?

編集 削除
魔界の仮面弁士  2007-12-14 13:18:11  No: 144174  IP: 192.*.*.*

BackgroundWorker の利用を検討してみてください。

> 透過したような感じになります。
時間のかかる処理を行っている最中は、Text 等を変更しても、
その描画が後回しにされてしまう可能性があります。

編集 削除
はく  2007-12-14 14:37:02  No: 144175  IP: 192.*.*.*

回答していただいてありがとうございます。

BackgroundWorker を使用してみました。
前述の流れから。。。

フォーム1より
  BgWorker.RunWorkerAsync()
  
  処理待ちの状態。。。

  BgWorker.CancelAsync()

  Private Sub BgWorker_DoWork(ByVal sender As System.Object, _
               ByVal e As   System.ComponentModel.DoWorkEventArgs) _
  Handles BgWorker.DoWork
     Call フォーム2.SetDialog(1, "実行中...")
  End Sub

  Private Sub BgWorker_Completed(ByVal sender As System.Object, _
     ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
  Handles BgWorker.RunWorkerCompleted
        フォーム2.Close()
  End Sub

 
とし、フォーム2はそのままの状態で実行してみました。

結果、バックグラウンドでフォーム2は起動されるのですが、
表示メッセージは変わらずです。
また、起動されたフォーム2が閉じれませんでした。


コメントしていただいた内容から、仮に描画を後回しにせず設定
する方法というのはないのでしょうか?

編集 削除
魔界の仮面弁士  2007-12-14 15:08:30  No: 144176  IP: 192.*.*.*

進捗表示を、どのように行っていますか?

もし、DoWork 内で表示させようとしているのであれば、
それらは ProgressChanged に移動させる必要があります。


=== DoWork イベント ====
長い処理を行い、必要に応じて、進捗表示のために
ReportProgress(Integer, Object) を呼び出す。

=== ProgressChanged イベント ====
イベント引数の内容を、処理中ダイアログに反映させる。

=== RunWorkerCompleted イベント ====
非同期処理の結果確認や、処理中ダイアログのクローズなど。

編集 削除
はく  2007-12-14 15:41:27  No: 144177  IP: 192.*.*.*

申し訳ありません。

大雑把な書き方をしていたので、私自身わけがわからなくなってしまいました。

今やりたい処理は
  (1)フォーム1の「開始」ボタンを押下
  (2)DBのバックアップを実行
        バックアップはバッチファイルを作成し、それをプロセス起動します。
  (3)処理中ダイアログを表示(フォーム2)
  (4)プロセス終了まで待機
  (5)処理中ダイアログを終了
  (6)後処理
        作成したファイルを指定先にコピーなどなど。。。

という処理です。
(2)と(3)は入れ替えてみましたが、どちらもダイアログにメッセージが表示されませんでした。
メッセージ表示以外では動作に問題はないのですが。。。

ちなみに、この処理の場合は、プログレスバーを使用しないので、非表示になっています。
プログレスバーを表示している場合は、フォーム2にTimerコントロールを
保持し、Timer.Tickイベントで確認しようかと思っていました。
(まだここまで確認が出来ていないため、構想中です。)

BackgroundWorker の使い方が悪いのかもしれませんね。
もう少し考えてみたいと思います。

編集 削除
魔界の仮面弁士  2007-12-14 18:01:33  No: 144178  IP: 192.*.*.*

>  (1)フォーム1の「開始」ボタンを押下
このイベント内のコードは、実質 1 行だけです。
Button1_Click で行うのは、RunWorkerAsync を呼び出すことだけです。

  Sub Button1_Click(〜
    BackgroundWorker1.RunWorkerAsync(〜)
  End Sub

実際の処理は、すべて BackgroundWorker1.DoWork に記述します。


その他、Button1_Click 内に付け加えることがあるとすれば、砂時計表示とか、
処理中に Form1 が閉じられないよう、FormClosing でキャンセルするための
フラグ制御などですかね。


>  (2)DBのバックアップを実行
>        バックアップはバッチファイルを作成し、それをプロセス起動します。
あれ? 別プロセスなのですか。
呼び出し元/呼び出される側で、進捗状況の報告(プロセス間通信)は行われるのでしょうか?

(パターン1)進捗確認は特に行われない。(単に終了を待つだけ)
(パターン2)呼び出された側が、進捗を非同期通知する。
(パターン3)呼び出した側が、定期的に進捗を問い合わせる。
(パターン4)その他


>  (3)処理中ダイアログを表示(フォーム2)
BackgroundWorker1.DoWork 内(またはそこから呼ばれたメソッド)から、
Form2 (モードレス ダイアログ)を表示したり、Label 等のコントロールを
操作したりすることはできません。

進捗表示は、メインスレッド側で行う必要があるため、
Form2 を開く/表示更新/閉じることができるのは、基本的には
Button1_Click 時、BackgroundWorker1_ProgressChanged時、そして
BackgroundWorker1_RunWorkerCompleted時となります。

たとえば…。

Imports System.ComponentModel
Public Class Form1
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    Form2.Show(Me)
    BackgroundWorker1.WorkerReportsProgress = True
    BackgroundWorker1.RunWorkerAsync()
  End Sub

  Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Dim bgw As BackgroundWorker = DirectCast(sender, BackgroundWorker)
    For n As Integer = 1 To 100
      '長い処理の代わり
      System.Threading.Thread.Sleep(123)
      '進捗表示の依頼
      bgw.ReportProgress(n, String.Format("処理中{0}%", n))
    Next
    e.Result = "処理成功"
  End Sub

  Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    '進捗表示
    Form2.Label1.Text = e.UserState.ToString()
  End Sub

  Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    Form2.Close()
    MsgBox(e.Result)
  End Sub
End Class


>  (4)プロセス終了まで待機
現在は、プロセスの起動〜終了までの処理を、どのように行っていますか?

Process.WaitForExit によって同期待機させるなら、プロセス起動処理を
メインスレッドにやらせるわけでは行かないので、BackgroundWorker を併用して、

  Public Class Form1
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Button1.Enabled = False
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        '電卓起動
        Dim P As Process = Process.Start("CALC.EXE")
        P.WaitForExit()
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        MsgBox("電卓が閉じられた")
        Button1.Enabled = True
    End Sub
  End Class

といった感じになるかと思います。

また、通知を非同期で行うのであれば、BackgroundWorker を使わずとも、
Process.Exited イベントで処理することができます。

  Public Class Form1
    Private WithEvents P As Process

    Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
      Button1.Enabled = False
      '電卓起動
      P = Process.Start("CALC.EXE")
      P.EnableRaisingEvents = True
    End Sub

    Private Sub P_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles P.Exited
      Invoke(New MethodInvoker(AddressOf Completed))
    End Sub

    Private Sub Completed()
      MsgBox("電卓が閉じられた")
      Button1.Enabled = True
    End Sub
  End Class


>  (5)処理中ダイアログを終了
>  (6)後処理
これらは、BackgroundWorker1_RunWorkerCompleted 内で行うことになりますね。


> プログレスバーを表示している場合は、フォーム2にTimerコントロールを
> 保持し、Timer.Tickイベントで確認しようかと思っていました。
Timer.Tick 内では、何を確認させるのでしょうか?
その内容次第では、BackgroundWorker1 を使わず、Timer だけで処理できるかも知れません。

編集 削除
はく  2007-12-17 14:45:25  No: 144179  IP: 192.*.*.*

1つ1つ細かくご説明いただいてありがとうございます。

>>  (2)DBのバックアップを実行
>>        バックアップはバッチファイルを作成し、それをプロセス起動します。
>あれ? 別プロセスなのですか。
>呼び出し元/呼び出される側で、進捗状況の報告(プロセス間通信)は行われ>るのでしょうか?
>
>(パターン1)進捗確認は特に行われない。(単に終了を待つだけ)
>(パターン2)呼び出された側が、進捗を非同期通知する。
>(パターン3)呼び出した側が、定期的に進捗を問い合わせる。
>(パターン4)その他

今回の場合、(パターン1)および(パターン2)が当てはまるのでしょうか。

処理に応じて進捗確認をする場合としない場合があるのですが、
進捗確認する場合は、処理を行っているツールが経過ファイルというものを
出力しており、そこに8/10など、経過が入ってくるため、それを定期的に
見てプログレスバーに反映することになっています。


>> プログレスバーを表示している場合は、フォーム2にTimerコントロールを
>> 保持し、Timer.Tickイベントで確認しようかと思っていました。
>Timer.Tick 内では、何を確認させるのでしょうか?
>その内容次第では、BackgroundWorker1 を使わず、Timer だけで処理できる>かも知れません。

先の質問に記述した処理はTimer.Tickから行う予定でした。
いろいろ教えていただきましたので、確認をしてから返信させて
いただこうかと思っていたのですが、何分私の理解力が乏しく、すぐには
回答できそうにもありませんでしたので、指摘いただいたことだけ先に
回答させていただきました。

確認出来次第またご連絡いたします。

編集 削除
魔界の仮面弁士  2007-12-17 15:21:18  No: 144180  IP: 192.*.*.*

> メッセージラベルを貼り付けている部分が透過したような感じになります。
処理の監視をループ等で行うなど、時間のかかる処理を行っていた場合、
画面描画が後回しにされてしまい、そうなってしまうことがありますね。


> 処理を行っているツールが経過ファイルというものを
> 出力しており、そこに8/10など、経過が入ってくるため、
Tickイベントのたびに、そのファイルを読み込む、というわけですね。

その程度の単純なファイルの読み込みで、画面描画に影響を与えるほどの
負荷が発生するとは思えないので、少々不思議ですが…。

プロセスの終了待機については、どのようなコードで行っていますか?

編集 削除