┏第45号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ──<メニュー>─────────────────────── ■.NET Tips ・進行状況ダイアログを表示する ─────────────────────────────── ─────────────────────────────── ■.NET Tips ─────────────────────────────── ●進行状況ダイアログを表示する 前回は時間のかかる処理で進行状況を表示する方法と、さらにキャン セルボタンを付ける方法を紹介しました。その記事の最後でダイアロ グ使って進行状況を表示し、キャンセルできるようにするサンプルを 私のサイトで紹介すると書きましたが、それをここで紹介します。 このような進行状況ダイアログを作成するには、前号で説明したとお り、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にあたる処理は 省略しています。) [URL]C# to VB.NET Translator http://authors.aspalliance.com/aldotnet/examples/translate.aspx ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 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 ''' ''' 進行状況ダイアログを表示するためのクラス ''' 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 = "" ''' ''' ダイアログのタイトルバーに表示する文字列 ''' 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 ''' ''' プログレスバーの最小値 ''' 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 ''' ''' プログレスバーの最大値 ''' 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 ''' ''' プログレスバーの値 ''' 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 ''' ''' ダイアログに表示するメッセージ ''' 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 ''' ''' キャンセルされたか ''' Public ReadOnly Property Canceled() As Boolean Get Return _canceled End Get End Property ''' ''' ダイアログを表示する ''' ''' ''' ownerの中央にダイアログが表示される ''' ''' ''' このメソッドは一回しか呼び出せません。 ''' 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 ''' ''' ダイアログを閉じる ''' 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 ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; /// /// ProgressForm の概要の説明です。 /// 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; /// /// 必要なデザイナ変数です。 /// private System.ComponentModel.Container components = null; public ProgressForm() { // // Windows フォーム デザイナ サポートに必要です。 // InitializeComponent(); // // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。 // } /// /// 使用されているリソースに後処理を実行します。 /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows フォーム デザイナで生成されたコード /// /// デザイナ サポートに必要なメソッドです。このメソッドの内容を /// コード エディタで変更しないでください。 /// 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 } /// /// 進行状況ダイアログを表示するためのクラス /// 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 = ""; /// /// ダイアログのタイトルバーに表示する文字列 /// public string Title { set { _title = value; if (form != null) form.Invoke(new MethodInvoker(SetTitle)); } get { return _message; } } /// /// プログレスバーの最小値 /// public int Minimum { set { _minimum = value; if (form != null) form.Invoke(new MethodInvoker(SetProgressMinimum)); } get { return _minimum; } } /// /// プログレスバーの最大値 /// public int Maximum { set { _maximum = value; if (form != null) form.Invoke(new MethodInvoker(SetProgressMaximun)); } get { return _maximum; } } /// /// プログレスバーの値 /// public int Value { set { _value = value; if (form != null) form.Invoke(new MethodInvoker(SetProgressValue)); } get { return _value; } } /// /// ダイアログに表示するメッセージ /// public string Message { set { _message = value; if (form != null) form.Invoke(new MethodInvoker(SetMessage)); } get { return _message; } } /// /// キャンセルされたか /// public bool Canceled { get { return _canceled; } } /// /// ダイアログを表示する /// /// /// ownerの中央にダイアログが表示される /// /// /// このメソッドは一回しか呼び出せません。 /// 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(); } /// /// ダイアログを閉じる /// 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(); } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 次にこのクラスの使用法を示します。このコードはメインフォームか ら呼び出されるものとします。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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() ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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(); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ここで紹介した方法では、メインフォームと同じスレッドで時間のか かる処理をしているため、ダイアログが表示されている間、メインフ ォームは基本的に再描画されません。この問題は、時間のかかる処理 を別スレッドにすることにより解決できます。この方法により進行状 況ダイアログを表示するサンプルが、下の「参考」の「The Code Project - A .NET Progress Dialog」で紹介されています。 参考: [URL]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 [URL]The Code Project - A .NET Progress Dialog http://www.codeproject.com/cs/miscctrl/progressdialog.asp =============================== ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2004-Oct 2005)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 - 2004 DOBON! All rights reserved. ===============================