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
					
				
					 6 changed files with 158 additions and 26 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue