diff --git a/Nuclex.Windows.Forms.csproj b/Nuclex.Windows.Forms.csproj
index 1b69cdd..3f92341 100644
--- a/Nuclex.Windows.Forms.csproj
+++ b/Nuclex.Windows.Forms.csproj
@@ -84,6 +84,14 @@
false
EmbeddedControlCollection
+
+ Form
+ false
+ ProgressReporterForm
+
+
+ ProgressReporterForm.cs
+
false
TrackingBar
@@ -101,6 +109,14 @@
Nuclex.Support %28PC%29
+
+
+ Designer
+ false
+ ProgressReporterForm
+ ProgressReporterForm.cs
+
+
diff --git a/Source/ProgressReporter/ProgressReporterForm.Designer.cs b/Source/ProgressReporter/ProgressReporterForm.Designer.cs
new file mode 100644
index 0000000..7768ada
--- /dev/null
+++ b/Source/ProgressReporter/ProgressReporterForm.Designer.cs
@@ -0,0 +1,99 @@
+namespace Nuclex.Windows.Forms {
+ partial class ProgressReporterForm {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing) {
+ if(disposing && (components != null)) {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent() {
+ this.components = new System.ComponentModel.Container();
+ this.cancelButton = new System.Windows.Forms.Button();
+ this.progressBar = new System.Windows.Forms.ProgressBar();
+ this.statusLabel = new System.Windows.Forms.Label();
+ this.controlCreationTimer = new System.Windows.Forms.Timer(this.components);
+ this.SuspendLayout();
+ //
+ // cancelButton
+ //
+ this.cancelButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
+ this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.cancelButton.Location = new System.Drawing.Point(151, 55);
+ this.cancelButton.Name = "cancelButton";
+ this.cancelButton.Size = new System.Drawing.Size(75, 23);
+ this.cancelButton.TabIndex = 0;
+ this.cancelButton.Text = "&Cancel";
+ this.cancelButton.UseVisualStyleBackColor = true;
+ this.cancelButton.Click += new System.EventHandler(this.cancelClicked);
+ //
+ // progressBar
+ //
+ this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.progressBar.Location = new System.Drawing.Point(12, 26);
+ this.progressBar.Name = "progressBar";
+ this.progressBar.Size = new System.Drawing.Size(352, 23);
+ this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+ this.progressBar.TabIndex = 1;
+ //
+ // statusLabel
+ //
+ this.statusLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.statusLabel.Location = new System.Drawing.Point(12, 9);
+ this.statusLabel.Name = "statusLabel";
+ this.statusLabel.Size = new System.Drawing.Size(352, 14);
+ this.statusLabel.TabIndex = 2;
+ this.statusLabel.Text = "Please Wait...";
+ this.statusLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter;
+ //
+ // controlCreationTimer
+ //
+ this.controlCreationTimer.Enabled = true;
+ this.controlCreationTimer.Interval = 1;
+ this.controlCreationTimer.Tick += new System.EventHandler(this.controlCreationTimerTicked);
+ //
+ // ProgressReporterForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.CancelButton = this.cancelButton;
+ this.ClientSize = new System.Drawing.Size(376, 90);
+ this.ControlBox = false;
+ this.Controls.Add(this.statusLabel);
+ this.Controls.Add(this.progressBar);
+ this.Controls.Add(this.cancelButton);
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "ProgressReporterForm";
+ this.ShowIcon = false;
+ this.ShowInTaskbar = false;
+ this.Text = "Progress";
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Button cancelButton;
+ private System.Windows.Forms.ProgressBar progressBar;
+ private System.Windows.Forms.Label statusLabel;
+ private System.Windows.Forms.Timer controlCreationTimer;
+ }
+}
\ No newline at end of file
diff --git a/Source/ProgressReporter/ProgressReporterForm.cs b/Source/ProgressReporter/ProgressReporterForm.cs
new file mode 100644
index 0000000..736cb06
--- /dev/null
+++ b/Source/ProgressReporter/ProgressReporterForm.cs
@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Text;
+using System.Windows.Forms;
+using System.Threading;
+
+using Nuclex.Support.Tracking;
+using Nuclex.Support.Scheduling;
+
+namespace Nuclex.Windows.Forms {
+
+ ///
+ /// Blocking progress dialog that prevents the user from accessing the application
+ /// window during all-blocking background processes.
+ ///
+ public partial class ProgressReporterForm : Form {
+
+ /// Initializes a new progress reporter
+ internal ProgressReporterForm() {
+ InitializeComponent();
+
+ this.updateProgressDelegate = new MethodInvoker(updateProgress);
+ this.asyncEndedDelegate = new EventHandler(asyncEnded);
+ this.asyncProgressUpdatedDelegate = new EventHandler(
+ asyncProgressUpdated
+ );
+ }
+
+ ///
+ /// Shows the progress reporter until the specified progression has ended.
+ ///
+ ///
+ /// Progression for whose duration to show the progress reporter
+ ///
+ public static void Track(Progression progression) {
+ Track(null, progression);
+ }
+
+ ///
+ /// Shows the progress reporter until the specified progression has ended.
+ ///
+ ///
+ /// Text to be shown in the progress reporter's title bar
+ ///
+ ///
+ /// Progression for whose duration to show the progress reporter
+ ///
+ public static void Track(string windowTitle, Progression progression) {
+
+ // Small optimization to avoid the lengthy control creation when
+ // the progression has already ended
+ if(progression.Ended)
+ return;
+
+ // Open the form and let it monitor the progression's state
+ using(ProgressReporterForm theForm = new ProgressReporterForm()) {
+ theForm.track(windowTitle, progression);
+ }
+
+ }
+
+ /// Called when the user tries to close the form manually
+ /// Contains flag that can be used to abort the close attempt
+ protected override void OnClosing(CancelEventArgs e) {
+ base.OnClosing(e);
+
+ // Only allow the form to close when the form is ready to close and the
+ // progression being tracked has also finished.
+ e.Cancel = (this.state < 2);
+ }
+
+ ///
+ /// Shows the progress reporter until the specified progression has ended.
+ ///
+ ///
+ /// Text to be shown in the progress reporter's title bar
+ ///
+ ///
+ /// Progression for whose duration to show the progress reporter
+ ///
+ private void track(string windowTitle, Progression progression) {
+
+ // Set the window title if the user wants to use a custom one
+ if(windowTitle != null)
+ Text = windowTitle;
+
+ // Only enable the cancel button if the progression can be aborted
+ this.cancelButton.Enabled = (progression is IAbortable);
+
+ // Subscribe the form to the progression it is supposed to monitor
+ progression.AsyncEnded += this.asyncEndedDelegate;
+ progression.AsyncProgressUpdated += this.asyncProgressUpdatedDelegate;
+
+ // The progression might have ended before this line was reached, if that's
+ // the case, we don't show the dialog at all.
+ if(!progression.Ended)
+ ShowDialog();
+
+ // We're done, unsubscribe from the progression's events
+ progression.AsyncProgressUpdated -= this.asyncProgressUpdatedDelegate;
+ progression.AsyncEnded -= this.asyncEndedDelegate;
+
+ }
+
+ /// Called when the progression has ended
+ /// Progression that has ended
+ /// Not used
+ private void asyncEnded(object sender, EventArgs arguments) {
+
+ // If the new state is 2, the form was ready to close (since the state
+ // is incremented once when the form becomes ready to be closed)
+ int newState = Interlocked.Increment(ref this.state);
+ if(newState == 2) {
+
+ // Close the dialog. Ensure the Close() method is invoked from the
+ // same thread the dialog was created in.
+ if(InvokeRequired)
+ Invoke(new MethodInvoker(Close));
+ else
+ Close();
+
+ }
+
+ }
+
+ /// Called when the tracked progression's progress updates
+ /// Progression whose progress has been updated
+ ///
+ /// Contains the new progress achieved by the progression
+ ///
+ private void asyncProgressUpdated(object sender, ProgressUpdateEventArgs arguments) {
+
+ // Set the new progress without any synchronization
+ this.currentProgress = arguments.Progress;
+
+ // Another use of the double-checked locking idiom, here we're trying to optimize
+ // away the lock in case some "trigger-happy" progressions send way more
+ // progress updates than the poor control can process :)
+ if(!this.progressUpdatePending) {
+ lock(this) {
+ if(!this.progressUpdatePending) {
+ this.progressUpdatePending = true;
+ this.progressUpdateAsyncResult = BeginInvoke(this.updateProgressDelegate);
+ }
+ } // lock
+ }
+
+ }
+
+ /// Synchronously updates the value visualized in the progress bar
+ private void updateProgress() {
+ lock(this) {
+
+ // Reset the update flag so incoming updates will cause the control to
+ // update itself another time.
+ this.progressUpdatePending = false;
+ EndInvoke(this.progressUpdateAsyncResult);
+
+ // Until the first progress event is received, the progress reporter shows
+ // a marquee bar to entertain the user even when no progress reports are
+ // being made at all.
+ if(this.progressBar.Style == ProgressBarStyle.Marquee)
+ this.progressBar.Style = ProgressBarStyle.Blocks;
+
+ // Transform the progress into an integer in the range of the progress bar's
+ // min and max values (these should normally be set to 0 and 100).
+ int min = this.progressBar.Minimum;
+ int max = this.progressBar.Maximum;
+ int progress = (int)(this.currentProgress * (max - min)) + min;
+
+ // Update the control
+ this.progressBar.Value = Math.Min(Math.Max(progress, min), max);;
+
+ // Assigning the value sends PBM_SETPOS to the control which,
+ // according to MSDN, already causes a redraw!
+ //base.Invalidate();
+
+ } // lock
+ }
+
+ ///
+ /// One-time timer callback that ensurs the form doesn't stay open when the
+ /// close request arrives at an inappropriate time.
+ ///
+ /// Timer that has ticked
+ /// Not used
+ private void controlCreationTimerTicked(object sender, EventArgs e) {
+
+ // This timer is intended to run only once to find out when the dialog has
+ // been fully constructed and is running its message pump. So we'll disable
+ // it as soon as it has been triggered once.
+ this.controlCreationTimer.Enabled = false;
+
+ // If the new state is 2, then the form was requested to close before it had
+ // been fully constructed, so we should close it now!
+ int newState = System.Threading.Interlocked.Increment(ref this.state);
+ if(newState == 2)
+ Close();
+
+ }
+
+ ///
+ /// Aborts the background operation when the user clicks the cancel button
+ ///
+ /// Button that has been clicked
+ /// Not used
+ private void cancelClicked(object sender, EventArgs e) {
+
+ if(this.abortReceiver != null) {
+
+ // Do this first because the abort receiver might trigger the AsyncEnded
+ // event in the calling thread and thus destroy our window even in
+ // the safe and synchronous UI thread :)
+ this.cancelButton.Enabled = false;
+
+ // Now we're ready to abort!
+ this.abortReceiver.AsyncAbort();
+ this.abortReceiver = null;
+
+ }
+
+ }
+
+ /// Whether an update of the control state is pending
+ private volatile bool progressUpdatePending;
+ /// Async result for the invoked control state update method
+ private volatile IAsyncResult progressUpdateAsyncResult;
+ /// Most recently reported progress of the tracker
+ private volatile float currentProgress;
+
+ /// Delegate for the asyncEnded() method
+ private EventHandler asyncEndedDelegate;
+ /// Delegate for the asyncProgressUpdated() method
+ private EventHandler asyncProgressUpdatedDelegate;
+ /// Delegate for the progress update method
+ private MethodInvoker updateProgressDelegate;
+ /// Whether the form can be closed and should be closed
+ ///
+ /// 0: Nothing happened yet
+ /// 1: Ready to close or close requested
+ /// 2: Ready to close and close requested, triggers close
+ ///
+ private int state;
+ ///
+ /// If set, reference to an object implementing IAbortable by which the
+ /// ongoing background process can be aborted.
+ ///
+ private IAbortable abortReceiver;
+
+ }
+
+} // namespace Nuclex.Windows.Forms
diff --git a/Source/ProgressReporter/ProgressReporterForm.resx b/Source/ProgressReporter/ProgressReporterForm.resx
new file mode 100644
index 0000000..c709957
--- /dev/null
+++ b/Source/ProgressReporter/ProgressReporterForm.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
\ No newline at end of file