Added a tool strip host for the tracking bar, allowing it to be embedded inside a status bar; fixed a bug in the progress reporter form that would prevent AsyncAbort() from actually being called when the user clicked on the cancel button; AsyncProgressBar no longer changes the style of the progress bar, this is now up to the user; ProgressReporterForm now switches ProgressBar between Marquee and Blocks styles on its own; various formatting enhancements
git-svn-id: file:///srv/devel/repo-conversion/nuwi@13 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
f37b946a3d
commit
1de87c2c00
|
@ -104,6 +104,11 @@
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
<Name>ProgressReporterForm.Designer</Name>
|
<Name>ProgressReporterForm.Designer</Name>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\TrackingBar\ToolStripTrackingBar.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>ToolStripTrackingBar</Name>
|
||||||
|
<SubType>Component</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\TrackingBar\TrackingBar.cs">
|
<Compile Include="Source\TrackingBar\TrackingBar.cs">
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
<Name>TrackingBar</Name>
|
<Name>TrackingBar</Name>
|
||||||
|
|
|
@ -11,10 +11,17 @@ namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
/// <summary>Progress bar with optimized multi-threading behavior</summary>
|
/// <summary>Progress bar with optimized multi-threading behavior</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If a background thread is generating lots of progress updates, using synchronized
|
/// <para>
|
||||||
/// calls can drastically reduce performance. This progress bar optimizes this case
|
/// If a background thread is generating lots of progress updates, using synchronized
|
||||||
/// by performing the update asynchronously and keeping only the most recent update
|
/// calls can drastically reduce performance. This progress bar optimizes that case
|
||||||
/// when multiple updates arrive while the asynchronous update call is still running.
|
/// by performing the update asynchronously and keeping only the most recent update
|
||||||
|
/// when multiple updates arrive while the asynchronous update call is still running.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// 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.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public partial class AsyncProgressBar : ProgressBar {
|
public partial class AsyncProgressBar : ProgressBar {
|
||||||
|
|
||||||
|
@ -22,8 +29,8 @@ namespace Nuclex.Windows.Forms {
|
||||||
public AsyncProgressBar() {
|
public AsyncProgressBar() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.updateProgressDelegate = new MethodInvoker(updateProgress);
|
|
||||||
this.Disposed += new EventHandler(progressBarDisposed);
|
this.Disposed += new EventHandler(progressBarDisposed);
|
||||||
|
this.updateProgressDelegate = new MethodInvoker(updateProgress);
|
||||||
|
|
||||||
// Could probably use VolatileWrite() as well, but for consistency reasons
|
// Could probably use VolatileWrite() as well, but for consistency reasons
|
||||||
// this is an Interlocked call, too. Mixing different synchronization measures
|
// this is an Interlocked call, too. Mixing different synchronization measures
|
||||||
|
@ -37,6 +44,11 @@ namespace Nuclex.Windows.Forms {
|
||||||
/// <param name="arguments">Not used</param>
|
/// <param name="arguments">Not used</param>
|
||||||
private void progressBarDisposed(object sender, EventArgs arguments) {
|
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()
|
// 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
|
// 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
|
// 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;
|
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? :>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Asynchronously updates the value to be shown in the progress bar</summary>
|
/// <summary>Asynchronously updates the value to be shown in the progress bar</summary>
|
||||||
/// <param name="value">New value to set the progress bar to</param>
|
/// <param name="value">New value to set the progress bar to</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
public void AsyncSetValue(float value) {
|
public void AsyncSetValue(float value) {
|
||||||
|
|
||||||
// Update the value to be shown on the progress bar. If this happens multiple
|
// Update the value to be shown on the progress bar. If this happens multiple
|
||||||
|
@ -79,10 +94,6 @@ namespace Nuclex.Windows.Forms {
|
||||||
/// <summary>Synchronously updates the value visualized in the progress bar</summary>
|
/// <summary>Synchronously updates the value visualized in the progress bar</summary>
|
||||||
private void updateProgress() {
|
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 :)
|
// Cache these to shorten the code that follows :)
|
||||||
int minimum = base.Minimum;
|
int minimum = base.Minimum;
|
||||||
int maximum = base.Maximum;
|
int maximum = base.Maximum;
|
||||||
|
@ -93,8 +104,10 @@ namespace Nuclex.Windows.Forms {
|
||||||
// invocation to ensure the most recent value will remain at the end.
|
// invocation to ensure the most recent value will remain at the end.
|
||||||
float progress = Interlocked.Exchange(ref this.newProgress, -1.0f);
|
float progress = Interlocked.Exchange(ref this.newProgress, -1.0f);
|
||||||
|
|
||||||
// Convert the value to the progress bar's configured range and assign it
|
// Restrain the value to the progress bar's configured range and assign it.
|
||||||
// to the progress bar
|
// 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;
|
int value = (int)(progress * (maximum - minimum)) + minimum;
|
||||||
base.Value = Math.Min(Math.Max(value, minimum), maximum);
|
base.Value = Math.Min(Math.Max(value, minimum), maximum);
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace Nuclex.Windows.Forms {
|
||||||
/// embedded controls seperate of the ListView's items. The first option
|
/// embedded controls seperate of the ListView's items. The first option
|
||||||
/// would require a complete rewrite of the ListViewItem class and its related
|
/// would require a complete rewrite of the ListViewItem class and its related
|
||||||
/// support classes, all of which are surprisingly large and complex. Thus,
|
/// 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.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public partial class ContainerListView : System.Windows.Forms.ListView {
|
public partial class ContainerListView : System.Windows.Forms.ListView {
|
||||||
|
|
||||||
|
@ -45,14 +45,17 @@ namespace Nuclex.Windows.Forms {
|
||||||
this.embeddedControlClickedHandler = new EventHandler(embeddedControlClicked);
|
this.embeddedControlClickedHandler = new EventHandler(embeddedControlClicked);
|
||||||
|
|
||||||
this.embeddedControls = new ListViewEmbeddedControlCollection();
|
this.embeddedControls = new ListViewEmbeddedControlCollection();
|
||||||
|
|
||||||
this.embeddedControls.Added +=
|
this.embeddedControls.Added +=
|
||||||
new EventHandler<ListViewEmbeddedControlCollection.ListViewEmbeddedControlEventArgs>(
|
new EventHandler<ListViewEmbeddedControlCollection.ListViewEmbeddedControlEventArgs>(
|
||||||
embeddedControlAdded
|
embeddedControlAdded
|
||||||
);
|
);
|
||||||
|
|
||||||
this.embeddedControls.Removed +=
|
this.embeddedControls.Removed +=
|
||||||
new EventHandler<ListViewEmbeddedControlCollection.ListViewEmbeddedControlEventArgs>(
|
new EventHandler<ListViewEmbeddedControlCollection.ListViewEmbeddedControlEventArgs>(
|
||||||
embeddedControlRemoved
|
embeddedControlRemoved
|
||||||
);
|
);
|
||||||
|
|
||||||
this.embeddedControls.Clearing +=
|
this.embeddedControls.Clearing +=
|
||||||
new EventHandler(embeddedControlsClearing);
|
new EventHandler(embeddedControlsClearing);
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,8 @@ namespace Nuclex.Windows.Forms {
|
||||||
Text = windowTitle;
|
Text = windowTitle;
|
||||||
|
|
||||||
// Only enable the cancel button if the progression can be aborted
|
// 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
|
// Subscribe the form to the progression it is supposed to monitor
|
||||||
progression.AsyncEnded += this.asyncEndedDelegate;
|
progression.AsyncEnded += this.asyncEndedDelegate;
|
||||||
|
@ -144,7 +145,19 @@ namespace Nuclex.Windows.Forms {
|
||||||
/// Contains the new progress achieved by the progression
|
/// Contains the new progress achieved by the progression
|
||||||
/// </param>
|
/// </param>
|
||||||
private void asyncProgressUpdated(object sender, ProgressUpdateEventArgs arguments) {
|
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);
|
this.progressBar.AsyncSetValue(arguments.Progress);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -176,8 +189,8 @@ namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
if(this.abortReceiver != null) {
|
if(this.abortReceiver != null) {
|
||||||
|
|
||||||
// Do this first because the abort receiver might trigger the AsyncEnded
|
// Do this first because the abort receiver might trigger the AsyncEnded()
|
||||||
// event in the calling thread and thus destroy our window even in
|
// event in the calling thread (us!) and thus destroy our window even in
|
||||||
// the safe and synchronous UI thread :)
|
// the safe and synchronous UI thread :)
|
||||||
this.cancelButton.Enabled = false;
|
this.cancelButton.Enabled = false;
|
||||||
|
|
||||||
|
@ -200,6 +213,12 @@ namespace Nuclex.Windows.Forms {
|
||||||
/// 2: Ready to close and close requested, triggers close
|
/// 2: Ready to close and close requested, triggers close
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private int state;
|
private int state;
|
||||||
|
/// <summary>Whether we're receiving progress updates from the progression</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 0: No progress updates have arrived so far
|
||||||
|
/// 1: We have received at least one progress update from the progression
|
||||||
|
/// </remarks>
|
||||||
|
private int areProgressUpdatesIncoming;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If set, reference to an object implementing IAbortable by which the
|
/// If set, reference to an object implementing IAbortable by which the
|
||||||
/// ongoing background process can be aborted.
|
/// ongoing background process can be aborted.
|
||||||
|
|
92
Source/TrackingBar/ToolStripTrackingBar.cs
Normal file
92
Source/TrackingBar/ToolStripTrackingBar.cs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using Nuclex.Support.Tracking;
|
||||||
|
|
||||||
|
namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
|
/// <summary>Tracking bar that can be hosted in a tool strip container</summary>
|
||||||
|
public class ToolStripTrackingBar : ToolStripControlHost {
|
||||||
|
|
||||||
|
/// <summary>Initializes a new tool strip tracking bar</summary>
|
||||||
|
public ToolStripTrackingBar() : base(createTrackingBar()) {
|
||||||
|
hideControlAtRuntime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Initializes a new tool strip tracking bar with a name</summary>
|
||||||
|
/// <param name="name">Name of the tracking bar control</param>
|
||||||
|
public ToolStripTrackingBar(string name) : base(createTrackingBar(), name) {
|
||||||
|
hideControlAtRuntime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The tracking bar control being hosted by the tool strip host</summary>
|
||||||
|
public TrackingBar TrackingBarControl {
|
||||||
|
get { return base.Control as TrackingBar; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tracks the specified progression in the tracking bar</summary>
|
||||||
|
/// <param name="progression">Progression to be tracked</param>
|
||||||
|
public void Track(Progression progression) {
|
||||||
|
TrackingBarControl.Track(progression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Tracks the specified progression in the tracking bar</summary>
|
||||||
|
/// <param name="progression">Progression to be tracked</param>
|
||||||
|
/// <param name="weight">Weight of this progression in the total progress</param>
|
||||||
|
public void Track(Progression progression, float weight) {
|
||||||
|
TrackingBarControl.Track(progression, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Stops tracking the specified progression</summary>
|
||||||
|
/// <param name="progression">Progression to stop tracking</param>
|
||||||
|
public void Untrack(Progression progression) {
|
||||||
|
TrackingBarControl.Untrack(progression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Default size of the hosted control</summary>
|
||||||
|
protected override Size DefaultSize {
|
||||||
|
get { return new Size(100, 15); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Default margin to leave around the control in the tool strip</summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates a new tracking bar</summary>
|
||||||
|
/// <returns>A new tracking bar</returns>
|
||||||
|
private static TrackingBar createTrackingBar() {
|
||||||
|
TrackingBar trackingBar = new TrackingBar();
|
||||||
|
trackingBar.Size = new Size(100, 15);
|
||||||
|
return trackingBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Hides the control during runtime usage</summary>
|
||||||
|
private void hideControlAtRuntime() {
|
||||||
|
TrackingBarControl.VisibleChanged += new EventHandler(trackingBarVisibleChanged);
|
||||||
|
|
||||||
|
LicenseUsageMode usageMode = System.ComponentModel.LicenseManager.UsageMode;
|
||||||
|
if(usageMode == LicenseUsageMode.Runtime)
|
||||||
|
base.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the visibility of the tool strip host when the tracking bar control's
|
||||||
|
/// visibility changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">Tracking bar control whose visiblity has changed</param>
|
||||||
|
/// <param name="e">Not used</param>
|
||||||
|
private void trackingBarVisibleChanged(object sender, EventArgs e) {
|
||||||
|
base.Visible = TrackingBarControl.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Windows.Forms
|
|
@ -40,12 +40,7 @@ namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
// We start off being in the idle state (and thus, being invisible)
|
// We start off being in the idle state (and thus, being invisible)
|
||||||
this.isIdle = true;
|
this.isIdle = true;
|
||||||
base.Visible = false;
|
this.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;
|
|
||||||
|
|
||||||
// Initialize the delegates we use to update the control's state and those
|
// Initialize the delegates we use to update the control's state and those
|
||||||
// we use to register ourselfes to the tracker's events
|
// we use to register ourselfes to the tracker's events
|
||||||
|
@ -56,6 +51,11 @@ namespace Nuclex.Windows.Forms {
|
||||||
this.asyncProgressUpdateDelegate = new EventHandler<ProgressUpdateEventArgs>(
|
this.asyncProgressUpdateDelegate = new EventHandler<ProgressUpdateEventArgs>(
|
||||||
asyncProgressUpdated
|
asyncProgressUpdated
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create the tracker and attach ourselfes to its events
|
||||||
|
this.tracker = new ProgressionTracker();
|
||||||
|
this.tracker.AsyncIdleStateChanged += this.asyncIdleStateChangedDelegate;
|
||||||
|
this.tracker.AsyncProgressUpdated += this.asyncProgressUpdateDelegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tracks the specified progression in the tracking bar</summary>
|
/// <summary>Tracks the specified progression in the tracking bar</summary>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user