Click here to Skip to main content
15,893,161 members
Articles / Desktop Programming / Windows Forms

Progress Reporting Framework

Rate me:
Please Sign up or sign in to vote.
4.82/5 (10 votes)
7 Sep 2011CPOL9 min read 31.3K   836   51  
Reporting Progress for Complex Algorithms
// This file is part of the ProgressTracker library
// Copyright: Andreas Raczek
// This file is published under the The Code Project Open License (CPOL) 
// See the file "CPOL.html" for the full license governing this code. 

using System.ComponentModel;
using System.Threading;
using System;

namespace ProgressTracker
{
    public class NewsCaster : INewsCaster
    {
        private readonly object memberLocker = new object();
        private readonly object syncContextLocker = new object();

        private IReporter rootReporter = null;
        private SynchronizationContext syncContext = null;

        private event PropertyChangedEventHandler propertyChanged;
        private object propertyChangedLocker = new object();


        ProgressDiagramDlg progressDiagramDlg = null;
        public bool EnableProgressResultDlg
        {
            set
            {
                if (value) progressDiagramDlg = new ProgressDiagramDlg();
                else progressDiagramDlg = null;
            }
        }
        
        private event Action finishedEvent;
        private readonly object finishedEventLocker = new object();
        public event Action Finished
        {
            add
            {
                lock (this.finishedEventLocker)
                {
                    this.finishedEvent += value;
                }
            }

            remove
            {
                lock (this.finishedEventLocker)
                {
                    this.finishedEvent -= value;
                }
            }
        }

        /// <summary>
        /// Stores the time in ms when the progress was updated last time
        /// </summary>
        private int timeLastUpdate = 0;

        /// <summary>
        /// Start Time
        /// </summary>
        private int timeStart = 0;

        /// <summary>
        /// Will be set true once the root reporter indicates completion
        /// of the assigned task.
        /// </summary>
        private bool isFinished = false;

        /// <summary>
        /// Contains the cached progress value that is queried
        /// by <see cref="Progress"/>
        private int progress = 0;

        /// <summary>
        /// Returns the cached value of the current progress
        /// <see cref="progress"/>
        /// </summary>
        public int Progress
        {
            get
            {
                lock (this.memberLocker)
                {
                    return progress;
                }
            }
        }

        private int getTimeElapsed() 
        {
            return timeLastUpdate - timeStart;
        }

        public int TimeElapsed
        {
            get
            {
                lock (this.memberLocker)
                {
                    return getTimeElapsed();
                }
            }
        }

        /// <summary>
        /// </summary>
        private int getTimeEstimated()
        {
            int timeElapsed = timeLastUpdate - timeStart;

            if (timeElapsed <= 0) return -1;
            if (progress <= 0) return -1;

            return timeElapsed * 100 / progress;
        }

        public int TimeEstimated
        {
            get
            {
                lock (this.memberLocker)
                {
                    return getTimeEstimated();
                }
            }
        }
        
        public NewsCaster() 
        {
            lock (this.syncContextLocker)
            {
                this.syncContext = SynchronizationContext.Current;
                if (this.syncContext == null)
                {
                    // We are already in a worker thread and create a transparent
                    // synchronizationContext
                    this.syncContext = new SynchronizationContext();
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged
        {
            add 
            {
                lock (this.propertyChangedLocker)
                {
                    this.propertyChanged += value;
                }
            }

            remove 
            {
                lock (this.propertyChangedLocker)
                {
                    this.propertyChanged -= value;
                }
            }
        }


        public IReporter CreateReporter()
        {
            lock (this.memberLocker)
            {
                this.timeStart = System.Environment.TickCount;
                this.timeLastUpdate = 0;
                this.progress = 0;
                this.isFinished = false;

                this.rootReporter = new Reporter(this);

                return this.rootReporter;
            }
        }
       
        public void OnProgressChanged() 
        {
            bool sendUpdate = false;

            lock (this.memberLocker) 
            {
                if (this.isFinished) return;

                int nowTime = System.Environment.TickCount;

                // 100 ms  must be passed since last update
                if (nowTime - this.timeLastUpdate > 100)
                {
                    int progressNow = (int) ((this.rootReporter.GetProgress() * 100) + 0.5d);
                    if (progressNow > 100) progressNow = 100;
                    if (progressNow < 0) progressNow = 0;

                    this.timeLastUpdate = nowTime;
                    
                    this.progress = progressNow;

                    if (null != progressDiagramDlg)
                    {
                        progressDiagramDlg.AddDataSet(
                            getTimeElapsed(),
                            progress,
                            getTimeEstimated());
                    }

                    sendUpdate = true;
                }
            }

            if (sendUpdate) OnPropertyChanged("Progress");
        }

        /// <summary>
        /// Internally triggered by OnProgressChanged() to raise PropertyChanged event
        /// </summary>
        private void OnPropertyChanged(string propertyName)
        {
            lock (this.syncContextLocker)
            {
                this.syncContext.Post(
                    new SendOrPostCallback((object state) =>
                    {
                        PropertyChangedEventHandler h = null;
                        lock (this.propertyChangedLocker)
                        {
                            h = this.propertyChanged;
                        }

                        if (h != null)
                        {
                            h(this, new PropertyChangedEventArgs(propertyName));
                        }
                    }),
                    null);
            }
        }

        /// <summary>
        /// Internally triggered by OnProgressChanged() to raise Finished event
        /// </summary>
        public void Finish()
        {
            lock (this.memberLocker)
            {
                this.isFinished = true;

                // First make sure, progress is set to 100
                this.progress = 100;
            }

            OnPropertyChanged("Progress");

            // Then show statistics and raise Finished event 
            lock (this.syncContextLocker)
            {
                if (null != progressDiagramDlg)
                {
                    this.syncContext.Post(
                        new SendOrPostCallback((object state) =>
                        {
                            progressDiagramDlg.Show();
                        }),
                        null);
                }

                this.syncContext.Post(
                    new SendOrPostCallback((object state) =>
                    {
                        Action h = null;
                        lock (this.finishedEventLocker)
                        {
                            h = this.finishedEvent;
                        }

                        if (h != null)
                        {
                            h();
                        }
                    }),
                    null);
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions