注意:この記事は、.NET Framework 1.1以前を想定して書かれています。.NET Framework 2.0以降でも動作するとは思いますが、.NET Framework 2.0以降では「BackgroundWorkerクラスを使用して進行状況ダイアログを作成する」の方を参考にしてください。
ここでは、時間のかかる処理を行っている時にその進行状況を表示する、進行状況ダイアログを表示する方法を紹介します。
進行状況ダイアログを作成するには、「時間のかかる処理の進行状況を表示する」で紹介した方法が参考になります。ここではその内、マルチスレッドを使用する方法で進行状況ダイアログを作る方法を紹介します。
補足:ここではDoEventsメソッドを使った方法は紹介しませんが、「時間のかかる処理の進行状況を表示する」を参考にすれば簡単でしょう。しかしDoEventsメソッドを使って進行状況ダイアログを表示する場合、「時間のかかる処理の進行状況を表示する」で紹介したような欠点があるだけでなく、別の問題が生じます。というのは、本来ならば進行状況ダイアログはShowDialogメソッドによりモーダルで表示したいところですが、モーダルで表示するとShowDialog以降のコードはダイアログが閉じられるまで実行されないため、進行状況を示したい時間のかかる処理はダイアログのクラス内に記述(あるいは、デリゲートやイベントを使用)しなければならなくなります。ダイアログを表示し、その直後に時間のかかる処理を記述できるようにするには、ダイアログはモードレスでメインフォームをオーナーとして表示し、メインフォームのEnabledプロパティをFalseにして操作できないようにするといったごまかしが必要になります。
下に進行状況ダイアログを表示するためのクラス(ProgressDialogクラス)のサンプルを示します。ここでProgressFormクラスはVisual Studioのフォームデザイナで作成されたフォームで、プログレスバーとキャンセルボタン、さらにメッセージを表示するラベルが配置されており、進行状況ダイアログとして表示されるものですが、このフォームのShowDialogメソッドでダイアログを表示されるのではなく、ProgressDialogクラスを使って進行状況ダイアログを表示、操作します。
Public Class ProgressForm Inherits System.Windows.Forms.Form #Region " Windows フォーム デザイナで生成されたコード " Public Sub New() MyBase.New() ' この呼び出しは Windows フォーム デザイナで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後に初期化を追加します。 End Sub ' Form は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。 Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub ' Windows フォーム デザイナで必要です。 Private components As System.ComponentModel.IContainer ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。 'Windows フォーム デザイナを使って変更してください。 ' コード エディタを使って変更しないでください。 Friend WithEvents Label1 As System.Windows.Forms.Label Friend WithEvents ProgressBar1 As System.Windows.Forms.ProgressBar Friend WithEvents Button1 As System.Windows.Forms.Button <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.Label1 = New System.Windows.Forms.Label Me.ProgressBar1 = New System.Windows.Forms.ProgressBar Me.Button1 = New System.Windows.Forms.Button Me.SuspendLayout() ' 'Label1 ' Me.Label1.Location = New System.Drawing.Point(8, 8) Me.Label1.Name = "Label1" Me.Label1.Size = New System.Drawing.Size(296, 48) Me.Label1.TabIndex = 0 ' 'ProgressBar1 ' Me.ProgressBar1.Location = New System.Drawing.Point(8, 56) Me.ProgressBar1.Name = "ProgressBar1" Me.ProgressBar1.Size = New System.Drawing.Size(216, 23) Me.ProgressBar1.TabIndex = 1 ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(232, 56) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 2 Me.Button1.Text = "キャンセル" ' 'ProgressForm ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12) Me.ClientSize = New System.Drawing.Size(320, 85) Me.Controls.Add(Me.Button1) Me.Controls.Add(Me.ProgressBar1) Me.Controls.Add(Me.Label1) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog Me.MaximizeBox = False Me.MinimizeBox = False Me.Name = "ProgressForm" Me.Text = "ProgressForm" Me.ResumeLayout(False) End Sub #End Region End Class ''' <summary> ''' 進行状況ダイアログを表示するためのクラス ''' </summary> Public Class ProgressDialog Implements IDisposable 'キャンセルボタンがクリックされたか Private _canceled As Boolean = False Private ReadOnly canceledSyncObject = New Object Private Property canceled() As Boolean Get SyncLock (canceledSyncObject) Return Me._canceled End SyncLock End Get Set(ByVal Value As Boolean) SyncLock (canceledSyncObject) Me._canceled = Value End SyncLock End Set End Property 'ダイアログフォーム Private form As ProgressForm 'フォームが表示されるまで待機するための待機ハンドル Private startEvent As System.Threading.ManualResetEvent 'フォームが一度表示されたか Private showed As Boolean = False 'フォームをコードで閉じているか Private closing As Boolean = False 'オーナーフォーム Private ownerForm As form '別処理をするためのスレッド Private thread As System.Threading.Thread 'フォームのタイトル Private _title As String = "進行状況" 'ProgressBarの最小、最大、現在の値 Private _minimum As Integer = 0 Private _maximum As Integer = 100 Private _value As Integer = 0 '表示するメッセージ Private _message As String = "" ''' <summary> ''' ダイアログのタイトルバーに表示する文字列 ''' </summary> Public Property Title() As String Get Return _title End Get Set(ByVal Value As String) _title = Value If Not (form Is Nothing) Then form.Invoke(New MethodInvoker(AddressOf SetTitle)) End If End Set End Property ''' <summary> ''' プログレスバーの最小値 ''' </summary> Public Property Minimum() As Integer Get Return _minimum End Get Set(ByVal Value As Integer) _minimum = Value If Not (form Is Nothing) Then form.Invoke(New MethodInvoker( _ AddressOf SetProgressMinimum)) End If End Set End Property ''' <summary> ''' プログレスバーの最大値 ''' </summary> Public Property Maximum() As Integer Get Return _maximum End Get Set(ByVal Value As Integer) _maximum = Value If Not (form Is Nothing) Then form.Invoke(New MethodInvoker( _ AddressOf SetProgressMaximun)) End If End Set End Property ''' <summary> ''' プログレスバーの値 ''' </summary> Public Property Value() As Integer Get Return _value End Get Set(ByVal Value As Integer) _value = Value If Not (form Is Nothing) Then form.Invoke(New MethodInvoker( _ AddressOf SetProgressValue)) End If End Set End Property ''' <summary> ''' ダイアログに表示するメッセージ ''' </summary> Public Property Message() As String Get Return _message End Get Set(ByVal Value As String) _message = Value If Not (form Is Nothing) Then form.Invoke(New MethodInvoker(AddressOf SetMessage)) End If End Set End Property ''' <summary> ''' キャンセルされたか ''' </summary> Public ReadOnly Property Canceled() As Boolean Get Return _canceled End Get End Property ''' <summary> ''' ダイアログを表示する ''' </summary> ''' <param name="owner"> ''' ownerの中央にダイアログが表示される ''' </param> ''' <remarks> ''' このメソッドは一回しか呼び出せません。 ''' </remarks> Public Overloads Sub Show(ByVal owner As form) If showed Then Throw New Exception("ダイアログは一度表示されています。") End If showed = True _canceled = False startEvent = New System.Threading.ManualResetEvent(False) ownerForm = owner 'スレッドを作成 thread = New System.Threading.Thread( _ New System.Threading.ThreadStart(AddressOf Run)) thread.IsBackground = True Me.thread.ApartmentState = System.Threading.ApartmentState.STA thread.Start() 'フォームが表示されるまで待機する startEvent.WaitOne() End Sub Public Overloads Sub Show() Show(Nothing) End Sub '別スレッドで処理するメソッド Private Sub Run() 'フォームの設定 form = New ProgressForm form.Text = _title AddHandler form.Button1.Click, AddressOf Button1_Click AddHandler form.Closing, AddressOf form_Closing AddHandler form.Activated, AddressOf form_Activated form.ProgressBar1.Minimum = _minimum form.ProgressBar1.Maximum = _maximum form.ProgressBar1.Value = _value 'フォームの表示位置をオーナーの中央へ If Not (ownerForm Is Nothing) Then form.StartPosition = FormStartPosition.Manual form.Left = _ ownerForm.Left + (ownerForm.Width - form.Width) \ 2 form.Top = _ ownerForm.Top + (ownerForm.Height - form.Height) \ 2 End If 'フォームの表示 form.ShowDialog() form.Dispose() End Sub ''' <summary> ''' ダイアログを閉じる ''' </summary> Public Sub Close() closing = True form.Invoke(New MethodInvoker(AddressOf form.Close)) End Sub 'Close Public Sub Dispose() Implements System.IDisposable.Dispose form.Invoke(New MethodInvoker(AddressOf form.Dispose)) End Sub Private Sub SetProgressValue() If Not (form Is Nothing) And Not form.IsDisposed Then form.ProgressBar1.Value = _value End If End Sub Private Sub SetMessage() If Not (form Is Nothing) And Not form.IsDisposed Then form.Label1.Text = _message End If End Sub Private Sub SetTitle() If Not (form Is Nothing) And Not form.IsDisposed Then form.Text = _title End If End Sub Private Sub SetProgressMaximun() If Not (form Is Nothing) And Not form.IsDisposed Then form.ProgressBar1.Maximum = _maximum End If End Sub Private Sub SetProgressMinimum() If Not (form Is Nothing) And Not form.IsDisposed Then form.ProgressBar1.Minimum = _minimum End If End Sub Private Sub Button1_Click(ByVal sender As Object, _ ByVal e As EventArgs) _canceled = True End Sub Private Sub form_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) If Not closing Then e.Cancel = True _canceled = True End If End Sub Private Sub form_Activated(ByVal sender As Object, _ ByVal e As EventArgs) RemoveHandler form.Activated, AddressOf form_Activated startEvent.Set() End Sub End Class
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; /// <summary> /// ProgressForm の概要の説明です。 /// </summary> public class ProgressForm : System.Windows.Forms.Form { internal System.Windows.Forms.Label Label1; internal System.Windows.Forms.ProgressBar ProgressBar1; internal System.Windows.Forms.Button Button1; /// <summary> /// 必要なデザイナ変数です。 /// </summary> private System.ComponentModel.Container components = null; public ProgressForm() { // // Windows フォーム デザイナ サポートに必要です。 // InitializeComponent(); // // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。 // } /// <summary> /// 使用されているリソースに後処理を実行します。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows フォーム デザイナで生成されたコード /// <summary> /// デザイナ サポートに必要なメソッドです。このメソッドの内容を /// コード エディタで変更しないでください。 /// </summary> private void InitializeComponent() { this.Label1 = new System.Windows.Forms.Label(); this.ProgressBar1 = new System.Windows.Forms.ProgressBar(); this.Button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // Label1 // this.Label1.Location = new System.Drawing.Point(16, 8); this.Label1.Name = "Label1"; this.Label1.Size = new System.Drawing.Size(288, 40); this.Label1.TabIndex = 0; // // ProgressBar1 // this.ProgressBar1.Location = new System.Drawing.Point(8, 48); this.ProgressBar1.Name = "ProgressBar1"; this.ProgressBar1.Size = new System.Drawing.Size(216, 23); this.ProgressBar1.TabIndex = 1; // // Button1 // this.Button1.Location = new System.Drawing.Point(232, 48); this.Button1.Name = "Button1"; this.Button1.TabIndex = 2; this.Button1.Text = "キャンセル"; // // ProgressForm // this.AutoScaleBaseSize = new System.Drawing.Size(5, 12); this.ClientSize = new System.Drawing.Size(320, 85); this.Controls.Add(this.Button1); this.Controls.Add(this.ProgressBar1); this.Controls.Add(this.Label1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "ProgressForm"; this.ShowInTaskbar = false; this.Text = "ProgressForm"; this.ResumeLayout(false); } #endregion } /// <summary> /// 進行状況ダイアログを表示するためのクラス /// </summary> public class ProgressDialog : IDisposable { //キャンセルボタンがクリックされたか private volatile bool _canceled = false; //ダイアログフォーム private volatile ProgressForm form; //フォームが表示されるまで待機するための待機ハンドル private System.Threading.ManualResetEvent startEvent; //フォームが一度表示されたか private bool showed = false; //フォームをコードで閉じているか private volatile bool closing = false; //オーナーフォーム private Form ownerForm; //別処理をするためのスレッド private System.Threading.Thread thread; //フォームのタイトル private volatile string _title = "進行状況"; //ProgressBarの最小、最大、現在の値 private volatile int _minimum = 0; private volatile int _maximum = 100; private volatile int _value = 0; //表示するメッセージ private volatile string _message = ""; /// <summary> /// ダイアログのタイトルバーに表示する文字列 /// </summary> public string Title { set { _title = value; if (form != null) form.Invoke(new MethodInvoker(SetTitle)); } get { return _title; } } /// <summary> /// プログレスバーの最小値 /// </summary> public int Minimum { set { _minimum = value; if (form != null) form.Invoke(new MethodInvoker(SetProgressMinimum)); } get { return _minimum; } } /// <summary> /// プログレスバーの最大値 /// </summary> public int Maximum { set { _maximum = value; if (form != null) form.Invoke(new MethodInvoker(SetProgressMaximun)); } get { return _maximum; } } /// <summary> /// プログレスバーの値 /// </summary> public int Value { set { _value = value; if (form != null) form.Invoke(new MethodInvoker(SetProgressValue)); } get { return _value; } } /// <summary> /// ダイアログに表示するメッセージ /// </summary> public string Message { set { _message = value; if (form != null) form.Invoke(new MethodInvoker(SetMessage)); } get { return _message; } } /// <summary> /// キャンセルされたか /// </summary> public bool Canceled { get { return _canceled; } } /// <summary> /// ダイアログを表示する /// </summary> /// <param name="owner"> /// ownerの中央にダイアログが表示される /// </param> /// <remarks> /// このメソッドは一回しか呼び出せません。 /// </remarks> public void Show(Form owner) { if (showed) throw new Exception("ダイアログは一度表示されています。"); showed = true; _canceled = false; startEvent = new System.Threading.ManualResetEvent(false); ownerForm = owner; //スレッドを作成 thread = new System.Threading.Thread( new System.Threading.ThreadStart(Run)); thread.IsBackground = true; this.thread.ApartmentState = System.Threading.ApartmentState.STA; thread.Start(); //フォームが表示されるまで待機する startEvent.WaitOne(); } public void Show() { Show(null); } //別スレッドで処理するメソッド private void Run() { //フォームの設定 form = new ProgressForm(); form.Text = _title; form.Button1.Click += new EventHandler(Button1_Click); form.Closing += new CancelEventHandler(form_Closing); form.Activated += new EventHandler(form_Activated); form.ProgressBar1.Minimum = _minimum; form.ProgressBar1.Maximum = _maximum; form.ProgressBar1.Value = _value; //フォームの表示位置をオーナーの中央へ if (ownerForm != null) { form.StartPosition = FormStartPosition.Manual; form.Left = ownerForm.Left + (ownerForm.Width - form.Width) / 2; form.Top = ownerForm.Top + (ownerForm.Height - form.Height) / 2; } //フォームの表示 form.ShowDialog(); form.Dispose(); } /// <summary> /// ダイアログを閉じる /// </summary> public void Close() { closing = true; form.Invoke(new MethodInvoker(form.Close)); } public void Dispose() { form.Invoke(new MethodInvoker(form.Dispose)); } private void SetProgressValue() { if (form != null && !form.IsDisposed) form.ProgressBar1.Value = _value; } private void SetMessage() { if (form != null && !form.IsDisposed) form.Label1.Text = _message; } private void SetTitle() { if (form != null && !form.IsDisposed) form.Text = _title; } private void SetProgressMaximun() { if (form != null && !form.IsDisposed) form.ProgressBar1.Maximum = _maximum; } private void SetProgressMinimum() { if (form != null && !form.IsDisposed) form.ProgressBar1.Minimum = _minimum; } private void Button1_Click(object sender, EventArgs e) { _canceled = true; } private void form_Closing(object sender, CancelEventArgs e) { if (!closing) { e.Cancel = true; _canceled = true; } } private void form_Activated(object sender, EventArgs e) { form.Activated -= new EventHandler(form_Activated); startEvent.Set(); } }
次にこのクラスの使用法を示します。このコードはメインフォームから呼び出されるものとします。
Dim pd As New ProgressDialog 'ダイアログのタイトルを設定 pd.Title = "カウントアップ" 'プログレスバーの最小値を設定 pd.Minimum = 0 'プログレスバーの最大値を設定 pd.Maximum = 10 'プログレスバーの初期値を設定 pd.Value = 0 '進行状況ダイアログを表示する pd.Show(Me) '処理を開始 Dim i As Integer For i = 1 To 10 'プログレスバーの値を変更する pd.Value = i 'メッセージを変更する pd.Message = i.ToString() + "番目を処理中..." 'キャンセルされた時はループを抜ける If pd.Canceled Then Exit For End If '1秒間待機する(本来なら何らかの処理を行う) System.Threading.Thread.Sleep(1000) Next i 'ダイアログを閉じる pd.Close()
ProgressDialog pd = new ProgressDialog(); //ダイアログのタイトルを設定 pd.Title = "カウントアップ"; //プログレスバーの最小値を設定 pd.Minimum = 0; //プログレスバーの最大値を設定 pd.Maximum = 10; //プログレスバーの初期値を設定 pd.Value = 0; //進行状況ダイアログを表示する pd.Show(this); //処理を開始 for (int i = 1; i <= 10; i++) { //プログレスバーの値を変更する pd.Value = i; //メッセージを変更する pd.Message = i.ToString() + "番目を処理中..."; //キャンセルされた時はループを抜ける if (pd.Canceled) break; //1秒間待機する(本来なら何らかの処理を行う) System.Threading.Thread.Sleep(1000); } //ダイアログを閉じる pd.Close();
ここで紹介した方法では、メインフォームと同じスレッドで時間のかかる処理をしているため、ダイアログが表示されている間、メインフォームは基本的に再描画されません。この問題は、時間のかかる処理を別スレッドにすることにより解決できます。この方法により進行状況ダイアログを表示するサンプルが、「The Code Project - A .NET Progress Dialog」で紹介されています。
(この記事は、「.NETプログラミング研究 第45号」で紹介したものを基にしています。)