.NETプログラミング研究 第45号 †
.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にあたる処理は省略しています。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
| | Public Class ProgressForm
Inherits System.Windows.Forms.Form
#Region " Windows フォーム デザイナで生成されたコード "
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
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
Private components As System.ComponentModel.IContainer
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()
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
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
Me.Button1.Location = New System.Drawing.Point(232, 56)
Me.Button1.Name = "Button1"
Me.Button1.TabIndex = 2
Me.Button1.Text = "キャンセル"
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 = "進行状況"
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
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
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
| | using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
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()
{
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();
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;
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;
this.Button1.Location = new System.Drawing.Point(232, 48);
this.Button1.Name = "Button1";
this.Button1.TabIndex = 2;
this.Button1.Text = "キャンセル";
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 = "進行状況";
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; }
}
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();
}
}
|
次にこのクラスの使用法を示します。このコードはメインフォームから呼び出されるものとします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| | 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
System.Threading.Thread.Sleep(1000)
Next i
pd.Close()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| | 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;
System.Threading.Thread.Sleep(1000);
}
pd.Close();
|
ここで紹介した方法では、メインフォームと同じスレッドで時間のかかる処理をしているため、ダイアログが表示されている間、メインフォームは基本的に再描画されません。この問題は、時間のかかる処理を別スレッドにすることにより解決できます。この方法により進行状況ダイアログを表示するサンプルが、下の「参考」の「The Code Project - A .NET Progress Dialog」で紹介されています。
参考:
コメント †