【VB.NET】非同期のプログレスバーを作成する

VB.NET でプログレスバーを非同期で動作させてみます。

VB.NET における非同期処理、キャンセル処理、Progress クラスの使い方等のサンプルになってます。C# の情報はたくさん見つかるけど、VB.NET のサンプルは少なくて苦労しますね。

この記事ではダイアログを WPF で作ってますが、 Forms で作ったとしても、プログラムコードは共通で使えると思います。

下のビデオでは、プログレスバーが非同期で動いていることを示すために、プログレスバーの動作中にダイアログを動かしたりサイズ変更したりしています。また、キャンセルのテストもしています。

 

XAML

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow" Height="135" Width="392">
<Grid>
    <TextBlock Height="23" Name="progressMsg" Text="進行状況" Margin="10, 0, 10, 70" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" />
    <ProgressBar Height="20" HorizontalAlignment="Stretch" Margin="10, 0, 10, 50" Name="progressBar" VerticalAlignment="Bottom" />
    <Button Name="executeBtn" Content="実行(_E)" Margin="10, 0, 0, 10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Height="30" Width="100" />
    <Button Name="cancelBtn" Content="キャンセル(_C)" Margin="0, 0, 0, 10" HorizontalAlignment="Center" VerticalAlignment="Bottom" Height="30" Width="100" />
    <Button Name="closeBtn" Content="閉じる(_Q)" Margin="0, 0, 10, 10" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="30" Width="100" />
</Grid>
</Window>

 

コードビハインド

Imports System.Threading

Class MainWindow


Dim oCancellationTokenSource As CancellationTokenSource = Nothing '個々のキャンセルトークンへのキャンセル通知を管理・送信する
Dim oCancellationToken As CancellationToken = Nothing 'キャンセルをリッスンしているタスクまたはスレッドに渡すトークン

'Progress クラスで使う進捗データ
Class progressClass
    Friend progress As Integer = 0
    Friend message As String = ""
    Sub New(_progress As Integer, _message As String)
        progress = _progress
        message = _message
    End Sub
End Class



'「実行」ボタン
Private Async Sub executeBtn_Click(sender As Object, e As RoutedEventArgs) Handles executeBtn.Click

oCancellationTokenSource = New CancellationTokenSource() '.Cancel されたときの状態をリセットするため(終了時に .Dispose して)New し直す。
oCancellationToken = oCancellationTokenSource.Token

'テスト用のファイル名を 100 個作成
Dim files As New List(Of String)
For i As Integer = 0 To 99
    files.Add("File" & i + 1 & ".txt")
Next

'Progress クラスの進捗報告は New Progress されたコンテキストに対して行われるので、非同期処理を開始する前に New しておく。
Dim progress_files = New Progress(Of progressClass)(AddressOf showProgress_files)

Call ボタンを無効化

Try

    '別スレッドで非同期処理
    Dim bContinue As Boolean = Await Task.Run(Function() 複数ファイルの処理(progress_files, files))
    Me.progressMsg.Text = "処理が完了しました"

Catch ex As OperationCanceledException

    'ユーザーがキャンセルした
    Me.progressMsg.Text = "キャンセルされました"

Catch ex2 As Exception

    Me.progressMsg.Text = "何かエラーが起きたみたい"

Finally

    oCancellationTokenSource.Dispose
    oCancellationTokenSource = Nothing

End Try

Call ボタンを有効化

End Sub



Private Function 複数ファイルの処理(progress_files As IProgress(Of progressClass), files As List(Of String)) As Boolean

'UI スレッドとは別スレッドなのでこの関数内から直接 UI にはアクセスできない(Dispatcher.Invoke を使えば可能)

Dim filesTotal As Integer = files.Count

For i As Integer = 0 To files.Count -1

    Dim progressPercent As Integer = CInt((i + 1) / filesTotal * 100) '進捗率
    progress_files.Report(New progressClass(progressPercent, files.Item(i) & " (" & (i + 1).ToString("N0") & "/" & filesTotal.ToString("N0") & ")"))

    System.Threading.Thread.Sleep(100)

    'Call ファイルごとの処理(files.Item(i))

    oCancellationToken.ThrowIfCancellationRequested 'ユーザーがキャンセルしたら例外を投げる
    'デバッグモードで非同期処理の例外が拾えない?と思ったら --> https://elleneast.com/?p=5918

    '例外を使わないやり方
    'If token.IsCancellationRequested = True Then
    '    Return False
    'End If
Next

progress_files.Report(New progressClass(100, ""))

Return True

End Function



'進捗表示。この関数は UI スレッドで呼び出される
Private Sub showProgress_files(oProgress As progressClass)

Me.progressMsg.Text = oProgress.message

Call showProgress_setValue(Me.progressBar, oProgress.progress)

End Sub

Private Sub showProgress_setValue(oProgressBar As ProgressBar, progress As Integer)

oProgressBar.Value = progress

'これ↓は Forms の話かな?WPF ではやらなくても大丈夫みたい。

'Vista 以降 ProgressBar の値を変更しても即座にバーに反映されず、少し遅れてバーが伸びてくるようになってしまった。
'これだと実際には処理が完了していても、バーが最後まで伸びてないので処理が未完了に見えることがある。
'設定した値が即座にバーに反映されるようにするため、下記のような変なことをしている。
'https://dobon.net/vb/dotnet/control/pbdisableanimation.html

'If progress < oProgressBar.Maximum Then
'     oProgressBar.Value = progress + 1
'     oProgressBar.Value = progress
'Else
'     oProgressBar.Maximum += 1
'     oProgressBar.Value = progress + +1
'     oProgressBar.Value = progress
'     oProgressBar.Maximum -= 1
'End If

End Sub



Private Sub ボタンを無効化
Me.executeBtn.IsEnabled = False
Me.closeBtn.IsEnabled = False
End Sub



Private Sub ボタンを有効化
Me.executeBtn.IsEnabled = True
Me.closeBtn.IsEnabled = True
End Sub



'「閉じる」ボタン
Private Sub closeBtn_Click(sender As Object, e As RoutedEventArgs) Handles closeBtn.Click

Me.Close

End Sub



'「キャンセル」ボタン
Private Sub cancelBtn_Click(sender As Object, e As RoutedEventArgs) Handles cancelBtn.Click

If oCancellationTokenSource IsNot Nothing Then

    oCancellationTokenSource.Cancel 'ユーザーがキャンセルした

End If

End Sub

End Class

 

 

購読する
通知を受け取る対象
guest
0 Comments
Inline Feedbacks
View all comments