This repository has been archived by the owner on Oct 16, 2020. It is now read-only.
/
AsynchronousWaitDialog.cs
320 lines (282 loc) · 10.1 KB
/
AsynchronousWaitDialog.cs
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
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.SharpDevelop.Gui
{
internal sealed partial class AsynchronousWaitDialogForm
{
internal AsynchronousWaitDialogForm(bool allowCancel)
{
InitializeComponent();
cancelButton.Text = ResourceService.GetString("Global.CancelButtonText");
if (allowCancel) {
cancelButton.Visible = true;
progressBar.Width = cancelButton.Left - 8 - progressBar.Left;
} else {
cancelButton.Visible = false;
progressBar.Width = cancelButton.Right - progressBar.Left;
}
}
}
/// <summary>
/// Shows an wait dialog on a separate thread if the action takes longer than 500ms.
/// Usage:
/// using (AsynchronousWaitDialog.ShowWaitDialog("title")) {
/// long_running_action();
/// }
/// or:
/// using (IProgressMonitor monitor = AsynchronousWaitDialog.ShowWaitDialog("title")) {
/// long_running_action(monitor);
/// }
/// </summary>
public sealed class AsynchronousWaitDialog : IProgressMonitor
{
/// <summary>
/// Delay until the wait dialog becomes visible, in ms.
/// </summary>
public const int ShowWaitDialogDelay = 500;
readonly string titleName;
readonly string defaultTaskName;
readonly CancellationTokenSource cancellation;
readonly ProgressCollector collector;
readonly SynchronizationHelper synchronizationHelper = new SynchronizationHelper();
AsynchronousWaitDialogForm dlg;
#region Constructors
/// <summary>
/// Shows a wait dialog.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName)
{
return ShowWaitDialog(titleName, null, false);
}
/// <summary>
/// Shows a wait dialog.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="allowCancel">Specifies whether a cancel button should be shown.</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName, bool allowCancel)
{
return ShowWaitDialog(titleName, null, allowCancel);
}
/// <summary>
/// Shows a wait dialog that does not support cancelling.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="allowCancel">Specifies whether a cancel button should be shown.</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName, string defaultTaskName, bool allowCancel)
{
if (titleName == null)
throw new ArgumentNullException("titleName");
AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, allowCancel);
h.Start();
return h;
}
/// <summary>
/// Shows a wait dialog that does supports cancelling.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <param name="action">The action to run within the wait dialog</param>
public static void RunInCancellableWaitDialog(string titleName, string defaultTaskName, Action<AsynchronousWaitDialog> action)
{
if (titleName == null)
throw new ArgumentNullException("titleName");
using (AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, true)) {
h.Start();
try {
action(h);
} catch (OperationCanceledException ex) {
// consume OperationCanceledException
if (ex.CancellationToken != h.CancellationToken)
throw;
}
}
}
private AsynchronousWaitDialog(string titleName, string defaultTaskName, bool allowCancel)
{
this.titleName = StringParser.Parse(titleName);
this.defaultTaskName = StringParser.Parse(defaultTaskName ?? "${res:Global.PleaseWait}");
if (allowCancel)
this.cancellation = new CancellationTokenSource();
this.collector = new ProgressCollector(synchronizationHelper, allowCancel ? cancellation.Token : CancellationToken.None);
}
#endregion
#region SynchronizationHelper
// this class works around the issue that we don't initially have an ISynchronizeInvoke implementation
// for the target thread, we only create it after ShowWaitDialogDelay.
sealed class SynchronizationHelper : ISynchronizeInvoke
{
volatile ISynchronizeInvoke targetSynchronizeInvoke;
public bool InvokeRequired {
get {
ISynchronizeInvoke si = targetSynchronizeInvoke;
return si != null && si.InvokeRequired;
}
}
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
ISynchronizeInvoke si = targetSynchronizeInvoke;
if (si != null)
return si.BeginInvoke(method, args);
else {
// When target is not available, invoke method on current thread, but use a lock
// to ensure we don't run multiple methods concurrently.
lock (this) {
method.DynamicInvoke(args);
return null;
}
// yes this is morally questionable - maybe it would be better to enqueue all invocations and run them later?
// ProgressCollector would have to avoid enqueuing stuff multiple times for all kinds of updates
// (currently it does this only with updates to the Progress property)
}
}
public void SetTarget(ISynchronizeInvoke targetSynchronizeInvoke)
{
lock (this) {
this.targetSynchronizeInvoke = targetSynchronizeInvoke;
}
}
public object EndInvoke(IAsyncResult result)
{
throw new NotSupportedException();
}
public object Invoke(Delegate method, object[] args)
{
throw new NotSupportedException();
}
}
#endregion
#region Start waiting thread
/// <summary>
/// Start waiting thread
/// </summary>
internal void Start()
{
Thread newThread = new Thread(Run);
newThread.Name = "AsynchronousWaitDialog thread";
newThread.Start();
Thread.Sleep(0); // allow new thread to start
}
[STAThread]
void Run()
{
Thread.Sleep(ShowWaitDialogDelay);
if (collector.ProgressMonitorIsDisposed)
return;
dlg = new AsynchronousWaitDialogForm(cancellation != null);
dlg.Text = titleName;
dlg.cancelButton.Click += CancelButtonClick;
dlg.CreateControl();
IntPtr h = dlg.Handle; // force handle creation
// ensure events occur on this thread, then register event handlers
synchronizationHelper.SetTarget(dlg);
collector.ProgressMonitorDisposed += progress_ProgressMonitorDisposed;
collector.PropertyChanged += progress_PropertyChanged;
// check IsDisposed once again (we might have missed an event while we initialized the dialog):
if (collector.ProgressMonitorIsDisposed) {
dlg.Dispose();
return;
}
progress_PropertyChanged(null, new System.ComponentModel.PropertyChangedEventArgs("TaskName"));
if (collector.ShowingDialog) {
Application.Run();
} else {
Application.Run(dlg);
}
}
#endregion
/// <summary>
/// Closes the wait dialog.
/// </summary>
void progress_ProgressMonitorDisposed(object sender, EventArgs e)
{
dlg.Dispose();
Application.ExitThread();
}
bool reshowTimerRunning = false;
void progress_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// show/hide dialog as required by ShowingDialog
if (dlg.Visible == collector.ShowingDialog) {
if (collector.ShowingDialog) {
dlg.Hide();
} else if (!reshowTimerRunning) {
reshowTimerRunning = true;
var timer = new System.Windows.Forms.Timer();
timer.Interval = 100;
timer.Tick += delegate {
timer.Dispose();
reshowTimerRunning = false;
if (!collector.ShowingDialog)
dlg.Show();
};
timer.Start();
}
}
// update task name and progress
if (e.PropertyName == "TaskName")
dlg.taskLabel.Text = collector.TaskName ?? defaultTaskName;
if (double.IsNaN(collector.Progress)) {
dlg.progressBar.Style = ProgressBarStyle.Marquee;
} else {
dlg.progressBar.Style = ProgressBarStyle.Continuous;
dlg.progressBar.Value = Math.Max(0, Math.Min(100, (int)(collector.Progress * 100)));
}
}
void CancelButtonClick(object sender, EventArgs e)
{
dlg.cancelButton.Enabled = false;
if (cancellation != null)
cancellation.Cancel();
}
#region IProgressMonitor interface impl (forwards to progress.ProgressMonitor)
/// <inheritdoc/>
public string TaskName {
get { return collector.ProgressMonitor.TaskName; }
set { collector.ProgressMonitor.TaskName = value; }
}
/// <inheritdoc/>
public double Progress {
get { return collector.ProgressMonitor.Progress; }
set { collector.ProgressMonitor.Progress = value; }
}
/// <inheritdoc/>
public bool ShowingDialog {
get { return collector.ProgressMonitor.ShowingDialog; }
set { collector.ProgressMonitor.ShowingDialog = value; }
}
/// <inheritdoc/>
public OperationStatus Status {
get { return collector.ProgressMonitor.Status; }
set { collector.ProgressMonitor.Status = value; }
}
/// <inheritdoc/>
public CancellationToken CancellationToken {
get { return collector.ProgressMonitor.CancellationToken; }
}
/// <inheritdoc/>
public IProgressMonitor CreateSubTask(double workAmount)
{
return collector.ProgressMonitor.CreateSubTask(workAmount);
}
public void Dispose()
{
collector.ProgressMonitor.Dispose();
}
#endregion
}
}