source: branches/eraser6/pluginsRewrite/Eraser.Plugins/ProgressManager.cs @ 2406

Revision 2406, 21.6 KB checked in by lowjoel, 3 years ago (diff)

Moved the ProgressManager? classes to Eraser.Plugins since it does form a component which plugins will potentially need to implement

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26
27using Eraser.Util;
28
29namespace Eraser.Plugins
30{
31    /// <summary>
32    /// Manages the progress for any operation.
33    /// </summary>
34    public abstract class ProgressManagerBase
35    {
36        /// <summary>
37        /// Constructor.
38        ///
39        /// This sets the starting time of this task to allow the computation of
40        /// the estimated end time by extrapolating collected data based on the
41        /// amount of time already elapsed.
42        /// </summary>
43        protected ProgressManagerBase()
44        {
45            StartTime = DateTime.Now;
46        }
47
48        /// <summary>
49        /// Resets the starting time of the task. The speed measurement is
50        /// automatically started when the ProgressManagerBase object is created.
51        /// </summary>
52        public void Restart()
53        {
54            StartTime = DateTime.Now;
55            lock (Speeds)
56                Speeds.Reset();
57        }
58
59        /// <summary>
60        /// Gets the percentage of the operation completed.
61        /// </summary>
62        /// <remarks>If the <see cref="ProgressIndeterminate"/> property is true, this
63        /// property will return <see cref="System.Float.NaN"/>.</remarks>
64        public abstract float Progress
65        {
66            get;
67        }
68
69        /// <summary>
70        /// Gets whether the current progress is undefined.
71        /// </summary>
72        public abstract bool ProgressIndeterminate
73        {
74            get;
75        }
76
77        /// <summary>
78        /// Computes the speed of the erase, in percentage of completion per second,
79        /// based on the information collected in the previous 15 seconds.
80        /// </summary>
81        public abstract float Speed
82        {
83            get;
84        }
85
86        /// <summary>
87        /// Calculates the estimated amount of time left based on the total
88        /// amount of information to erase and the current speed of the erase
89        /// </summary>
90        public abstract TimeSpan TimeLeft
91        {
92            get;
93        }
94
95        /// <summary>
96        /// The starting time of this task.
97        /// </summary>
98        public DateTime StartTime
99        {
100            get;
101            private set;
102        }
103
104        /// <summary>
105        /// Samples the current speed of the task.
106        /// </summary>
107        /// <param name="speed">The speed of the task.</param>
108        protected void SampleSpeed(float speed)
109        {
110            lock (Speeds)
111            {
112                IList<KeyValuePair<DateTime, double>> outliers = Speeds.GetOutliers(0.95);
113                if (outliers != null)
114                {
115                    List<KeyValuePair<DateTime, double>> recentOutliers = outliers.Where(
116                        sample => (DateTime.Now - sample.Key).TotalSeconds <= 60).ToList();
117                    if (recentOutliers.Count >= 5)
118                    {
119                        Speeds.Reset();
120                        recentOutliers.ForEach(sample => Speeds.Add(sample.Value));
121                    }
122                }
123
124                Speeds.Add(speed);
125                PredictedSpeed = Speeds.Predict(0.50);
126            }
127        }
128
129        /// <summary>
130        /// Predicts the speed of the operation, given the current samples.
131        /// </summary>
132        protected Interval PredictedSpeed
133        {
134            get;
135            private set;
136        }
137
138        /// <summary>
139        /// The speed sampler.
140        /// </summary>
141        private Sampler Speeds = new Sampler();
142    }
143
144    /// <summary>
145    /// Manages progress based only on one input, set through the Completed and Total
146    /// properties.
147    /// </summary>
148    public class ProgressManager : ProgressManagerBase
149    {
150        /// <summary>
151        /// Marks this task's progress as indeterminate.
152        /// </summary>
153        public void MarkIndeterminate()
154        {
155            progressIndeterminate = true;
156        }
157
158        /// <summary>
159        /// Marks this task as complete.
160        /// </summary>
161        public void MarkComplete()
162        {
163            progressIndeterminate = false;
164            if (total == 0)
165                completed = total = 1;
166            else
167                completed = total;
168        }
169
170        /// <summary>
171        /// Gets or sets the number of work units already completed.
172        /// </summary>
173        /// <remarks>This unsets the Indeterminate flag for the progress of this Task.</remarks>
174        public long Completed
175        {
176            get
177            {
178                return completed;
179            }
180            set
181            {
182                if (value > Total)
183                    throw new ArgumentOutOfRangeException("value", value, "The Completed " +
184                        "property of the Progress Manager cannot exceed the total work units for " +
185                        "the task.");
186
187                completed = value;
188                progressIndeterminate = false;
189            }
190        }
191
192        /// <summary>
193        /// Gets or sets the total number of work units that this task has.
194        /// </summary>
195        public long Total
196        {
197            get
198            {
199                return total;
200            }
201            set
202            {
203                if (value < Completed)
204                    throw new ArgumentOutOfRangeException("value", value, "The Total property " +
205                        "of the Progress Manager must be greater than or equal to the completed " +
206                        "work units for the task.");
207                if (value < 0)
208                    throw new ArgumentOutOfRangeException("value", value, "The Total property " +
209                        "of the Progress Manager must be a positive integer");
210
211                total = value;
212            }
213        }
214
215        public override float Progress
216        {
217            get
218            {
219                if (Total == 0)
220                    return 0.0f;
221                else if (ProgressIndeterminate)
222                    return float.NaN;
223
224                return (float)((double)Completed / Total);
225            }
226        }
227
228        public override bool ProgressIndeterminate
229        {
230            get
231            {
232                return progressIndeterminate;
233            }
234        }
235
236        /// <summary>
237        /// Stores whether the progress of the current task cannot be determined.
238        /// </summary>
239        /// <see cref="ProgressIndeterminate"/>
240        private bool progressIndeterminate;
241
242        public override float Speed
243        {
244            get
245            {
246                if ((DateTime.Now - lastSpeedCalc).Seconds <= 1 && lastSpeed != 0)
247                    return lastSpeed;
248
249                //Calculate how much time has passed
250                double timeElapsed = (DateTime.Now - lastSpeedCalc).TotalSeconds;
251                if (timeElapsed == 0.0)
252                    return 0;
253
254                //Then compute the speed of the calculation
255                long progressDelta = Completed - lastCompleted;
256                float currentSpeed = (float)(progressDelta / timeElapsed / total);
257                lastSpeedCalc = DateTime.Now;
258                lastCompleted = Completed;
259
260                //We won't update the speed of the task if the current speed is within
261                //the lower and upper prediction interval.
262                Interval interval = PredictedSpeed;
263                if (interval != null)
264                {
265                    if (currentSpeed < interval.Minimum)
266                    {
267                        Restart();
268                        lastSpeed = currentSpeed;
269                    }
270                    else if (currentSpeed > interval.Maximum)
271                    {
272                        Restart();
273                        lastSpeed = currentSpeed;
274                    }
275                    else if (lastSpeed == 0.0f)
276                    {
277                        lastSpeed = currentSpeed;
278                    }
279                }
280
281                SampleSpeed(currentSpeed);
282                return lastSpeed;
283            }
284        }
285
286        public override TimeSpan TimeLeft
287        {
288            get
289            {
290                float speed = Speed;
291                if (speed == 0.0)
292                    return TimeSpan.MinValue;
293
294                return TimeSpan.FromSeconds((1.0f - Progress) / speed);
295            }
296        }
297
298        /// <summary>
299        /// The last time a speed calculation was computed so that speed is not
300        /// computed too often.
301        /// </summary>
302        private DateTime lastSpeedCalc;
303
304        /// <summary>
305        /// The amount of the operation completed at the last speed computation.
306        /// </summary>
307        private long lastCompleted;
308
309        /// <summary>
310        /// The last calculated speed of the operation.
311        /// </summary>
312        private float lastSpeed;
313
314        /// <summary>
315        /// The backing field for <see cref="Completed"/>
316        /// </summary>
317        private long completed;
318
319        /// <summary>
320        /// The backing field for <see cref="Total"/>
321        /// </summary>
322        private long total;
323    }
324
325    /// <summary>
326    /// Manages progress based on sub-tasks.
327    /// </summary>
328    public abstract class ChainedProgressManager : ProgressManagerBase
329    {
330    }
331
332    /// <summary>
333    /// Manages progress based on sub-tasks, taking each sub-task to be a step
334    /// in which the next step will not be executed until the current step is
335    /// complete. Each step is also assign weights so that certain steps which
336    /// take more time are given a larger amount of progress-bar space for finer
337    /// grained progress reporting.
338    /// </summary>
339    public class SteppedProgressManager : ChainedProgressManager
340    {
341        /// <summary>
342        /// The class which manages the steps which comprise the overall progress.
343        /// </summary>
344        private class StepsList : IList<SteppedProgressManagerStep>
345        {
346            public StepsList(SteppedProgressManager manager)
347            {
348                List = new List<SteppedProgressManagerStep>();
349                ListLock = manager.ListLock;
350            }
351
352            #region IList<SteppedProgressManagerStep> Members
353
354            public int IndexOf(SteppedProgressManagerStep item)
355            {
356                lock (ListLock)
357                    return List.IndexOf(item);
358            }
359
360            public void Insert(int index, SteppedProgressManagerStep item)
361            {
362                lock (ListLock)
363                {
364                    List.Insert(index, item);
365                    TotalWeights += item.Weight;
366                }
367            }
368
369            public void RemoveAt(int index)
370            {
371                lock (ListLock)
372                {
373                    TotalWeights -= List[index].Weight;
374                    List.RemoveAt(index);
375                }
376            }
377
378            public SteppedProgressManagerStep this[int index]
379            {
380                get
381                {
382                    lock (ListLock)
383                        return List[index];
384                }
385                set
386                {
387                    lock (ListLock)
388                    {
389                        TotalWeights -= List[index].Weight;
390                        List[index] = value;
391                        TotalWeights += value.Weight;
392                    }
393                }
394            }
395
396            #endregion
397
398            #region ICollection<SteppedProgressManagerStep> Members
399
400            public void Add(SteppedProgressManagerStep item)
401            {
402                lock (ListLock)
403                {
404                    List.Add(item);
405                    TotalWeights += item.Weight;
406                }
407            }
408
409            public void Clear()
410            {
411                lock (ListLock)
412                {
413                    List.Clear();
414                    TotalWeights = 0;
415                }
416            }
417
418            public bool Contains(SteppedProgressManagerStep item)
419            {
420                lock (ListLock)
421                    return List.Contains(item);
422            }
423
424            public void CopyTo(SteppedProgressManagerStep[] array, int arrayIndex)
425            {
426                lock (ListLock)
427                    List.CopyTo(array, arrayIndex);
428            }
429
430            public int Count
431            {
432                get
433                {
434                    lock (ListLock) 
435                        return List.Count;
436                }
437            }
438
439            public bool IsReadOnly
440            {
441                get { return false; }
442            }
443
444            public bool Remove(SteppedProgressManagerStep item)
445            {
446                int index = List.IndexOf(item);
447                if (index != -1)
448                    TotalWeights -= List[index].Weight;
449
450                return List.Remove(item);
451            }
452
453            #endregion
454
455            #region IEnumerable<SteppedProgressManagerStep> Members
456
457            public IEnumerator<SteppedProgressManagerStep> GetEnumerator()
458            {
459                return List.GetEnumerator();
460            }
461
462            #endregion
463
464            #region IEnumerable Members
465
466            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
467            {
468                return List.GetEnumerator();
469            }
470
471            #endregion
472
473            /// <summary>
474            /// The total weights of all the steps.
475            /// </summary>
476            private float TotalWeights
477            {
478                get
479                {
480                    return totalWeights;
481                }
482                set
483                {
484                    if (value >= 1.1f || value < 0.0f)
485                        throw new ArgumentOutOfRangeException("value", "The total weights of " +
486                            "all steps in the task must be within the range [0.0, 1.0]");
487
488                    totalWeights = value;
489                }
490            }
491
492            /// <summary>
493            /// The list storing the steps for this instance.
494            /// </summary>
495            private List<SteppedProgressManagerStep> List;
496
497            /// <summary>
498            /// The lock object guarding the list against parallel writes.
499            /// </summary>
500            private object ListLock;
501
502            /// <summary>
503            /// The backing variable for the total weights of all the steps.
504            /// </summary>
505            private float totalWeights;
506        }
507
508        /// <summary>
509        /// Constructor.
510        /// </summary>
511        public SteppedProgressManager()
512        {
513            ListLock = new object();
514            Steps = new StepsList(this);
515        }
516
517        public override float Progress
518        {
519            get
520            {
521                lock (ListLock)
522                    return Steps.Sum(step => step.Progress.Progress * step.Weight);
523            }
524        }
525
526        public override bool ProgressIndeterminate
527        {
528            get
529            {
530                lock (ListLock)
531                    return Steps.Any(x => x.Progress.ProgressIndeterminate);
532            }
533        }
534
535        public override float Speed
536        {
537            get
538            {
539                if ((DateTime.Now - lastSpeedCalc).TotalSeconds < SpeedCalcInterval)
540                    return lastSpeed;
541
542                //Calculate how much time has passed
543                double timeElapsed = (DateTime.Now - lastSpeedCalc).TotalSeconds;
544
545                //Then compute the speed of the calculation
546                float currentProgress = Progress;
547                float progressDelta = currentProgress - lastCompleted;
548                float currentSpeed = (float)(progressDelta / timeElapsed);
549                lastSpeedCalc = DateTime.Now;
550                lastCompleted = Progress;
551
552                //If the progress delta is zero, it usually means that the amount
553                //completed within the calculation interval is too short -- lengthen
554                //the interval so we can get a small difference, significant to make
555                //a speed calculation. Likewise, if it is too great a difference,
556                //we need to shorten the interval to get more accurate calculations
557                if (progressDelta == 0.0)
558                    SpeedCalcInterval += SpeedCalcInterval / 3;
559                else if (progressDelta > 0.01 && SpeedCalcInterval > 6)
560                    SpeedCalcInterval -= 3;
561
562                //We won't update the speed of the task if the current speed is within
563                //the lower and upper prediction interval.
564                Interval interval = PredictedSpeed;
565                if (interval != null)
566                {
567                    if (currentSpeed < interval.Minimum)
568                    {
569                        Restart();
570                        lastSpeed = currentSpeed;
571                    }
572                    else if (currentSpeed > interval.Maximum)
573                    {
574                        Restart();
575                        lastSpeed = currentSpeed;
576                    }
577                    else if (lastSpeed == 0.0f)
578                    {
579                        lastSpeed = currentSpeed;
580                    }
581                }
582
583                SampleSpeed(currentSpeed);
584                return lastSpeed;
585            }
586        }
587
588        public override TimeSpan TimeLeft
589        {
590            get
591            {
592                float speed = Speed;
593                float remaining = 1.0f - Progress;
594
595                if (speed == 0)
596                    return TimeSpan.MinValue;
597                else if (remaining <= 0)
598                    return TimeSpan.Zero;
599
600                try
601                {
602                    return TimeSpan.FromSeconds(remaining / speed);
603                }
604                catch (OverflowException)
605                {
606                    return TimeSpan.MaxValue;
607                }
608            }
609        }
610
611        /// <summary>
612        /// The list of steps involved in completion of the task.
613        /// </summary>
614        public IList<SteppedProgressManagerStep> Steps
615        {
616            get;
617            private set;
618        }
619
620        /// <summary>
621        /// Gets the current step which is executing. This property is null if
622        /// no steps are executing (also when the task is complete)
623        /// </summary>
624        public SteppedProgressManagerStep CurrentStep
625        {
626            get
627            {
628                lock (ListLock)
629                {
630                    if (Steps.Count == 0)
631                        return null;
632
633                    foreach (SteppedProgressManagerStep step in Steps)
634                        if (step.Progress.Progress < 1.0f)
635                            return step;
636
637                    //Return the last step since we don't have any
638                    return Steps[Steps.Count - 1];
639                }
640            }
641        }
642
643        /// <summary>
644        /// The lock object guarding the list of steps against concurrent read and write.
645        /// </summary>
646        private object ListLock;
647
648        /// <summary>
649        /// The amount of time elapsed before a new speed calculation is made.
650        /// </summary>
651        private int SpeedCalcInterval = 15;
652
653        /// <summary>
654        /// The last time a speed calculation was computed so that speed is not
655        /// computed too often.
656        /// </summary>
657        private DateTime lastSpeedCalc;
658
659        /// <summary>
660        /// The amount of the operation completed at the last speed computation.
661        /// </summary>
662        private float lastCompleted;
663
664        /// <summary>
665        /// The last calculated speed of the operation.
666        /// </summary>
667        private float lastSpeed;
668    }
669
670    /// <summary>
671    /// Represents one step in the list of steps to complete.
672    /// </summary>
673    public class SteppedProgressManagerStep
674    {
675        /// <summary>
676        /// Constructor.
677        /// </summary>
678        /// <param name="progress">The <see cref="ProgressManagerBase"/> instance
679        /// which measures the progress of this step.</param>
680        /// <param name="weight">The weight of this step. The weight is a decimal
681        /// number in the range [0.0, 1.0] which represents the percentage of the
682        /// entire process this particular step is.</param>
683        public SteppedProgressManagerStep(ProgressManagerBase progress, float weight)
684            : this(progress, weight, null)
685        {
686        }
687
688        /// <summary>
689        /// Constructor.
690        /// </summary>
691        /// <param name="progress">The <see cref="ProgressManagerBase"/> instance
692        /// which measures the progress of this step.</param>
693        /// <param name="weight">The weight of this step. The weight is a decimal
694        /// number in the range [0.0, 1.0] which represents the percentage of the
695        /// entire process this particular step is.</param>
696        /// <param name="name">A user-specified value of the name of this step.
697        /// This value is not used by the class at all.</param>
698        public SteppedProgressManagerStep(ProgressManagerBase progress, float weight, string name)
699        {
700            if (float.IsInfinity(weight) || float.IsNaN(weight))
701                throw new ArgumentException("The weight of a progress manager step must be " +
702                    "a valid floating-point value.");
703
704            Progress = progress;
705            Weight = weight;
706            Name = name;
707        }
708
709        /// <summary>
710        /// The <see cref="ProgressManagerBase"/> instance which measures the
711        /// progress of the step.
712        /// </summary>
713        public ProgressManagerBase Progress
714        {
715            get;
716            set;
717        }
718
719        /// <summary>
720        /// The weight associated with this step.
721        /// </summary>
722        public float Weight
723        {
724            get;
725            private set;
726        }
727
728        /// <summary>
729        /// The name of this step.
730        /// </summary>
731        public string Name
732        {
733            get;
734            set;
735        }
736    }
737
738    /// <summary>
739    /// Manages progress based on sub-tasks, assuming each sub-task to be independent
740    /// of the rest.
741    /// </summary>
742    public class ParallelProgressManager : ChainedProgressManager
743    {
744        /// <summary>
745        /// The class which manages the progress of each dependent task.
746        /// </summary>
747        private class SubTasksList : IList<ProgressManagerBase>
748        {
749            public SubTasksList(ParallelProgressManager manager)
750            {
751                List = new List<ProgressManagerBase>();
752                ListLock = manager.TaskLock;
753            }
754
755            #region IList<SubTasksList> Members
756
757            public int IndexOf(ProgressManagerBase item)
758            {
759                lock (ListLock)
760                    return List.IndexOf(item);
761            }
762
763            public void Insert(int index, ProgressManagerBase item)
764            {
765                lock (ListLock)
766                    List.Insert(index, item);
767            }
768
769            public void RemoveAt(int index)
770            {
771                lock (ListLock)
772                    List.RemoveAt(index);
773            }
774
775            public ProgressManagerBase this[int index]
776            {
777                get
778                {
779                    lock (ListLock)
780                        return List[index];
781                }
782                set
783                {
784                    lock (ListLock)
785                        List[index] = value;
786                }
787            }
788
789            #endregion
790
791            #region ICollection<SteppedProgressManagerStep> Members
792
793            public void Add(ProgressManagerBase item)
794            {
795                lock (ListLock)
796                    List.Add(item);
797            }
798
799            public void Clear()
800            {
801                lock (ListLock)
802                    List.Clear();
803            }
804
805            public bool Contains(ProgressManagerBase item)
806            {
807                return List.Contains(item);
808            }
809
810            public void CopyTo(ProgressManagerBase[] array, int arrayIndex)
811            {
812                lock (ListLock)
813                    List.CopyTo(array, arrayIndex);
814            }
815
816            public int Count
817            {
818                get
819                {
820                    lock (ListLock) 
821                        return List.Count;
822                }
823            }
824
825            public bool IsReadOnly
826            {
827                get { return false; }
828            }
829
830            public bool Remove(ProgressManagerBase item)
831            {
832                lock (ListLock)
833                    return List.Remove(item);
834            }
835
836            #endregion
837
838            #region IEnumerable<ProgressManagerBase> Members
839
840            public IEnumerator<ProgressManagerBase> GetEnumerator()
841            {
842                return List.GetEnumerator();
843            }
844
845            #endregion
846
847            #region IEnumerable Members
848
849            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
850            {
851                return List.GetEnumerator();
852            }
853
854            #endregion
855
856            /// <summary>
857            /// The list storing the steps for this instance.
858            /// </summary>
859            private List<ProgressManagerBase> List;
860
861            /// <summary>
862            /// The lock object guarding the list from concurrent read/writes.
863            /// </summary>
864            private object ListLock;
865        }
866
867        /// <summary>
868        /// Constructor.
869        /// </summary>
870        public ParallelProgressManager()
871        {
872            Tasks = new SubTasksList(this);
873            TaskLock = new object();
874        }
875
876        public override float Progress
877        {
878            get
879            {
880                lock (TaskLock)
881                    return Tasks.Sum(task => task.Progress * (1.0f / Tasks.Count));
882            }
883        }
884
885        public override bool ProgressIndeterminate
886        {
887            get
888            {
889                lock (TaskLock)
890                    return Tasks.Any(x => x.ProgressIndeterminate);
891            }
892        }
893
894        public override float Speed
895        {
896            get
897            {
898                lock (TaskLock)
899                    return Tasks.Max(task => task.Speed);
900            }
901        }
902
903        public override TimeSpan TimeLeft
904        {
905            get
906            {
907                lock (TaskLock)
908                    return Tasks.Max(task => task.TimeLeft);
909            }
910        }
911
912        /// <summary>
913        /// Gets the list of tasks which must complete execution before the task
914        /// is completed.
915        /// </summary>
916        public IList<ProgressManagerBase> Tasks
917        {
918            get;
919            private set;
920        }
921
922        /// <summary>
923        /// The lock object guarding the list of tasks against concurrent read and write.
924        /// </summary>
925        private object TaskLock;
926    }
927
928    /// <summary>
929    /// Provides data for the Eraser.Manager.ProgressChanged event.
930    /// </summary>
931    public class ProgressChangedEventArgs : EventArgs
932    {
933        /// <summary>
934        /// Constructor.
935        /// </summary>
936        /// <param name="progress">The ProgressManagerBase object that stores the progress
937        /// for the given task.</param>
938        /// <param name="userState">A client-specified state object.</param>
939        public ProgressChangedEventArgs(ProgressManagerBase progress, object userState)
940        {
941            Progress = progress;
942            UserState = userState;
943        }
944
945        /// <summary>
946        /// The ProgressManagerBase object that stores the progress for the given
947        /// task.
948        /// </summary>
949        public ProgressManagerBase Progress { get; private set; }
950
951        /// <summary>
952        /// A client-specified state object.
953        /// </summary>
954        public object UserState { get; private set; }
955    }
956
957    /// <summary>
958    /// Represents the method that will handle the ProgressChanged event from
959    /// the <see cref="ProgressManagerBase"/> class.
960    /// </summary>
961    /// <param name="sender">The source of the event.</param>
962    /// <param name="e">A <see cref="ProgressChangedEventArgs"/> event that
963    /// stores the event data.</param>
964    public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
965}
Note: See TracBrowser for help on using the repository browser.