Wie verwende ich den WinForms-Fortschrittsbalken?

101

Ich möchte den Fortschritt von Berechnungen zeigen, die in einer externen Bibliothek ausgeführt werden.

Wenn ich beispielsweise eine Berechnungsmethode habe und diese für 100000 Werte in meiner Formularklasse verwenden möchte, kann ich Folgendes schreiben:

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

Ich sollte nach jeder Berechnung einen Schritt ausführen. Was aber, wenn ich alle 100000 Berechnungen in einer externen Methode durchführe? Wann sollte ich "Schritt ausführen", wenn ich diese Methode nicht vom Fortschrittsbalken abhängig machen möchte? Ich kann zum Beispiel schreiben

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

aber so will ich das nicht machen.

Dmytro
quelle
4
Übergeben Sie ein Delegatenobjekt an die Methode.
Hans Passant

Antworten:

112

Ich würde vorschlagen, dass Sie sich BackgroundWorker ansehen . Wenn Sie eine so große Schleife in Ihrem WinForm haben, wird diese blockiert und Ihre App sieht aus, als wäre sie hängen geblieben.

Sehen Sie sich an BackgroundWorker.ReportProgress(), wie Sie den Fortschritt an den UI-Thread zurückmelden können.

Beispielsweise:

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
quelle
5
Schönes Beispiel, aber es gibt einen kleinen Fehler in Ihrem Code. Sie müssen den backgroundWorker.WorkerReportsProgress auf true setzen. Überprüfen Sie meine Bearbeitung
Mana
1
@mana Die Annahme ist, dass das BackgroundWorkerüber den Designer hinzugefügt und dort konfiguriert wird. Aber ja, es muss so konfiguriert werden, dass es WorkerReportsProgresseingestellt ist true.
Peter Ritchie
Ah, mein Schlechtes, wusste nicht, dass man es im Designer einstellen kann
Mana
Wenn Sie sich etwas anderes fragen, zählt Ihr Beispiel nur bis 99 backgroundWorker.ReportProgress ((j * 100) / 100000); wie man 100% zählt
Mana
1
@mana Wenn Sie die 100% im Fortschritt anzeigen möchten, tun Sie es im RunWorkerCompletedEvent-Handler, wenn Ihr DoWorkHandler es nicht tut ..
Peter Ritchie
77

Seit .NET 4.5 können Sie eine Kombination aus Async verwenden und mit Progress auf das Senden von Updates an den UI-Thread warten :

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
}

Aufgaben sind derzeit die bevorzugte Methode, um zu implementieren, was BackgroundWorkerfunktioniert.

Aufgaben und Progresswerden hier näher erläutert:

Quasoft
quelle
2
Dies sollte die ausgewählte Antwort sein, IMO. Gute Antwort!
JohnOpincar
Fast die schönste Antwort, außer dass Task.Run anstelle einer normalen asynchronen Funktion verwendet wird
KansaiRobot
@KansaiRobot Du meinst im Gegensatz zu await DoWorkAsync(progress);? Dies ist sehr absichtlich, da dies nicht dazu führen würde, dass ein zusätzlicher Thread ausgeführt wird. Nur wenn DoWorkAsynces awaitzum Beispiel auf eine E / A-Operation warten würde, würde die button1_ClickFunktion fortgesetzt. Der Haupt-UI-Thread ist für diese Dauer blockiert. Wenn DoWorkAsynces nicht wirklich asynchron ist, sondern nur viele synchrone Anweisungen, erhalten Sie nichts.
Wolfzoon
System.Windows.Controls.ProgressBar enthält kein Feld "Step". Es sollte aus dem Beispiel entfernt werden. zumal es sowieso nicht benutzt wird.
Robert Tausig
2
@RobertTausig Es ist wahr, dass Stepes nur in der Fortschrittsanzeige von WinForms verfügbar ist und hier nicht benötigt wird, aber es war im Beispielcode der Frage (getaggte Winforms) vorhanden, also könnte es bleiben.
Quasoft
4

Hey, es gibt ein nützliches Tutorial zu Dot Net-Perlen: http://www.dotnetperls.com/progressbar

In Übereinstimmung mit Peter müssen Sie eine gewisse Menge an Threading verwenden, da das Programm sonst nur hängen bleibt und den Zweck etwas zunichte macht.

Beispiel mit ProgressBar und 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
quelle
1

Es Taskgibt, es ist Unnesscery mit BackgroundWorker, Taskist einfacher. beispielsweise:

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

Getan! Dann können Sie ProgressDialog überall wiederverwenden:

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
quelle
Bitte posten Sie keine identischen Antworten auf mehrere Fragen . Schreiben Sie eine gute Antwort und stimmen Sie ab, um die anderen Fragen als Duplikate zu schließen. Wenn die Frage kein Duplikat ist, passen Sie Ihre Antworten auf die Frage an .
Martijn Pieters