Come utilizzare la barra di avanzamento di WinForms?


101

Voglio mostrare lo stato di avanzamento dei calcoli, che vengono eseguiti nella libreria esterna.

Ad esempio, se ho un metodo di calcolo e voglio usarlo per 100000 valori nella mia classe Form, posso scrivere:

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

Dovrei eseguire il passaggio dopo ogni calcolo. Ma cosa succede se eseguo tutti i 100000 calcoli in metodo esterno. Quando devo "eseguire il passaggio" se non voglio che questo metodo dipenda dalla barra di avanzamento? Posso, ad esempio, scrivere

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

ma non voglio fare così.


4
Passare un oggetto delegato al metodo.
Hans Passant

Risposte:


112

Ti suggerisco di dare un'occhiata a BackgroundWorker . Se hai un loop così grande nel tuo WinForm, si bloccherà e la tua app sembrerà bloccata.

Guarda BackgroundWorker.ReportProgress()per vedere come riportare i progressi nel thread dell'interfaccia utente.

Per esempio:

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
Bell'esempio, ma c'è un piccolo errore nel codice. È necessario impostare backgroundWorker.WorkerReportsProgress su true. Controlla la mia modifica
Mana

1
@mana Il presupposto è che BackgroundWorkervenga aggiunto tramite il designer e configurato lì. Ma sì, dovrà essere configurato per essere WorkerReportsProgressimpostato su true.
Peter Ritchie

ah, colpa mia, non sapevo che potevi impostarlo nel designer
Mana

Chiedendosi qualcos'altro, il tuo esempio conta solo fino a 99 backgroundWorker.ReportProgress ((j * 100) / 100000); come ottenere il 100% di conteggio
Mana

1
@mana Se vuoi mostrare il 100% in corso, fallo nel RunWorkerCompletedgestore dell'evento, se il tuo DoWorkgestore non lo fa ..
Peter Ritchie

77

A partire da .NET 4.5 è possibile utilizzare la combinazione di async e await con Progress per inviare aggiornamenti al thread dell'interfaccia utente:

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
}

Le attività sono attualmente il modo preferito per implementare ciò che BackgroundWorkerfa.

Le attività e Progresssono spiegate in modo più dettagliato qui:


2
Questa dovrebbe essere la risposta selezionata, IMO. Bella risposta!
JohnOpincar

Quasi quasi la risposta più carina, tranne per il fatto che sta usando Task.Run invece di una normale funzione asincrona
KansaiRobot

@KansaiRobot Intendi al contrario di await DoWorkAsync(progress);? Questo è molto apposta, in quanto ciò non comporterebbe un thread aggiuntivo in esecuzione. Solo se DoWorkAsyncchiamasse il proprio awaitper esempio in attesa di un'operazione di I / O, la button1_Clickfunzione continuerebbe. Il thread dell'interfaccia utente principale è bloccato per questa durata. Se DoWorkAsyncnon è realmente asincrono ma solo molte istruzioni sincrone, non guadagni nulla.
Wolfzoon

System.Windows.Controls.ProgressBar non contiene un campo "Step". Dovrebbe essere rimosso dall'esempio; soprattutto perché non viene utilizzato comunque.
Robert Tausig

2
@RobertTausig È vero che Stepè disponibile solo nella barra di avanzamento di WinForms e non è necessario qui, ma era presente nel codice di esempio della domanda (winform con tag), quindi potrebbe rimanere.
quasoft

4

Ehi, c'è un utile tutorial sulle perle Dot Net: http://www.dotnetperls.com/progressbar

In accordo con Peter, è necessario utilizzare una certa quantità di thread o il programma si bloccherà, vanificando in qualche modo lo scopo.

Esempio che utilizza 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

Esiste Task, è usare disagio BackgroundWorker, Taskè più semplice. per esempio:

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

Fatto! Quindi puoi riutilizzare ProgressDialog ovunque:

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

Si prega di non pubblicare risposte identiche a più domande . Pubblica una buona risposta, quindi vota / contrassegna per chiudere le altre domande come duplicati. Se la domanda non è un duplicato, adatta le tue risposte alla domanda .
Martijn Pieters
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.