diff --git a/Nuclex.Windows.Forms.csproj b/Nuclex.Windows.Forms.csproj
index f0212e4..cc9db0a 100644
--- a/Nuclex.Windows.Forms.csproj
+++ b/Nuclex.Windows.Forms.csproj
@@ -104,6 +104,11 @@
false
ProgressReporterForm.Designer
+
+ false
+ ToolStripTrackingBar
+ Component
+
false
TrackingBar
diff --git a/Source/AsyncProgressBar/AsyncProgressBar.cs b/Source/AsyncProgressBar/AsyncProgressBar.cs
index b623253..3990a11 100644
--- a/Source/AsyncProgressBar/AsyncProgressBar.cs
+++ b/Source/AsyncProgressBar/AsyncProgressBar.cs
@@ -11,10 +11,17 @@ namespace Nuclex.Windows.Forms {
/// Progress bar with optimized multi-threading behavior
///
- /// If a background thread is generating lots of progress updates, using synchronized
- /// calls can drastically reduce performance. This progress bar optimizes this case
- /// by performing the update asynchronously and keeping only the most recent update
- /// when multiple updates arrive while the asynchronous update call is still running.
+ ///
+ /// If a background thread is generating lots of progress updates, using synchronized
+ /// calls can drastically reduce performance. This progress bar optimizes that case
+ /// by performing the update asynchronously and keeping only the most recent update
+ /// when multiple updates arrive while the asynchronous update call is still running.
+ ///
+ ///
+ /// This design eliminates useless queueing of progress updates, thereby reducing
+ /// CPU load occuring in the UI thread and at the same time avoids blocking the
+ /// worker thread, increasing its performance.
+ ///
///
public partial class AsyncProgressBar : ProgressBar {
@@ -22,8 +29,8 @@ namespace Nuclex.Windows.Forms {
public AsyncProgressBar() {
InitializeComponent();
- this.updateProgressDelegate = new MethodInvoker(updateProgress);
this.Disposed += new EventHandler(progressBarDisposed);
+ this.updateProgressDelegate = new MethodInvoker(updateProgress);
// Could probably use VolatileWrite() as well, but for consistency reasons
// this is an Interlocked call, too. Mixing different synchronization measures
@@ -37,6 +44,11 @@ namespace Nuclex.Windows.Forms {
/// Not used
private void progressBarDisposed(object sender, EventArgs arguments) {
+ // CHECK: This method is only called on an explicit Dispose() of the control.
+ // Microsoft officially states that it's allowed to call Control.BeginInvoke()
+ // without calling Control.EndInvoke(), so this code is quite correct,
+ // but is it also clean? :>
+
// Since this has to occur in the UI thread, there's no way that updateProgress()
// could be executing just now. But the final call to updateProgress() will not
// have EndInvoke() called on it yet, so we do this here before the control
@@ -46,15 +58,18 @@ namespace Nuclex.Windows.Forms {
this.progressUpdateAsyncResult = null;
}
- // CHECK: This method is only called on an explicit Dispose() of the control.
- // Microsoft officially states that it's allowed to call Control.BeginInvoke()
- // without calling Control.EndInvoke(), so this code is quite correct,
- // but is it also clean? :>
-
}
/// Asynchronously updates the value to be shown in the progress bar
/// New value to set the progress bar to
+ ///
+ /// This will schedule an asynchronous update of the progress bar in the UI thread.
+ /// If you change the progress value again before the progress bar has completed its
+ /// update cycle, the original progress value will be skipped and the progress bar
+ /// jumps directly to the latest progress value. Updates are not queued, there is
+ /// at most one update waiting on the UI thread. It is also strictly guaranteed that
+ /// the last most progress value set will be shown and never skipped.
+ ///
public void AsyncSetValue(float value) {
// Update the value to be shown on the progress bar. If this happens multiple
@@ -79,10 +94,6 @@ namespace Nuclex.Windows.Forms {
/// Synchronously updates the value visualized in the progress bar
private void updateProgress() {
- // Switch the style if the progress bar is still set to marquee mode
- if(Style == ProgressBarStyle.Marquee)
- Style = ProgressBarStyle.Blocks;
-
// Cache these to shorten the code that follows :)
int minimum = base.Minimum;
int maximum = base.Maximum;
@@ -93,8 +104,10 @@ namespace Nuclex.Windows.Forms {
// invocation to ensure the most recent value will remain at the end.
float progress = Interlocked.Exchange(ref this.newProgress, -1.0f);
- // Convert the value to the progress bar's configured range and assign it
- // to the progress bar
+ // Restrain the value to the progress bar's configured range and assign it.
+ // This is done to prevent exceptions in the UI thread (theoretically the user
+ // could change the progress bar's min and max just before the UI thread executes
+ // this method, so we cannot validate the value in AsyncSetValue())
int value = (int)(progress * (maximum - minimum)) + minimum;
base.Value = Math.Min(Math.Max(value, minimum), maximum);
diff --git a/Source/ContainerListView/ContainerListView.cs b/Source/ContainerListView/ContainerListView.cs
index 8032439..8b9df59 100644
--- a/Source/ContainerListView/ContainerListView.cs
+++ b/Source/ContainerListView/ContainerListView.cs
@@ -36,7 +36,7 @@ namespace Nuclex.Windows.Forms {
/// embedded controls seperate of the ListView's items. The first option
/// would require a complete rewrite of the ListViewItem class and its related
/// support classes, all of which are surprisingly large and complex. Thus,
- /// the less clean but more doable latter option has been chosen.
+ /// I chose the less clean but more doable latter option.
///
public partial class ContainerListView : System.Windows.Forms.ListView {
@@ -45,14 +45,17 @@ namespace Nuclex.Windows.Forms {
this.embeddedControlClickedHandler = new EventHandler(embeddedControlClicked);
this.embeddedControls = new ListViewEmbeddedControlCollection();
+
this.embeddedControls.Added +=
new EventHandler(
embeddedControlAdded
);
+
this.embeddedControls.Removed +=
new EventHandler(
embeddedControlRemoved
);
+
this.embeddedControls.Clearing +=
new EventHandler(embeddedControlsClearing);
diff --git a/Source/ProgressReporter/ProgressReporterForm.cs b/Source/ProgressReporter/ProgressReporterForm.cs
index 7f82494..826160f 100644
--- a/Source/ProgressReporter/ProgressReporterForm.cs
+++ b/Source/ProgressReporter/ProgressReporterForm.cs
@@ -101,7 +101,8 @@ namespace Nuclex.Windows.Forms {
Text = windowTitle;
// Only enable the cancel button if the progression can be aborted
- this.cancelButton.Enabled = (progression is IAbortable);
+ this.abortReceiver = (progression as IAbortable);
+ this.cancelButton.Enabled = (this.abortReceiver != null);
// Subscribe the form to the progression it is supposed to monitor
progression.AsyncEnded += this.asyncEndedDelegate;
@@ -144,7 +145,19 @@ namespace Nuclex.Windows.Forms {
/// Contains the new progress achieved by the progression
///
private void asyncProgressUpdated(object sender, ProgressUpdateEventArgs arguments) {
+
+ // See if this is the first progress update we're receiving. If yes, we need to
+ // switch the progress bar from marquee into its normal mode!
+ int haveProgress = Interlocked.Exchange(ref this.areProgressUpdatesIncoming, 1);
+ if(haveProgress == 0) {
+ this.progressBar.BeginInvoke(
+ (MethodInvoker)delegate() { this.progressBar.Style = ProgressBarStyle.Blocks; }
+ );
+ }
+
+ // Send the new progress to the progress bar
this.progressBar.AsyncSetValue(arguments.Progress);
+
}
///
@@ -176,8 +189,8 @@ namespace Nuclex.Windows.Forms {
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
+ // Do this first because the abort receiver might trigger the AsyncEnded()
+ // event in the calling thread (us!) and thus destroy our window even in
// the safe and synchronous UI thread :)
this.cancelButton.Enabled = false;
@@ -200,6 +213,12 @@ namespace Nuclex.Windows.Forms {
/// 2: Ready to close and close requested, triggers close
///
private int state;
+ /// Whether we're receiving progress updates from the progression
+ ///
+ /// 0: No progress updates have arrived so far
+ /// 1: We have received at least one progress update from the progression
+ ///
+ private int areProgressUpdatesIncoming;
///
/// If set, reference to an object implementing IAbortable by which the
/// ongoing background process can be aborted.
diff --git a/Source/TrackingBar/ToolStripTrackingBar.cs b/Source/TrackingBar/ToolStripTrackingBar.cs
new file mode 100644
index 0000000..c66aaae
--- /dev/null
+++ b/Source/TrackingBar/ToolStripTrackingBar.cs
@@ -0,0 +1,92 @@
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Forms;
+
+using Nuclex.Support.Tracking;
+
+namespace Nuclex.Windows.Forms {
+
+ /// Tracking bar that can be hosted in a tool strip container
+ public class ToolStripTrackingBar : ToolStripControlHost {
+
+ /// Initializes a new tool strip tracking bar
+ public ToolStripTrackingBar() : base(createTrackingBar()) {
+ hideControlAtRuntime();
+ }
+
+ /// Initializes a new tool strip tracking bar with a name
+ /// Name of the tracking bar control
+ public ToolStripTrackingBar(string name) : base(createTrackingBar(), name) {
+ hideControlAtRuntime();
+ }
+
+ /// The tracking bar control being hosted by the tool strip host
+ public TrackingBar TrackingBarControl {
+ get { return base.Control as TrackingBar; }
+ }
+
+ /// Tracks the specified progression in the tracking bar
+ /// Progression to be tracked
+ public void Track(Progression progression) {
+ TrackingBarControl.Track(progression);
+ }
+
+ /// Tracks the specified progression in the tracking bar
+ /// Progression to be tracked
+ /// Weight of this progression in the total progress
+ public void Track(Progression progression, float weight) {
+ TrackingBarControl.Track(progression, weight);
+ }
+
+ /// Stops tracking the specified progression
+ /// Progression to stop tracking
+ public void Untrack(Progression progression) {
+ TrackingBarControl.Untrack(progression);
+ }
+
+ /// Default size of the hosted control
+ protected override Size DefaultSize {
+ get { return new Size(100, 15); }
+ }
+
+ /// Default margin to leave around the control in the tool strip
+ protected override Padding DefaultMargin {
+ get {
+ if((base.Owner != null) && (base.Owner is StatusStrip))
+ return new Padding(1, 3, 1, 3);
+
+ return new Padding(1, 2, 1, 1);
+ }
+ }
+
+ /// Creates a new tracking bar
+ /// A new tracking bar
+ private static TrackingBar createTrackingBar() {
+ TrackingBar trackingBar = new TrackingBar();
+ trackingBar.Size = new Size(100, 15);
+ return trackingBar;
+ }
+
+ /// Hides the control during runtime usage
+ private void hideControlAtRuntime() {
+ TrackingBarControl.VisibleChanged += new EventHandler(trackingBarVisibleChanged);
+
+ LicenseUsageMode usageMode = System.ComponentModel.LicenseManager.UsageMode;
+ if(usageMode == LicenseUsageMode.Runtime)
+ base.Visible = false;
+ }
+
+ ///
+ /// Toggles the visibility of the tool strip host when the tracking bar control's
+ /// visibility changes.
+ ///
+ /// Tracking bar control whose visiblity has changed
+ /// Not used
+ private void trackingBarVisibleChanged(object sender, EventArgs e) {
+ base.Visible = TrackingBarControl.Visible;
+ }
+
+ }
+
+} // namespace Nuclex.Windows.Forms
diff --git a/Source/TrackingBar/TrackingBar.cs b/Source/TrackingBar/TrackingBar.cs
index c951435..53eeaaa 100644
--- a/Source/TrackingBar/TrackingBar.cs
+++ b/Source/TrackingBar/TrackingBar.cs
@@ -40,12 +40,7 @@ namespace Nuclex.Windows.Forms {
// We start off being in the idle state (and thus, being invisible)
this.isIdle = true;
- base.Visible = false;
-
- // Create the tracker and attach ourselfes to its events
- this.tracker = new ProgressionTracker();
- this.tracker.AsyncIdleStateChanged += this.asyncIdleStateChangedDelegate;
- this.tracker.AsyncProgressUpdated += this.asyncProgressUpdateDelegate;
+ this.Visible = false;
// Initialize the delegates we use to update the control's state and those
// we use to register ourselfes to the tracker's events
@@ -56,6 +51,11 @@ namespace Nuclex.Windows.Forms {
this.asyncProgressUpdateDelegate = new EventHandler(
asyncProgressUpdated
);
+
+ // Create the tracker and attach ourselfes to its events
+ this.tracker = new ProgressionTracker();
+ this.tracker.AsyncIdleStateChanged += this.asyncIdleStateChangedDelegate;
+ this.tracker.AsyncProgressUpdated += this.asyncProgressUpdateDelegate;
}
/// Tracks the specified progression in the tracking bar