Comment utiliser la barre de progression WinForms?

101

Je veux montrer la progression des calculs, qui sont exécutés dans une bibliothèque externe.

Par exemple, si j'ai une méthode de calcul et que je veux l'utiliser pour 100000 valeurs dans ma classe Form, je peux écrire:

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

Je devrais effectuer une étape après chaque calcul. Mais que faire si j'effectue tous les 100000 calculs en méthode externe. Quand dois-je "exécuter l'étape" si je ne veux pas que cette méthode dépende de la barre de progression? Je peux, par exemple, écrire

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

mais je ne veux pas faire comme ça.

Dmytro
la source
4
Passez un objet délégué à la méthode.
Hans Passant

Réponses:

112

Je vous suggère de jeter un œil à BackgroundWorker . Si vous avez une boucle aussi grande dans votre WinForm, elle se bloquera et votre application aura l'air de se bloquer.

Regardez BackgroundWorker.ReportProgress()pour voir comment rendre compte de la progression vers le fil de discussion de l'interface utilisateur.

Par exemple:

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
la source
5
Bel exemple, mais il y a une petite erreur dans votre code. Vous devez définir backgroundWorker.WorkerReportsProgress sur true. Vérifier mon montage
Mana
1
@mana L'hypothèse est que le BackgroundWorkerest ajouté via le concepteur et configuré ici. Mais oui, il devra être configuré pour être WorkerReportsProgressdéfini sur true.
Peter Ritchie
ah, mon mauvais, je ne savais pas que vous pouviez le définir dans le concepteur
Mana
Vous vous demandez quelque chose d'autre cependant, votre exemple ne compte que 99 backgroundWorker.ReportProgress ((j * 100) / 100000); comment obtenir 100% compte
Mana
1
@mana Si vous voulez afficher les 100% de la progression, faites-le dans le RunWorkerCompletedgestionnaire d'événements, si votre DoWorkgestionnaire ne le fait pas ..
Peter Ritchie
77

Depuis .NET 4.5, vous pouvez utiliser une combinaison d' async et attendre avec Progress pour l'envoi de mises à jour au thread d'interface utilisateur:

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
}

Les tâches sont actuellement le moyen préféré de mettre en œuvre ce que BackgroundWorkerfait.

Les tâches et Progresssont expliquées plus en détail ici:

quasoft
la source
2
Cela devrait être la réponse choisie, l'OMI. Très bonne réponse!
JohnOpincar
Presque presque la meilleure réponse, sauf qu'elle utilise Task.Run au lieu d'une fonction asynchrone normale
KansaiRobot
@KansaiRobot Vous voulez dire par opposition à await DoWorkAsync(progress);? C'est tout à fait exprès, car cela n'entraînerait pas un thread supplémentaire en cours d'exécution. Ce n'est que si la DoWorkAsyncfonction s'appellerait elle-même awaitpour, par exemple, attendre une opération d'E / S, que la button1_Clickfonction continuerait. Le thread d'interface utilisateur principal est bloqué pendant cette durée. Si ce DoWorkAsyncn'est pas vraiment asynchrone mais juste beaucoup d'instructions synchrones, vous ne gagnez rien.
Wolfzoon
System.Windows.Controls.ProgressBar ne contient pas de champ "Step". Il devrait être supprimé de l'exemple; d'autant plus qu'il n'est de toute façon pas utilisé.
Robert Tausig
2
@RobertTausig Il est vrai que ce Stepn'est disponible que dans la barre de progression de WinForms et n'est pas nécessaire ici, mais Il était présent dans l'exemple de code de la question (winforms étiqueté), donc peut-être qu'il pourrait rester.
quasoft
4

Hé, il y a un tutoriel utile sur les perles Dot Net: http://www.dotnetperls.com/progressbar

En accord avec Peter, vous devez utiliser une certaine quantité de threading ou le programme se bloque, ce qui va quelque peu à l'encontre de l'objectif.

Exemple qui utilise ProgressBar et 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
la source
1

Il Taskexiste, c'est unnesscery en utilisant BackgroundWorker, Taskc'est plus simple. par exemple:

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

Terminé! Ensuite, vous pouvez réutiliser ProgressDialog n'importe où:

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
la source
Veuillez ne pas publier de réponses identiques à plusieurs questions . Publiez une bonne réponse, puis votez / marquez pour fermer les autres questions comme des doublons. Si la question n'est pas un doublon, adaptez vos réponses à la question .
Martijn Pieters