#region CPL License /* Nuclex Framework Copyright (C) 2002-2007 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using System.Threading; using Nuclex.Support.Tracking; namespace Nuclex.Windows.Forms { /// Progress bar for tracking the progress of background operations public partial class TrackingBar : ProgressBar { /// Initializes a new tracking bar public TrackingBar() { InitializeComponent(); // We start off being in the idle state (and thus, being invisible) this.isIdle = true; base.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 this.updateIdleStateDelegate = new MethodInvoker(updateIdleState); this.updateProgressDelegate = new MethodInvoker(updateProgress); this.asyncIdleStateChangedDelegate = new EventHandler( asyncIdleStateChanged ); 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 /// Progression to be tracked public void Track(Progression progression) { this.tracker.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) { this.tracker.Track(progression, weight); } /// Stops tracking the specified progression /// Progression to stop tracking public void Untrack(Progression progression) { this.tracker.Untrack(progression); } /// /// Called when the summed progressed of the tracked progressions has changed /// /// Progression whose progress has changed /// Contains the 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 } } /// Called when the tracker becomes enters of leaves the idle state /// Tracker that has entered or left the idle state /// Contains the new idle state private void asyncIdleStateChanged(object sender, IdleStateEventArgs arguments) { // Do a fully synchronous update of the idle state. This update must not be // lost because otherwise, the progress bar might stay on-screen when in fact, // the background operation has already finished and nothing is happening anymore. this.isIdle = arguments.Idle; Invoke(this.updateIdleStateDelegate); } /// 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); // 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 = base.Minimum; int max = base.Maximum; int progress = (int)(this.currentProgress * (max - min)) + min; // Update the control base.Value = progress; // Assigning the value sends PBM_SETPOS to the control which, // according to MSDN, already causes a redraw! //base.Invalidate(); } // lock } /// /// Updates the idle state of the progress bar /// (controls whether the progress bar is shown or invisible) /// private void updateIdleState() { base.Visible = !this.isIdle; } /// 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; /// Whether the progress bar is in the idle state private volatile bool isIdle; /// Most recently reported progress of the tracker private volatile float currentProgress; /// Tracker used to sum and update the total progress private ProgressionTracker tracker; /// Delegate for the progress update method private MethodInvoker updateProgressDelegate; /// Delegate for the idle state update method private MethodInvoker updateIdleStateDelegate; /// Delegate for the OnAsyncProgressionEnded method private EventHandler asyncIdleStateChangedDelegate; /// Delegate for the OnAsyncProgressionProgressUpdated method private EventHandler asyncProgressUpdateDelegate; } } // namespace Nuclex.Windows.Forms