BackgroundWorker を使用して進行状況ダイアログを作成する(WPF & Livet版)

必要に迫られ、DOBON.NET さんの以下の記事

BackgroundWorkerクラスを使用して進行状況ダイアログを作成する

を参考に、WPF & Livet で進行状況ダイアログを作ってみました。



ダイアログは Forms からも呼び出せることを想定したインターフェイスになってます。VB.NET ですみませんが、暇見て C#版も書いてみようと思います。(いつになるか判りませんが・・・)


ProgressWindow.xaml (View)

<Window x:Class="ProgressWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:local="clr-namespace:LivetWPFApplication1"
        Title="{Binding Title}" Height="138" Width="500" WindowStartupLocation="CenterScreen">
     <i:Interaction.Triggers>
        <l:InteractionMessageTrigger MessageKey="Close" Messenger="{Binding Messenger}">
            <l:WindowInteractionMessageAction InvokeActionOnlyWhenWindowIsActive="False" />
        </l:InteractionMessageTrigger>
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="8" />
            <RowDefinition Height="24" />
            <RowDefinition Height="8" />
            <RowDefinition Height="16" />
            <RowDefinition Height="8" />
            <RowDefinition Height="26" />
            <RowDefinition Height="8" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="8" />
            <ColumnDefinition />
            <ColumnDefinition Width="8" />
        </Grid.ColumnDefinitions>
        <ProgressBar Grid.Column="1" Grid.Row="3" 
                     Value="{Binding Value}" 
                     Maximum="{Binding Maximum}" Minimum="{Binding Minimum}"/>
        <Button Content="キャンセル(C)" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Right" 
                Name="Button1" Width="100" Command="{Binding Path=CancelCommand}" />
        <TextBlock Grid.Column="1" Grid.Row="1" Name="TextBlock1" 
                   VerticalAlignment="Center" Text="{Binding Message}"/>
    </Grid>
</Window>


ProgressWindow.xaml.vb (View)

Option Explicit On
Option Strict On

Imports System.ComponentModel
Imports Livet

Class ProgressWindow
    Private _viewModel As ProgressViewModel

    Public Sub New(title As String, doWork As DoWorkEventHandler,
                   Optional minimum As Int32 = 0,
                   Optional maximum As Int32 = 100)
        InitializeComponent()
        _viewModel = New ProgressViewModel(title, doWork, minimum, maximum)
        Me.DataContext = _viewModel
    End Sub

    Public ReadOnly Property [Error]() As Exception
        <DebuggerNonUserCode()>
        Get
            Return _viewModel.Error
        End Get
    End Property

    Public ReadOnly Property HasError() As Boolean
        <DebuggerNonUserCode()>
        Get
            Return _viewModel.HasError
        End Get
    End Property

    Public ReadOnly Property IsCanceld() As Boolean
        <DebuggerNonUserCode()>
        Get
            Return _viewModel.IsCanceld
        End Get
    End Property

    Public ReadOnly Property Result() As Object
        <DebuggerNonUserCode()>
        Get
            Return _viewModel.Result
        End Get
    End Property
End Class


ProgressViewModel.vb (ViewModel)

Option Explicit On
Option Strict On

Imports System.ComponentModel
Imports System.Windows
Imports Livet
Imports Livet.Commands
Imports Livet.Messaging.Windows

Public Class ProgressViewModel
    Inherits ViewModel

    Private _backGroundWorker As BackgroundWorker
    Private _doWork As DoWorkEventHandler

#Region "コンストラクタ"
    Public Sub New(title As String, doWork As DoWorkEventHandler,
                   Optional minimum As Int32 = 0,
                   Optional maximum As Int32 = 100)
        Me.Title = title
        Me.Minimum = minimum
        Me.Maximum = maximum
        _backGroundWorker = New BackgroundWorker() With {
            .WorkerReportsProgress = True,
            .WorkerSupportsCancellation = True
        }
        _doWork = doWork
        AddHandler _backGroundWorker.DoWork, _doWork
        AddHandler _backGroundWorker.ProgressChanged, AddressOf ProgressChanged
        AddHandler _backGroundWorker.RunWorkerCompleted, AddressOf RunWorkerCompleted
        _backGroundWorker.RunWorkerAsync()
    End Sub
#End Region

#Region "Dispose Support"
    Protected Overrides Sub Dispose(disposing As Boolean)
        MyBase.Dispose(disposing)
        If (disposing) Then
            RemoveHandler _backGroundWorker.DoWork, _doWork
            RemoveHandler _backGroundWorker.ProgressChanged, AddressOf ProgressChanged
            RemoveHandler _backGroundWorker.RunWorkerCompleted, AddressOf RunWorkerCompleted
            _backGroundWorker.Dispose()
        End If
    End Sub
#End Region

#Region "プロパティ"

#Region "Errorプロパティ"
    Private _error As Exception
    ''' <summary>
    ''' バックグラウンド処理中に発生したエラーを取得・設定します。
    ''' </summary>
    Public ReadOnly Property [Error]() As Exception
        Get
            Return _error
        End Get
    End Property
#End Region

#Region "HasError変更通知プロパティ"
    Private _HasError As Boolean
    ''' <summary>
    ''' エラーが発生したかどうかを取得・設定します。
    ''' </summary>
    Public Property HasError() As Boolean
        <DebuggerNonUserCode()>
        Get
            Return _HasError
        End Get
        Set(ByVal value As Boolean)
            If (Object.Equals(_HasError, value)) Then Return
            _HasError = value
            RaisePropertyChanged("HasError")
        End Set
    End Property
#End Region

#Region "IsCanceld変更通知プロパティ"
    Private _IsCanceld As Boolean
    ''' <summary>
    ''' 処理がキャンセルされたかを取得・設定します。
    ''' </summary>
    Public Property IsCanceld() As Boolean
        <DebuggerNonUserCode()>
        Get
            Return _IsCanceld
        End Get
        Set(ByVal value As Boolean)
            If (Object.Equals(_IsCanceld, value)) Then Return
            _IsCanceld = value
            RaisePropertyChanged("IsCanceld")
            Me.CancelCommand.RaiseCanExecuteChanged()
        End Set
    End Property
#End Region

#Region "Maximum変更通知プロパティ"
    Private _Maximum As Int32
    ''' <summary>
    ''' プログレスバーの最大値を取得・設定します。
    ''' </summary>
    Public Property Maximum() As Int32
        <DebuggerNonUserCode()>
        Get
            Return _maximum
        End Get
        Set(ByVal value As Int32)
            If (Object.Equals(_maximum, value)) Then Return
            _maximum = value
            RaisePropertyChanged("Maximum")
        End Set
    End Property
#End Region

#Region "Message変更通知プロパティ"
    Private _Message As String
    ''' <summary>
    ''' メッセージを取得・設定します。
    ''' </summary>
    Public Property Message() As String
        <DebuggerNonUserCode()>
        Get
            Return _Message
        End Get
        Set(ByVal value As String)
            If (Object.Equals(_Message, value)) Then Return
            _Message = value
            RaisePropertyChanged("Message")
        End Set
    End Property
#End Region

#Region "Minimum変更通知プロパティ"
    Private _Minimum As Int32
    ''' <summary>
    ''' プログレスバーの最小値を取得・設定します。
    ''' </summary>
    Public Property Minimum() As Int32
        <DebuggerNonUserCode()>
        Get
            Return _minimum
        End Get
        Set(ByVal value As Int32)
            If (Object.Equals(_minimum, value)) Then Return
            _minimum = value
            RaisePropertyChanged("Minimum")
        End Set
    End Property
#End Region

#Region "Resultプロパティ"
    Private _result As Object
    ''' <summary>
    ''' DoWorkイベントハンドラで設定された結果を取得・設定します。
    ''' </summary>
    Public ReadOnly Property Result() As Object
        Get
            Return _result
        End Get
    End Property
#End Region

#Region "Title変更通知プロパティ"
    Private _Title As String
    ''' <summary>
    ''' 画面のタイトルを取得・設定します。
    ''' </summary>
    Public Property Title() As String
        <DebuggerNonUserCode()>
        Get
            Return _Title
        End Get
        Set(ByVal value As String)
            If (Object.Equals(_Title, value)) Then Return
            _Title = value
            RaisePropertyChanged("Title")
        End Set
    End Property
#End Region

#Region "Value変更通知プロパティ"
    Private _Value As Double
    ''' <summary>
    ''' プログレスバーの値を取得・設定します。
    ''' </summary>
    Public Property Value() As Double
        <DebuggerNonUserCode()>
        Get
            Return _Value
        End Get
        Set(ByVal value As Double)
            If (Object.Equals(_Value, value)) Then Return
            _Value = value
            RaisePropertyChanged("Value")
        End Set
    End Property
#End Region

#End Region

#Region "イベント"
    ''' <summary>
    ''' ReportProgressメソッドが呼び出された時のイベントハンドラです。
    ''' </summary>
    Private Sub ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
        'プログレスバーの値を変更する
        If (e.ProgressPercentage < Me.Minimum) Then
            Me.Value = Me.Minimum
        ElseIf (Me.Maximum < e.ProgressPercentage) Then
            Me.Value = Me.Maximum
        Else
            Me.Value = e.ProgressPercentage
        End If
        'メッセージのテキストを変更する
        Me.Message = DirectCast(e.UserState, String)
    End Sub

    ''' <summary>
    ''' バックグラウンド処理終了時のイベントハンドラです。
    ''' </summary>
    Private Sub RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
        If (e.Error IsNot Nothing) Then
            MessageBox.Show(
                "エラーが発生しました。" & vbCrLf & vbCrLf & e.Error.Message,
                "エラー",
                MessageBoxButton.OK, MessageBoxImage.Error)
            Me.HasError = True
            Me._error = e.Error
        ElseIf (e.Cancelled) Then
            _result = Nothing
        Else
            _result = e.Result
        End If
        Me.Close()
    End Sub
#End Region

#Region "コマンド"

#Region "CancelCommand"
    Private _CancelCommand As ViewModelCommand

    Public ReadOnly Property CancelCommand() As ViewModelCommand
        Get
            If _CancelCommand Is Nothing Then
                _CancelCommand = New ViewModelCommand(AddressOf Cancel, AddressOf CanCancel)
            End If
            Return _CancelCommand
        End Get
    End Property

    Private Function CanCancel() As Boolean
        Return Not Me.IsCanceld
    End Function

    Private Sub Cancel()
        Me.IsCanceld = True
        _backGroundWorker.CancelAsync()
        Me.Close()
    End Sub
#End Region

#Region "CloseCommand"
    Private _CloseCommand As ViewModelCommand

    Public ReadOnly Property CloseCommand() As ViewModelCommand
        Get
            If _CloseCommand Is Nothing Then
                _CloseCommand = New ViewModelCommand(AddressOf Close)
            End If
            Return _CloseCommand
        End Get
    End Property

    Private Sub Close()
        Messenger.Raise(New WindowActionMessage("Close", WindowAction.Close))
    End Sub
#End Region

#End Region

End Class


以下、使用例です。StartCommand を Button コントロールにバインドし実行します。

Option Explicit On
Option Strict On

Imports System.ComponentModel
Imports System.Windows
Imports Livet
Imports Livet.Commands

Public Class MainWindowViewModel
    Inherits ViewModel

#Region "StartCommand"
    Private _StartCommand As ViewModelCommand

    Public ReadOnly Property StartCommand() As ViewModelCommand
        Get
            If _StartCommand Is Nothing Then
                _StartCommand = New ViewModelCommand(AddressOf Start)
            End If
            Return _StartCommand
        End Get
    End Property

    Private Sub Start()
        Dim window = New ProgressWindow("進行状況ダイアログのテスト", AddressOf ProgressDialog_DoWork, 0, 100)
        '進行状況ダイアログを表示する
        window.ShowDialog()

        '結果を取得する
        If (window.HasError) Then
            'エラー情報を取得する
            MessageBox.Show("エラー: " + window.Error.Message)
        ElseIf (window.IsCanceld) Then
            MessageBox.Show("キャンセルされました")
        Else
            '結果を取得する
            Dim stopTime = CInt(window.Result)
            MessageBox.Show("成功しました: " & stopTime.ToString())
        End If
    End Sub

    ' DoWorkイベントハンドラ
    Private Sub ProgressDialog_DoWork(sender As Object, e As DoWorkEventArgs)
        Using bw As BackgroundWorker = DirectCast(sender, BackgroundWorker)
            ' 時間のかかる処理を開始する
            For i = 1 To 100
                ' キャンセルされたか調べる
                If bw.CancellationPending Then
                    ' キャンセルされたとき
                    e.Cancel = True
                    Return
                End If
                ' 指定された時間待機する
                System.Threading.Thread.Sleep(100)
                ' ProgressChanged イベントハンドラを呼び出し、コントロールの表示を変更する
                bw.ReportProgress(i, i.ToString() & "% 終了しました")
                ' 結果を設定する
                e.Result = i
            Next
        End Using
    End Sub
#End Region

End Class