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>
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