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 にはメッセージが設定されているのですが、
どうしても表示されません。
どうにかして解決できませんでしょうか?
BackgroundWorker の利用を検討してみてください。
> 透過したような感じになります。
時間のかかる処理を行っている最中は、Text 等を変更しても、
その描画が後回しにされてしまう可能性があります。
回答していただいてありがとうございます。
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が閉じれませんでした。
コメントしていただいた内容から、仮に描画を後回しにせず設定
する方法というのはないのでしょうか?
進捗表示を、どのように行っていますか?
もし、DoWork 内で表示させようとしているのであれば、
それらは ProgressChanged に移動させる必要があります。
=== DoWork イベント ====
長い処理を行い、必要に応じて、進捗表示のために
ReportProgress(Integer, Object) を呼び出す。
=== ProgressChanged イベント ====
イベント引数の内容を、処理中ダイアログに反映させる。
=== RunWorkerCompleted イベント ====
非同期処理の結果確認や、処理中ダイアログのクローズなど。
申し訳ありません。
大雑把な書き方をしていたので、私自身わけがわからなくなってしまいました。
今やりたい処理は
(1)フォーム1の「開始」ボタンを押下
(2)DBのバックアップを実行
バックアップはバッチファイルを作成し、それをプロセス起動します。
(3)処理中ダイアログを表示(フォーム2)
(4)プロセス終了まで待機
(5)処理中ダイアログを終了
(6)後処理
作成したファイルを指定先にコピーなどなど。。。
という処理です。
(2)と(3)は入れ替えてみましたが、どちらもダイアログにメッセージが表示されませんでした。
メッセージ表示以外では動作に問題はないのですが。。。
ちなみに、この処理の場合は、プログレスバーを使用しないので、非表示になっています。
プログレスバーを表示している場合は、フォーム2にTimerコントロールを
保持し、Timer.Tickイベントで確認しようかと思っていました。
(まだここまで確認が出来ていないため、構想中です。)
BackgroundWorker の使い方が悪いのかもしれませんね。
もう少し考えてみたいと思います。
> (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 だけで処理できるかも知れません。
1つ1つ細かくご説明いただいてありがとうございます。
>> (2)DBのバックアップを実行
>> バックアップはバッチファイルを作成し、それをプロセス起動します。
>あれ? 別プロセスなのですか。
>呼び出し元/呼び出される側で、進捗状況の報告(プロセス間通信)は行われ>るのでしょうか?
>
>(パターン1)進捗確認は特に行われない。(単に終了を待つだけ)
>(パターン2)呼び出された側が、進捗を非同期通知する。
>(パターン3)呼び出した側が、定期的に進捗を問い合わせる。
>(パターン4)その他
今回の場合、(パターン1)および(パターン2)が当てはまるのでしょうか。
処理に応じて進捗確認をする場合としない場合があるのですが、
進捗確認する場合は、処理を行っているツールが経過ファイルというものを
出力しており、そこに8/10など、経過が入ってくるため、それを定期的に
見てプログレスバーに反映することになっています。
>> プログレスバーを表示している場合は、フォーム2にTimerコントロールを
>> 保持し、Timer.Tickイベントで確認しようかと思っていました。
>Timer.Tick 内では、何を確認させるのでしょうか?
>その内容次第では、BackgroundWorker1 を使わず、Timer だけで処理できる>かも知れません。
先の質問に記述した処理はTimer.Tickから行う予定でした。
いろいろ教えていただきましたので、確認をしてから返信させて
いただこうかと思っていたのですが、何分私の理解力が乏しく、すぐには
回答できそうにもありませんでしたので、指摘いただいたことだけ先に
回答させていただきました。
確認出来次第またご連絡いたします。
> メッセージラベルを貼り付けている部分が透過したような感じになります。
処理の監視をループ等で行うなど、時間のかかる処理を行っていた場合、
画面描画が後回しにされてしまい、そうなってしまうことがありますね。
> 処理を行っているツールが経過ファイルというものを
> 出力しており、そこに8/10など、経過が入ってくるため、
Tickイベントのたびに、そのファイルを読み込む、というわけですね。
その程度の単純なファイルの読み込みで、画面描画に影響を与えるほどの
負荷が発生するとは思えないので、少々不思議ですが…。
プロセスの終了待機については、どのようなコードで行っていますか?