¿Cómo usar la barra de progreso de WinForms?

101

Quiero mostrar el progreso de los cálculos, que se realizan en una biblioteca externa.

Por ejemplo, si tengo algún método de cálculo y quiero usarlo para 100000 valores en mi clase de formulario, puedo escribir:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }            

    private void Caluculate(int i)
    {
        double pow = Math.Pow(i, i);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        progressBar1.Maximum = 100000;
        progressBar1.Step = 1;

        for(int j = 0; j < 100000; j++)
        {
            Caluculate(j);
            progressBar1.PerformStep();
        }
    }
}

Debo realizar un paso después de cada cálculo. Pero, ¿qué pasa si realizo todos los 100000 cálculos en un método externo? ¿Cuándo debo "realizar el paso" si no quiero que este método dependa de la barra de progreso? Puedo, por ejemplo, escribir

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void CaluculateAll(System.Windows.Forms.ProgressBar progressBar)
    {
        progressBar.Maximum = 100000;
        progressBar.Step = 1;

        for(int j = 0; j < 100000; j++)
        {
            double pow = Math.Pow(j, j); //Calculation
            progressBar.PerformStep();
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        CaluculateAll(progressBar1);
    }
}

pero no quiero hacer eso.

Dmytro
fuente
4
Pase un objeto delegado al método.
Hans Passant

Respuestas:

112

Te sugiero que eches un vistazo a BackgroundWorker . Si tiene un bucle tan grande en su WinForm, se bloqueará y su aplicación parecerá que se ha colgado.

Mire BackgroundWorker.ReportProgress()para ver cómo informar el progreso al hilo de la interfaz de usuario.

Por ejemplo:

private void Calculate(int i)
{
    double pow = Math.Pow(i, i);
}

private void button1_Click(object sender, EventArgs e)
{
    progressBar1.Maximum = 100;
    progressBar1.Step = 1;
    progressBar1.Value = 0;
    backgroundWorker.RunWorkerAsync();
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    var backgroundWorker = sender as BackgroundWorker;
    for (int j = 0; j < 100000; j++)
    {
        Calculate(j);
        backgroundWorker.ReportProgress((j * 100) / 100000);
    }
}

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar1.Value = e.ProgressPercentage;
}

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // TODO: do something with final calculation.
}
Peter Ritchie
fuente
5
Buen ejemplo, pero hay un pequeño error en su código. Debe establecer backgroundWorker.WorkerReportsProgress en verdadero. Verifique mi edición
Mana
1
@mana La suposición es que BackgroundWorkerse agrega a través del diseñador y se configura allí. Pero sí, deberá configurarse para que se WorkerReportsProgressestablezca en true.
Peter Ritchie
ah, mi mal, no sabía que se podía configurar en el diseñador
Mana
Sin embargo, preguntándose algo más, su ejemplo solo cuenta hasta 99 backgroundWorker.ReportProgress ((j * 100) / 100000); cómo obtener el 100% de recuento
Mana
1
@mana Si quieres mostrar el 100% en el progreso, hazlo en el RunWorkerCompletedcontrolador de eventos, si tu DoWorkcontrolador no lo hace ..
Peter Ritchie
77

Desde .NET 4.5, puede usar la combinación de async y esperar con Progress para enviar actualizaciones al hilo de la interfaz de usuario:

private void Calculate(int i)
{
    double pow = Math.Pow(i, i);
}

public void DoWork(IProgress<int> progress)
{
    // This method is executed in the context of
    // another thread (different than the main UI thread),
    // so use only thread-safe code
    for (int j = 0; j < 100000; j++)
    {
        Calculate(j);

        // Use progress to notify UI thread that progress has
        // changed
        if (progress != null)
            progress.Report((j + 1) * 100 / 100000);
    }
}

private async void button1_Click(object sender, EventArgs e)
{
    progressBar1.Maximum = 100;
    progressBar1.Step = 1;

    var progress = new Progress<int>(v =>
    {
        // This lambda is executed in context of UI thread,
        // so it can safely update form controls
        progressBar1.Value = v;
    });

    // Run operation in another thread
    await Task.Run(() => DoWork(progress));

    // TODO: Do something after all calculations
}

Las tareas son actualmente la forma preferida de implementar lo que BackgroundWorkerhace.

Tareas y Progressse explican con más detalle aquí:

casi suave
fuente
2
Esta debería ser la respuesta seleccionada, en mi opinión. ¡Gran respuesta!
JohnOpincar
Casi casi la mejor respuesta, excepto que está usando Task.Run en lugar de una función asíncrona normal
KansaiRobot
@KansaiRobot ¿Te refieres a await DoWorkAsync(progress);? Esto es muy a propósito, ya que eso no daría como resultado la ejecución de un hilo adicional. Solo si DoWorkAsyncse llamara a sí mismo, awaitpor ejemplo, esperando una operación de E / S, la button1_Clickfunción continuaría. El hilo principal de la interfaz de usuario está bloqueado durante este tiempo. Si DoWorkAsyncno es realmente asincrónico, sino muchas declaraciones sincrónicas, no gana nada.
Wolfzoon
System.Windows.Controls.ProgressBar no contiene ningún campo "Paso". Debe eliminarse del ejemplo; especialmente porque no se usa de todos modos.
Robert Tausig
2
@RobertTausig Es cierto que Stepsolo está disponible en la barra de progreso de WinForms y no es necesario aquí, pero estaba presente en el código de ejemplo de la pregunta (winforms etiquetados), por lo que podría permanecer.
quasoft
4

Hola, hay un tutorial útil sobre perlas Dot Net: http://www.dotnetperls.com/progressbar

De acuerdo con Peter, necesita usar cierta cantidad de subprocesos o el programa simplemente se bloqueará, frustrando un poco el propósito.

Ejemplo que usa ProgressBar y BackgroundWorker: C #

using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Start the BackgroundWorker.
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 1; i <= 100; i++)
            {
                // Wait 100 milliseconds.
                Thread.Sleep(100);
                // Report progress.
                backgroundWorker1.ReportProgress(i);
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Change the value of the ProgressBar to the BackgroundWorker progress.
            progressBar1.Value = e.ProgressPercentage;
            // Set the text.
            this.Text = e.ProgressPercentage.ToString();
        }
    }
} //closing here
Chong Ching
fuente
1

Hay Taskexiste, es un uso no comestible BackgroundWorker, Taskes más simple. por ejemplo:

ProgressDialog.cs:

   public partial class ProgressDialog : Form
    {
        public System.Windows.Forms.ProgressBar Progressbar { get { return this.progressBar1; } }

        public ProgressDialog()
        {
            InitializeComponent();
        }

        public void RunAsync(Action action)
        {
            Task.Run(action);
        }
    }

¡Hecho! Entonces puedes reutilizar ProgressDialog en cualquier lugar:

var progressDialog = new ProgressDialog();
progressDialog.Progressbar.Value = 0;
progressDialog.Progressbar.Maximum = 100;

progressDialog.RunAsync(() =>
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(1000)
        this.progressDialog.Progressbar.BeginInvoke((MethodInvoker)(() => {
            this.progressDialog.Progressbar.Value += 1;
        }));
    }
});

progressDialog.ShowDialog();
TangMonk
fuente
Por favor, no publiques respuestas idénticas a múltiples preguntas . Publique una buena respuesta, luego vote / marque para cerrar las otras preguntas como duplicadas. Si la pregunta no es un duplicado, adapte sus respuestas a la pregunta .
Martijn Pieters