Excel生成時にProgressBarを使って進捗状態を把握したい への返答
投稿で使用できる特殊コードの説明。(別タブで開きます。)
以下の返答は逆順(新しい順)に並んでいます。
投稿者 ロト君  (社会人)
投稿日時
2020/3/12 14:38:09
>Private Sub LongTimeAction()中に処理を入れる事は分かりましたが、Excelのセルに入れ込む際に、
問題が解決出来ました!
処理が少なすぎた様でした。
ProgressBarは問題なく動きました。
みなさん。ありがとうございます!!
問題が解決出来ました!
処理が少なすぎた様でした。
ProgressBarは問題なく動きました。
みなさん。ありがとうございます!!
投稿者 ロト君  (社会人)
投稿日時
2020/3/12 14:21:40
魔界の仮面弁士 さん。ありがとうございます。
>Microsoft Excel を直接操作するのではなく、ClosedXML、EPPlus、ReoGrid 等のExcel データあるいは Excel ファイルを読み書きできるライブラリを使うのはどうでしょう。
ClosedXMLを使って、Excelを生成しています。
Private Sub LongTimeAction()中に処理を入れる事は分かりましたが、
Excelのセルに入れ込む際に、
'シート名を指定してシートを取得
Dim sheet As ClosedXML.Excel.IXLWorksheet = workbook.Worksheet(1)
を、前もって読み込ませて
Private Sub LongTimeAction()中で、
sheet.Cell("A" & i).Value = i + 1
と、使えない感じです。
ローカルで宣言してるためでしょうか?
>Microsoft Excel を直接操作するのではなく、ClosedXML、EPPlus、ReoGrid 等のExcel データあるいは Excel ファイルを読み書きできるライブラリを使うのはどうでしょう。
ClosedXMLを使って、Excelを生成しています。
Private Sub LongTimeAction()中に処理を入れる事は分かりましたが、
Excelのセルに入れ込む際に、
'シート名を指定してシートを取得
Dim sheet As ClosedXML.Excel.IXLWorksheet = workbook.Worksheet(1)
を、前もって読み込ませて
Private Sub LongTimeAction()中で、
sheet.Cell("A" & i).Value = i + 1
と、使えない感じです。
ローカルで宣言してるためでしょうか?
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2020/3/12 11:38:57
> ProgressBarを使って進捗状況を把握したいと思っています。
ProgressBar は通常、全体進捗と現在進捗率が分かる場合に
使われますが、その点は大丈夫でしょうか?
進捗率が不明な場合はマーキースタイルにする手もありますが、
それではあまり意味が無いでしょうし。
> サンプルで作成した下記のモノは動きました。
UI スレッドである Button1_Click 中に Sleep を呼び出すことは避けてください。
提示頂いた例だと、最低でも 1分41秒間はビジー状態になってしまう計算であり、
処理が終わるまで、フォームの移動や操作が阻害されることになります。
VB に限った話ではありませんが、Windows のメッセージ処理の設計上、
処理結果の多くは空き時間(アイドルタイム)に画面に反映されるようになっています。
つまりイベント処理が終わって End Sub に到達した後で描画されたりします。
中には即時反映されるものもありますが、基本的には長い処理は UI スレッドではなく
ワーカースレッドで実施する設計にするのが望ましいかと。
VB2019 とのことなので、Async / Await を使う方法をお奨めしておきます。
あるいは昔ながらの BackgroundWorker を使う方法でも実装できますね。
以下のサンプルでは、処理の中断や二重実行の制御は省略しています。
> VB側のDataGridViewに検索項目を出力してExcelの各セルに埋め込んで行く感じのソフトです。
Microsoft Excel を直接操作するのではなく、ClosedXML、EPPlus、ReoGrid 等の
Excel データあるいは Excel ファイルを読み書きできるライブラリを使うのはどうでしょう。
これらはインプロセスでの制御となるため、Microsoft Excel を直接制御するよりも高速です。
https://reogrid.net/jp/document/
https://www.atmarkit.co.jp/ait/articles/1810/24/news016.html
VB から Microsoft Excel を制御する場合、アウトプロセスとなるため、
Excel VBA に比べると通信コストが高めです。そのため、Excel のオブジェクトを
操作する必要がある場合は、プロパティやメソッドを呼び出す回数が、
できるだけ少なくなるように設計するべきです。
たとえばループ処理などで、Excel のセル 1 つ 1 つに値を書き込んでいくことと時間がかかりますが、
ループ処理などで二次元配列を作って起き、その配列を複数セルへ一括代入するようにすれば、
通信回数が 1 回にまとまるため、処理速度を向上できます。
ProgressBar は通常、全体進捗と現在進捗率が分かる場合に
使われますが、その点は大丈夫でしょうか?
進捗率が不明な場合はマーキースタイルにする手もありますが、
それではあまり意味が無いでしょうし。
> サンプルで作成した下記のモノは動きました。
UI スレッドである Button1_Click 中に Sleep を呼び出すことは避けてください。
提示頂いた例だと、最低でも 1分41秒間はビジー状態になってしまう計算であり、
処理が終わるまで、フォームの移動や操作が阻害されることになります。
VB に限った話ではありませんが、Windows のメッセージ処理の設計上、
処理結果の多くは空き時間(アイドルタイム)に画面に反映されるようになっています。
つまりイベント処理が終わって End Sub に到達した後で描画されたりします。
中には即時反映されるものもありますが、基本的には長い処理は UI スレッドではなく
ワーカースレッドで実施する設計にするのが望ましいかと。
VB2019 とのことなので、Async / Await を使う方法をお奨めしておきます。
あるいは昔ながらの BackgroundWorker を使う方法でも実装できますね。
以下のサンプルでは、処理の中断や二重実行の制御は省略しています。
Imports System.ComponentModel
Imports System.Threading
Imports System.Threading.Tasks
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Button1.Text = "元の実装"
Button2.Text = "Aync/Await版"
Button3.Text = "BackgrouneWorker版"
BackgroundWorker1.WorkerReportsProgress = True
ResetProgressBar()
End Sub
Private Sub ResetProgressBar(Optional min As Integer = 0, Optional max As Integer = 100, Optional current As Integer = 0)
ProgressBar1.Minimum = min
ProgressBar1.Maximum = max
ProgressBar1.Value = current
End Sub
'長い処理を同期的に実行するメソッド
Private Sub LongTimeAction()
Thread.Sleep(1000)
Debug.WriteLine(Now.Ticks)
End Sub
'長い処理を非同期的に実行するメソッド
Private Function LongTimeActionAsync() As Task
Return Task.Run(Sub()
Thread.Sleep(1000)
Debug.WriteLine(Now.Ticks)
End Sub)
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ResetProgressBar()
For i = 0 To 100
LongTimeAction()
ProgressBar1.Value = i
Next
MsgBox("終了")
End Sub
Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
ResetProgressBar()
For i = 0 To 100
Await LongTimeActionAsync()
ProgressBar1.Value = i
Next
MsgBox("終了")
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim bgw = DirectCast(sender, BackgroundWorker)
For i = 0 To 100
LongTimeAction()
bgw.ReportProgress(i)
Next
e.Result = "終了"
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
MsgBox(e.Result)
End Sub
End Class
> VB側のDataGridViewに検索項目を出力してExcelの各セルに埋め込んで行く感じのソフトです。
Microsoft Excel を直接操作するのではなく、ClosedXML、EPPlus、ReoGrid 等の
Excel データあるいは Excel ファイルを読み書きできるライブラリを使うのはどうでしょう。
これらはインプロセスでの制御となるため、Microsoft Excel を直接制御するよりも高速です。
https://reogrid.net/jp/document/
https://www.atmarkit.co.jp/ait/articles/1810/24/news016.html
VB から Microsoft Excel を制御する場合、アウトプロセスとなるため、
Excel VBA に比べると通信コストが高めです。そのため、Excel のオブジェクトを
操作する必要がある場合は、プロパティやメソッドを呼び出す回数が、
できるだけ少なくなるように設計するべきです。
たとえばループ処理などで、Excel のセル 1 つ 1 つに値を書き込んでいくことと時間がかかりますが、
ループ処理などで二次元配列を作って起き、その配列を複数セルへ一括代入するようにすれば、
通信回数が 1 回にまとまるため、処理速度を向上できます。
投稿者 ロト君  (社会人)
投稿日時
2020/3/12 11:26:35
kiku さん。ありがとうございます。
サンプルコードを試してみます。
サンプルコードを試してみます。
投稿者 kiku  (社会人)
投稿日時
2020/3/12 10:28:52
下記にはもっと詳しくかかれています。
参考になると思うので貼っておきます。
https://dobon.net/vb/dotnet/programing/displayprogress.html
参考になると思うので貼っておきます。
https://dobon.net/vb/dotnet/programing/displayprogress.html
投稿者 kiku  (社会人)
投稿日時
2020/3/12 09:29:20
現在の変換処理は、ボタンの中で実行してると仮定して回答します。
ボタンの中でプログレスバーの状態を変化させていると思うのですが、
この場合、プログレスバーが表示が更新されるのは、
ボタンの処理がすべて終了した後になります。
なので、いきなり100%になります。
そもそも、ボタンの中で時間のかかる処理を行うべきではありません。
このようなことをした場合、ボタンの処理が終了しないと、
例えば、アプリ終了ボタンを押下しても、フリーズのような状態になり、
実行されません。
どのような構造が良いかということですが、
時間のかかる処理は、UIスレッドとは別のスレッドで実行する方が良いです。
バックグラウンドワーカーを使う例ですが、その方法のサンプルは下記になります。
いろいろとぐぐってみると良いです。
http://hensa40.cutegirl.jp/archives/2971
上記の例ですと、
BackgroundWorker1_DoWorkの中に
時間のかかる処理を記述すれば
プログレスバーが表示できるようになります。
まずは、上記のサンプルをそのまま記述し、
動作を確認してみてください。
投稿者 ロト君  (社会人)
投稿日時
2020/3/12 08:55:05
環境:
VB2019
内容:
AccessデータをVB2019を使ってExcelに変換するソフトを作っています。
VB側のDataGridViewに検索項目を出力してExcelの各セルに埋め込んで行く感じのソフトです。
その際に、大きな検索範囲だと動いているのか分からないので、ProgressBarを使って進捗状況を把握したいと思っています。
サンプルで作成した下記のモノは動きました。
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
pb_output.Value = 0
pb_output.Minimum = 0
pb_output.Maximum = 100
Dim i As Int32
For i = 0 To 100 Step 1
System.Threading.Thread.Sleep(1000)
pb_output.Value = i
Next
End Sub
しかし、実際にForの部分でExcelを生成させると、今まで通り、ProgressBarが止まったままで、Excel完成時に100%となってしまいます・・・。
Excel生成中にPregressBarの進捗を進めるためにはどうすればよいでしょうか?
教えてもらえると、有難いです。
宜しくお願い致します。
VB2019
内容:
AccessデータをVB2019を使ってExcelに変換するソフトを作っています。
VB側のDataGridViewに検索項目を出力してExcelの各セルに埋め込んで行く感じのソフトです。
その際に、大きな検索範囲だと動いているのか分からないので、ProgressBarを使って進捗状況を把握したいと思っています。
サンプルで作成した下記のモノは動きました。
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
pb_output.Value = 0
pb_output.Minimum = 0
pb_output.Maximum = 100
Dim i As Int32
For i = 0 To 100 Step 1
System.Threading.Thread.Sleep(1000)
pb_output.Value = i
Next
End Sub
しかし、実際にForの部分でExcelを生成させると、今まで通り、ProgressBarが止まったままで、Excel完成時に100%となってしまいます・・・。
Excel生成中にPregressBarの進捗を進めるためにはどうすればよいでしょうか?
教えてもらえると、有難いです。
宜しくお願い致します。
『ローカル変数』だけを使うのであれば安全です。しかし、
『フィールド変数』をスレッド間で共有することは避けてください。
同一スレッド間であれば、フィールド変数を共有してもまったく問題ありませんが
複数のスレッドから同じフィールド変数を同時に読み書きするのは原則 NG です。
どうしても共有するなら、同時実行制御の仕組みを設けて、書き込み完了まで
他スレッドの読み取りを完全にロックするなど、排他制御をしっかり組み込む必要があります。
処理を分割できる場合は、Task(Of 進捗状況) を戻り値とする Async Function にして
それを繋いで処理するか、もしくは、Progress(Of 進捗状況) 型を利用する方法が使えます。
https://docs.microsoft.com/ja-jp/dotnet/api/system.progress-1
http://outside6.wp.xdomain.jp/2016/07/25/post-91/
あるいは先の例のように、BackgroundWorker などのイベントベースの通知機構を用いるとか、
もしくはワーカースレッドから Invoke メソッドを通じて実行させるという手もあります。
http://blog.livedoor.jp/gab_km/archives/765026.html
もしい¥ BackgroundWorker を使う場合は、過去に何度か注意点を
投稿していたと思うので、過去ログを検索してみてください。