开发者

implementing a progress bar to show work being done

I have one form Form1, and it contains two elements button and progress bar. When I click on button it calls class.SomeFunction(), which then calls a few other functions from different classes to complete some work.

I would like to increase value of progress bar from inside class.SomeFu开发者_如何学Cnctin() and all other functions that SomeFunctino call

class @class
{
    public static void SomeFunction(var something)
    {
       progressbar.value++; 
       class2.Function2(something);    
    }   
}

class class2
{
    public static void Function2(var something)
    {
        progressbar.value++;    
    }   
}

How can this be done?


You really shouldn't have those functions update the progressbar-- it violates the single responsibility principle. You are better off using a backgroundworker or just update the progressbar from within your button_click event after each function call.


If you are doing something that takes so long you have to show a progress bar, then you should be doing it in a background thread and not the form. That will make the UI become unresponsive.

A Code Project BackgroundWorker Thread article has an example of a background thread that shows a progress bar in a WinForms app.


The easiest way for you to do this is simply to call an event that is handled in your form, and in the event handler have that increment the progress bar.

What you will first want to do is create a custom EventArgs.

public class ProgressEventArgs : EventArgs
{
    public int Progress {get; set;}
    public ProgressEventArgs(int progress)
    {
        Progress = progress;
    }
}

Then in your classes that you want to increment the Progress bar you will want to raise this event.

class Class2
{
    public event EventHandler<ProgressEventArgs> ProgressEvent;

    public void Function2(var something)
    {
        OnRaiseProgressEvent(new ProgressEventArgs(1));
    }

    protected virtual void OnRaiseProgressEvent(ProgressEventArgs e)
    {
        // C# 6 and above:
        // Raise event if event handler is set (i.e. not null)
        ProgressEvent?.Invoke(this, e);
        // end C# >=6 code

        // C# 5 and earlier:
        EventHandler<ProgressEventArgs> handler = ProgressEvent;

        if(handler != null)
        {
            //this is what actually raises the event.
            handler(this, e);
        }
        // end C# <=5 code
    }
}

Then in your form you will want to subscribe to the event

public class YourForm
{
    public YourForm
    {
        Class2 yourClass2Instance = new Class2();
        yourClass2Instance.ProgressEvent += ProgressEventHandler;
    }

    private void ProgressEventHandler(object sender, ProgressEventArgs e)
    {
       progressbar.Value += e.Progress;
    }

}


In the past I've used a simple menu bar-less form with a label and a Forms.ProgressBar on it using the following code in the form:

public partial class ProgressDialog : Form
{
    //public delegate delSetProgress 
private readonly int progressBarMax;

/// <summary>
/// Structure used for passing progress bar related parameters as a single variable.
/// </summary>
public struct ProgressBarParams
{
    public int value;
    public string message;

    public ProgressBarParams(string Message, int Value)
    {
        message = Message;
        value = Value;
    }
}

/// <summary>
/// Constructs the progress bar dialog and sets the progress bar's maximum value to maxValue.
/// </summary>
/// <param name="maxValue">Value to set to progress bar's Maximum property.</param>
public ProgressDialog(int maxValue)
{
    InitializeComponent();
    progressBarMax = maxValue;
}

private void ProgressDialog_Load(object sender, EventArgs e)
{
    progressBar.Maximum = progressBarMax;
}

/// <summary>
/// Public method to update the progressDialog
/// </summary>
/// <param name="inputParams">Values to update on the progressDialog</param>
public void SetProgress(ProgressBarParams inputParams)
{
    lblMessage.Text = inputParams.message;
    progressBar.setValue(inputParams.value);
    Update();
}

/// <summary>
/// This method should be called when the operation represented by the ProgressDialog is
/// completed. It shows an "operation complete" message for a second and then closes the form.
/// </summary>
public void Finish()
{
    lblMessage.Text = "Operation complete.";
    progressBar.setValue(progressBar.Maximum);
    Update();
    System.Threading.Thread.Sleep(1000);
        this.Close();
    }
}

public static class MyExtensions
{
    /// <summary>
    /// Implements a hack to get around a stupid rendering problem with the .NET progress bar in some environments.
    /// Sets the progress bar value property.
    /// </summary>
    /// <param name="proBar">Progress bar control to set the value on.</param>
    /// <param name="value">Value to be set.</param>
    public static void setValue(this ProgressBar proBar, int value)
    {
        if (value > 0)
        {
            proBar.Value = value;
            proBar.Value = value - 1;
            proBar.Value = value;
        }
        else
        {
            proBar.Value = value;
            proBar.Value = value + 1;
            proBar.Value = value;
        }
    }
} 

Note the setValue extension method that uses a workaround to avoid an issue with some versions of Windows.

I then set it (and a splash screen) up with the following, where m_frmProgress is the progress bar form:

        // Create splash screen/progress bar thread on the thread pool
        ThreadPool.QueueUserWorkItem((x) =>
        {
            bool localDone = false;
            m_frmSplash.Show();
            m_frmProgress.Show();
                // Set the progress bar form up slightly below the bottom of the splash screen
                m_frmProgress.Location = new Point(m_frmProgress.Location.X, m_frmProgress.Location.Y + (m_frmSplash.Height / 2) + (m_frmProgress.Height / 2) + 10);    

                while (!localDone)  // Keep iterating until the main thread tells us we're done
                {
                    lock (splashScreenDoneLock)
                    {
                        localDone = splashScreenDone;
                    }
                    // I don't like this method of keeping the UI responsive, but as yet I have no better method
                    Application.DoEvents();
                    Thread.Sleep(500);
                }
                m_frmSplash.Close();
                m_frmProgress.Close();
                while (!m_frmProgress.IsDisposed || !m_frmSplash.IsDisposed)    // While either splash form is not disposed (still active)
                {
                    Thread.Sleep(100);  // Keep waiting until the splash forms are gone
                }
                splashFormsDisposed.Set();  // Signal the main thread that the splash forms are gone so the main form can be shown
            });

            bool isSplashHandleCreated = false;
            bool isProgressHandleCreated = false;

            // Wait until both splash forms are created
            while (!(isSplashHandleCreated && isProgressHandleCreated))
            {
                lock (m_frmSplash)
                {
                    isSplashHandleCreated = m_frmSplash.IsHandleCreated;
                }
                lock (m_frmProgress)
                {
                    isProgressHandleCreated = m_frmProgress.IsHandleCreated;
                }
                Thread.Sleep(500);
            }

And invoke it like this:

m_frmProgress.Invoke(new Action<ProgressDialog.ProgressBarParams>(m_frmProgress.SetProgress), progressLevel);

It's not the most elegant method, but it gives you a cleanly update-able progress bar on a separate thread that will stay responsive while you're messing with it. I pretty much copy and pasted all that code from a working app, so it should work. On the flip side, I apologize if any of it is unclear.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜