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.


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.
}

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:


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

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.