Jak korzystać z paska postępu WinForms?

101

Chcę pokazać postęp obliczeń, które wykonuję w zewnętrznej bibliotece.

Na przykład, jeśli mam jakąś metodę obliczeniową i chcę jej użyć dla 100000 wartości w mojej klasie Form, mogę napisać:

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

Po każdym obliczeniu powinienem wykonać krok. Ale co jeśli wykonam wszystkie 100000 obliczeń metodą zewnętrzną. Kiedy powinienem „wykonać krok”, jeśli nie chcę uzależniać tej metody od paska postępu? Mogę na przykład pisać

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

ale nie chcę tego robić.

Dmytro
źródło
4
Przekaż obiekt delegata do metody.
Hans Passant

Odpowiedzi:

112

Proponuję przyjrzeć się BackgroundWorker . Jeśli masz tak dużą pętlę w swoim WinForm, zostanie ona zablokowana, a aplikacja będzie wyglądać tak, jakby się zawiesiła.

Zobacz, BackgroundWorker.ReportProgress()jak zgłosić postęp z powrotem do wątku interfejsu użytkownika.

Na przykład:

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
źródło
5
Niezły przykład, ale w Twoim kodzie jest mały błąd. Musisz ustawić backgroundWorker.WorkerReportsProgress na true. Sprawdź moją edycję
Mana
1
@mana Założenie jest takie, że BackgroundWorkerjest dodawany przez projektanta i tam konfigurowany. Ale tak, należy go skonfigurować, aby był WorkerReportsProgressustawiony na true.
Peter Ritchie
ach, moja wina, nie wiedziałem, że można to ustawić w projektancie
Mana
Zastanawiasz się jednak nad czymś innym, Twój przykład liczy się tylko do 99 backgroundWorker.ReportProgress ((j * 100) / 100000); jak uzyskać 100% liczenia
Mana
1
@mana Jeśli chcesz pokazać 100% w toku, zrób to w programie RunWorkerCompletedobsługi zdarzeń, jeśli twój DoWorkhandler tego nie robi ..
Peter Ritchie
77

Od .NET 4.5 możesz używać kombinacji async i czekać z Progress na wysyłanie aktualizacji do wątku interfejsu użytkownika:

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
}

Zadania są obecnie preferowanym sposobem realizacji tego, co BackgroundWorkerrobi.

Zadania i Progresssą wyjaśnione bardziej szczegółowo tutaj:

quasoft
źródło
2
To powinna być wybrana odpowiedź, IMO. Świetna odpowiedź!
JohnOpincar,
Prawie najmilsza odpowiedź, z tym wyjątkiem, że używa Task.Run zamiast normalnej funkcji asynchronicznej
KansaiRobot
@KansaiRobot Masz na myśli w przeciwieństwie do await DoWorkAsync(progress);? Jest to bardzo celowe, ponieważ nie spowodowałoby uruchomienia dodatkowego wątku. Funkcja byłaby kontynuowana tylko w przypadku DoWorkAsyncwywołania własnego, awaitnp. Oczekiwania na operację we / wy button1_Click. Główny wątek interfejsu użytkownika jest zablokowany na ten czas. Jeśli DoWorkAsyncnie jest tak naprawdę asynchroniczny, ale tylko wiele instrukcji synchronicznych, nic nie zyskujesz.
Wolfzoon
System.Windows.Control.ProgressBar nie zawiera pola „Step”. Powinien zostać usunięty z przykładu; zwłaszcza, że ​​i tak nie jest używany.
Robert Tausig
2
@RobertTausig To prawda, że Stepjest dostępny tylko na pasku postępu WinForms i nie jest tu potrzebny, ale był obecny w przykładowym kodzie pytania (otagowany winforms), więc może zostać.
quasoft
4

Hej, jest przydatny samouczek na temat pereł Dot Net: http://www.dotnetperls.com/progressbar

W porozumieniu z Peterem, musisz użyć pewnej ilości wątków, w przeciwnym razie program po prostu się zawiesi, co nieco zniweczy cel.

Przykład wykorzystujący ProgressBar i 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
źródło
1

Jest Task, jest niecelowy BackgroundWorker, Taskjest prostszy. na przykład:

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

Gotowe! Następnie możesz ponownie użyć ProgressDialog w dowolnym miejscu:

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
źródło
Proszę nie pisać identycznych odpowiedzi na wiele pytań . Opublikuj jedną dobrą odpowiedź, a następnie zagłosuj / oflaguj, aby zamknąć pozostałe pytania jako duplikaty. Jeśli pytanie nie jest powtórzeniem, dostosuj swoje odpowiedzi do pytania .
Martijn Pieters