Como usar a barra de progresso do WinForms?

101

Quero mostrar o andamento dos cálculos, que estão sendo executados na biblioteca externa.

Por exemplo, se eu tiver algum método de cálculo e quiser usá-lo para 100.000 valores em minha classe Form, posso escrever:

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();
        }
    }
}

Devo realizar a etapa após cada cálculo. Mas e se eu executar todos os 100.000 cálculos no método externo. Quando devo "executar a etapa" se não quiser tornar esse método dependente da barra de progresso? Posso, por exemplo, escrever

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);
    }
}

mas eu não quero fazer assim.

Dmytro
fonte
4
Passe um objeto delegado para o método.
Hans Passant

Respostas:

112

Eu sugiro que você dê uma olhada em BackgroundWorker . Se você tiver um loop tão grande em seu WinForm, ele será bloqueado e seu aplicativo parecerá ter travado.

Consulte BackgroundWorker.ReportProgress()para ver como relatar o progresso de volta para o thread de interface do usuário.

Por exemplo:

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
fonte
5
Bom exemplo, mas há um pequeno erro em seu código. Você precisa definir backgroundWorker.WorkerReportsProgress como true. Verifique minha edição
Mana
1
@mana Presume-se que o BackgroundWorkeré adicionado por meio do designer e configurado lá. Mas sim, ele precisará ser configurado para WorkerReportsProgressdefinir como true.
Peter Ritchie
ah, que pena, não sabia que dava para configurá-lo no designer
Mana
Pensando em outra coisa, seu exemplo só conta para 99 backgroundWorker.ReportProgress ((j * 100) / 100000); como obter a contagem de 100%
Mana
1
@mana Se você quiser mostrar 100% no progresso, faça no RunWorkerCompletedmanipulador de eventos, se o seu DoWorkmanipulador não o fizer ..
Peter Ritchie
77

Desde o .NET 4.5, você pode usar a combinação de assíncrono e esperar com Progresso para enviar atualizações ao thread de interface do usuário:

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
}

As tarefas são atualmente a forma preferida de implementar o que BackgroundWorkerfaz.

As tarefas Progresssão explicadas com mais detalhes aqui:

quasoft
fonte
2
Esta deve ser a resposta selecionada, IMO. Ótima resposta!
JohnOpincar
Quase quase a melhor resposta, exceto que está usando Task.Run em vez de uma função assíncrona normal
KansaiRobot
@KansaiRobot Você quer dizer ao invés de await DoWorkAsync(progress);? Isso é muito proposital, pois não resultaria na execução de um thread extra. Somente se DoWorkAsyncchamar seu próprio awaitpara, por exemplo, aguardar uma operação de E / S, a button1_Clickfunção continuará. O thread principal da IU está bloqueado durante esse período. Se DoWorkAsyncnão for realmente assíncrono, mas apenas um monte de instruções síncronas, você não ganha nada.
Wolfzoon 01 de
System.Windows.Controls.ProgressBar não contém um campo "Etapa". Deve ser removido do exemplo; especialmente porque não é usado de qualquer maneira.
Robert Tausig
2
@RobertTausig É verdade que Stepestá disponível apenas na barra de progresso do WinForms e não é necessário aqui, mas estava presente no código de exemplo da pergunta (winforms marcados), então pode ser que possa ficar.
quasoft
4

Olá, há um tutorial útil sobre pérolas Dot Net: http://www.dotnetperls.com/progressbar

De acordo com Peter, você precisa usar alguma quantidade de threading ou o programa simplesmente travará, frustrando de alguma forma o propósito.

Exemplo que usa ProgressBar e 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
fonte
1

Existe Taskexiste, é unnesscery usando BackgroundWorker,Task é mais simples. por exemplo:

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);
        }
    }

Feito! Então você pode reutilizar ProgressDialog em qualquer 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
fonte
Por favor , não poste respostas idênticas para várias perguntas . Poste uma boa resposta e, em seguida, vote / sinalize para fechar as outras questões como duplicatas. Se a pergunta não for uma duplicata, adapte suas respostas à pergunta .
Martijn Pieters