using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace Chernobyl.ComponentModel { /// /// An that can cancel multiple /// instances and report the progress of those instances /// as a whole. Each contained by this /// maintains an equal portion of the progress /// reported by this . For example, if there are four /// instances contained within this /// this would report a /// progress of 25% when one fully completes, 50% when /// two fully complete, and so on. Invoking on /// this would cancel all of the /// instances contained in this . This type is /// an implementation of the composite pattern /// (http://en.wikipedia.org/wiki/Composite_pattern). Note that /// you can convert an of /// instances using the extension method /// . /// public class TaskEnumerable : ITask, IEnumerable { /// /// Initializes a new instance of the class. /// /// The instances that will be /// represented as a whole by this . public TaskEnumerable(IEnumerable tasks) { _tasks = tasks; foreach (ITask task in _tasks) { task.ProgressChanged += TaskProgressChanged; task.Canceled += TaskCanceled; ++_countCache; } } /// /// Invokes on all of the /// instances contained within this but only /// if this has not been canceled already and the /// has not reached 100%. If /// cancellation is successful, the event will be /// raised. Note that, when the work being done is on another thread, it /// is possible that the work will finish just as this method is invoked. /// public void Cancel() { if (IsCanceled == false && ProgressPercentage != 100) { IsCanceled = true; foreach (ITask task in _tasks) task.Cancel(); // Note: we do not need to raise the Canceled event here // because this class is already listening for all contained // ITasks to cancel. If they do, the this class will see it and // raise the Canceled event automatically. See the // TaskCanceled(object, EventArgs) method for more information. } } /// /// True if the task has been canceled, false if otherwise. /// public bool IsCanceled { get { return _isCanceled; } private set { bool previousValue = _isCanceled; _isCanceled = value; if (previousValue == false && _isCanceled == true && Canceled != null) Canceled(this, EventArgs.Empty); } } /// /// The percentage of work, from 0% to 100%, that has been completed on /// this task. /// public int ProgressPercentage { get { if (_progressPercentageCache.HasValue == false) _progressPercentageCache = (int)_tasks.Average(task => task.ProgressPercentage); return _progressPercentageCache.Value; } } /// /// An event that is raised when the is invoked /// on this instance. Note that this event is only raised once, /// regardless of how many times is invoked. This /// event will also be raised if all of the /// instances contained in this are canceled. /// This event will not be invoked if the /// is at 100%. /// public event EventHandler Canceled; /// /// An event that is raised whenever the progress of any /// instance contained in this has /// changed. Note that the /// in the instance included with /// the event is the of /// the contained in this /// that reported its progress and caused this event to be raised. /// public event ProgressChangedEventHandler ProgressChanged; /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through /// the collection. /// public IEnumerator GetEnumerator() { return _tasks.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate /// through the collection. /// IEnumerator IEnumerable.GetEnumerator() { return _tasks.GetEnumerator(); } /// /// An event handler that is assigned to the /// event of the /// contained in this . This method is /// responsible calculating the overall /// and raising of the event. /// /// The instance that generated the event. /// The instance /// containing the event data. void TaskProgressChanged(object sender, ProgressChangedEventArgs e) { // Force the ProgressPercentage to recalculate itself. _progressPercentageCache = null; if(ProgressChanged != null) ProgressChanged(this, new ProgressChangedEventArgs(ProgressPercentage, e.UserState)); } /// /// An event handler that is assigned to the /// event of the /// contained in this . This method is /// responsible for checking if all the instances /// contained within this have been /// canceled and raising the event if so. /// /// The instance that generated the event. /// The instance containing the /// event data. void TaskCanceled(object sender, EventArgs e) { ++_canceledTasksCount; if (_canceledTasksCount == _countCache) IsCanceled = true; } /// /// The instances that will be represented as a /// whole by this . /// IEnumerable _tasks; /// /// The number of instances in this /// . This value is cached so that we don't /// have to iterate over the /// to view the value. /// int _countCache; /// /// The number of instances contained within this /// that have been canceled. This variable /// is used to determine if the event /// should raised. /// int _canceledTasksCount; /// /// The backing field to . /// bool _isCanceled; /// /// The backing field and cache to . In /// order to calculate the we have to /// iterate over the to get the average. We cache /// the value to prevent us from having to do it more than we have to. /// int? _progressPercentageCache; } /// /// Extensions methods used with . /// public static class TaskEnumerableExtensions { /// /// Takes a group of instances /// and converts them to a single that represents /// the entire collection of instances. This method /// uses to accomplish this. /// /// The instances that are to be /// represented as one . /// That that represents the whole group of /// instances. public static ITask AsComposite(this IEnumerable tasks) { return new TaskEnumerable(tasks); } } }