//------------------------------------------------------------------------------------ // //------------------------------------------------------------------------------------ using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Threading; namespace Utilities { /// /// Extended BackgroundWorker class that supports automatic progress tracking and reporting. /// public class BackgroundWorkerEx : BackgroundWorker { #region EventArgs /// /// Event args for the LogUpdated event. /// public class LogUpdatedEventArgs : EventArgs { private string _logTextToAppend; /// /// The log text to append. /// public string LogTextToAppend { get { return _logTextToAppend; } } public LogUpdatedEventArgs(string logTextToAppend) { _logTextToAppend = logTextToAppend; } } /// /// Event args for the ReportError event. /// public class ReportErrorEventArgs : EventArgs { private string _errorText; /// /// The log text to append. /// public string ErrorText { get { return _errorText; } } public ReportErrorEventArgs(string errorText) { _errorText = errorText; } } #endregion // EventArgs /// /// Keeps track of a sequence of operations specified by an initial number of steps, and /// updated when steps are completed. /// private class Sequence { #region Private Fields ------------------------------------------------------- private long totalSteps; private long stepsComplete; #endregion // Private Fields #region Constructors --------------------------------------------------------- /// /// Constructor for a new Sequence. /// /// The total number of steps involved in completing this sequence. public Sequence(long totalSteps) { this.totalSteps = totalSteps; stepsComplete = 0; } #endregion // Constructors #region Properties ----------------------------------------------------------- /// /// The current progress of this sequence. /// public int Progress { get { int progress = 100; // be consistent with IsComplete property (treat 0 total steps as complete) if (totalSteps > 0 && stepsComplete < totalSteps) { progress = (int)(100.0 / totalSteps * stepsComplete); } return progress; } } /// /// Returns whether all the steps in the sequence have been completed. /// public bool IsComplete { get { return (stepsComplete == totalSteps); } } /// /// The total number of steps required to complete this sequence. /// public long TotalSteps { get { return totalSteps; } } #endregion // Properties #region Public Methods ------------------------------------------------------- /// /// Notifies the sequence that a single new step has been completed. /// public void StepCompleted() { StepsCompleted(1); } /// /// Notifies the sequence that one or more new steps have just been completed. /// /// The number of new steps completed. public void StepsCompleted(long numStepsJustCompleted) { if (stepsComplete + numStepsJustCompleted <= totalSteps) { stepsComplete += numStepsJustCompleted; // normal case } else if (stepsComplete + numStepsJustCompleted > totalSteps) { stepsComplete = totalSteps; // don't let us go past 100% } else if (stepsComplete + numStepsJustCompleted < 0) { stepsComplete = 0; // don't let us go lower than 0% } } #endregion // Public Methods }; #region Private Fields ------------------------------------------------------- /// /// Stack to keep track of multiple sequences involved in this operation. /// private Stack operationStack; /// /// Stack to keep track of progress descriptions throughout this operation. /// private Stack messageStack; /// /// Used to allow pausing/suspending and resuming of the worker thread. /// private ManualResetEvent locker; /// /// Flag indicating whether the worker thread is paused. /// private bool isPaused; #endregion // Private Fields #region Events /// /// Event handler to notify when the log is being updated. /// public event EventHandler LogUpdated; /// /// Event handler to notify when an error occurs. /// public event EventHandler ReportError; /// /// Raises the LogUpdated event. /// /// An System.EventArgs that contains the event data. //protected virtual void UpdateLog(LogUpdatedEventArgs e); #endregion // Events #region Constructors --------------------------------------------------------- /// /// Constructor for a new BackgroundWorkerEx. /// public BackgroundWorkerEx() { operationStack = new Stack(); messageStack = new Stack(); locker = new ManualResetEvent(true); } #endregion // Constructors #region Properties ----------------------------------------------------------- /// /// Flag indicating whether the worker thread is paused. /// public bool IsPaused { get { return isPaused; } } #endregion // Properties #region Public Methods ------------------------------------------------------- /// /// Pauses the worker thread. /// public void Pause() { if (!IsPaused) { isPaused = true; locker.Reset(); } } /// /// Resumes the worker thread. /// public void Resume() { if (IsPaused) { isPaused = false; locker.Set(); } } /// /// Suspends the worker thread if the thread owner has requested it to pause. /// /// This should only be called by the background worker thread itself. public void PauseIfRequired() { locker.WaitOne(); } /// /// Changes the progress bar style. /// /// public void SetProgressStyle(bool progressIsIndeterminate) { base.ReportProgress(0, progressIsIndeterminate); } /// /// Method to notify that the log is to be updated. /// /// New log text public void UpdateLog(string logText) { if (LogUpdated != null) { LogUpdated(this, new LogUpdatedEventArgs(logText)); } } /// /// Method to notify that an error occurred. /// /// Error text public void NotifyError(string errorText) { if (ReportError != null) { ReportError(this, new ReportErrorEventArgs(errorText)); } } /// /// Initiates the start of a new sequence within this operation. /// /// The total number of steps involved in this new sequence. public void StartSequence(long totalSteps) { operationStack.Push(new Sequence(totalSteps)); ReportSequencedProgress(); } /// /// Causes the current sequence progress to go to 100% and therefore be removed from the stack /// of sequences involved in this operation. /// public void StopSequence() { if (operationStack.Count > 0) { operationStack.Pop(); if (GetProgress() == 100) { ReportSequencedProgress(); } } } /// /// Signals that a step in the currently executing sequence has just been completed. /// public void StepCompleted() { StepsCompleted(1); } /// /// Signals that one or more steps in the currently executing sequence have just been completed. /// /// public void StepsCompleted(int numStepsJustCompleted) { if (operationStack.Count > 0) { Sequence currentSequence = operationStack.Pop(); currentSequence.StepsCompleted(numStepsJustCompleted); operationStack.Push(currentSequence); ReportSequencedProgress(); } } /// /// Adds a new message onto the message stack. /// /// The progress message to be appended. public void PushMessage(string message) { messageStack.Push(message); ReportSequencedProgress(); } /// /// Removes the last message from the message stack. /// public void PopMessage() { if (messageStack.Count > 0) { messageStack.Pop(); ReportSequencedProgress(); } } #endregion // Public Methods #region Private Methods ------------------------------------------------------ /// /// Asks the task's background worker to update the UI's progress bar and message. /// private void ReportSequencedProgress() { int progress = GetProgress(); string message = GetMessage(); base.ReportProgress(progress, message); } /// /// Retrieves the current overall progress of this operation, taking in account the progress /// of all sub-sequences. /// /// An integer between 0 and 100. private int GetProgress() { float progress = 0.0f; Sequence[] operationArray = operationStack.ToArray(); if (operationArray.Length > 0) { // go through backwards to calculate the current progress progress = operationArray[operationArray.Length - 1].Progress; for (int sequenceNum = operationArray.Length - 2; sequenceNum >= 0; sequenceNum--) { progress += operationArray[sequenceNum].Progress / operationArray[sequenceNum + 1].TotalSteps; } } return Convert.ToInt32(progress); } /// /// Retrieves the current progress message for this operation, taking into account the progress /// messages of all sub-sequences. These are combined from left to right as they were pushed onto /// the message stack. /// /// A string with the complete progress message private string GetMessage() { string message = ""; string[] messageArray = messageStack.ToArray(); // go through backwards to build the message string for (int messageNum = messageArray.Length - 1; messageNum >= 0; messageNum--) { message += messageArray[messageNum]; } return message; } #endregion // Private Methods } }