- 追加された行はこの色です。
- 削除された行はこの色です。
#title(.NETプログラミング研究 第45号)
#navi(.NETプログラミング研究)
#contents
*.NETプログラミング研究 第45号 [#z31e25ab]
**.NET Tips [#vaa1db20]
***進行状況ダイアログを表示する [#qf3d3efd]
#column(注意){{
この記事の最新版は「[[進行状況ダイアログを表示する>http://dobon.net/vb/dotnet/programing/progressdialog.html]]」で公開しています。
この記事の最新版は「[[進行状況ダイアログを表示する>https://dobon.net/vb/dotnet/programing/progressdialog.html]]」で公開しています。
}}
前回は時間のかかる処理で進行状況を表示する方法と、さらにキャンセルボタンを付ける方法を紹介しました。その記事の最後でダイアログ使って進行状況を表示し、キャンセルできるようにするサンプルを私のサイトで紹介すると書きましたが、それをここで紹介します。
このような進行状況ダイアログを作成するには、前号で説明したとおり、Application.DoEventsメソッドを使うか、マルチスレッドを使うということになります。
DoEventsメソッドを使って進行状況ダイアログを表示する場合、前回紹介したような欠点があるだけでなく、別の問題が生じます。というのは、本来ならば進行状況ダイアログはShowDialogメソッドによりモーダルで表示したいところですが、モーダルで表示するとShowDialog以降のコードはダイアログが閉じられるまで実行されないため、進行状況を示したい時間のかかる処理はダイアログのクラス内に記述(あるいは、デリゲートやイベントを使用)しなければならなくなります。ダイアログを表示し、その直後に時間のかかる処理を記述できるようにするには、ダイアログはモードレスでメインフォームをオーナーとして表示し、メインフォームのEnabledプロパティをFalseにして操作できないようにするといったごまかしが必要になります。
ここではこのようなDoEventsメソッドを使った方法ではなく、マルチスレッドによる方法のコードのみを紹介します(DoEventsメソッドを使った方法は今までの応用で簡単にできるでしょう)。マルチスレッドによる方法では、上記のような問題はクリアされます。
下に進行状況ダイアログを表示するためのクラス(ProgressDialogクラス)のサンプルを示します。ここでProgressFormクラスはVisual Studio .NETのフォームデザイナで作成されたフォームで、プログレスバーとキャンセルボタン、さらにメッセージを表示するラベルが配置されており、進行状況ダイアログとして表示されるものですが、このフォームのShowDialogメソッドでダイアログを表示されるのではなく、ProgressDialogクラスを使って進行状況ダイアログを表示、操作します。
なお.NETのマルチスレッドプログラミングについて、ここでは一切説明しません。これらについて詳しくは、このメールマガジンの第19号から第26号等を参考にしてください。
(VB.NETのコードは、C#のコードを「C# to VB.NET Translator」を使って変換し、修正を加えたものです。C#のvolatileにあたる処理は省略しています。)
-[[C# to VB.NET Translator>http://authors.aspalliance.com/aldotnet/examples/translate.aspx]]
#code(vbnet){{
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 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 _message
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
}}
#code(csharp){{
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 _message;
}
}
/// <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();
}
}
}}
次にこのクラスの使用法を示します。このコードはメインフォームから呼び出されるものとします。
#code(vbnet){{
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()
}}
#code(csharp){{
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」で紹介されています。
参考:
-[[Windows Forms FAQ | 14.1 How to display a status dialog in a background thread during a long operation and alow the user to cancel?>http://www.syncfusion.com/faq/winforms/search/490.asp]]
-[[The Code Project - A .NET Progress Dialog>http://www.codeproject.com/cs/miscctrl/progressdialog.asp]]
**コメント [#nda38e15]
#comment
//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2004-11-09 (火) 06:00:00,DOBON!,2010-03-21 (日) 19:44:21,DOBON!)