Bagaimana cara menggunakan bilah kemajuan WinForms?

101

Saya ingin menunjukkan kemajuan penghitungan, yang dilakukan di perpustakaan eksternal.

Misalnya jika saya memiliki beberapa metode penghitungan, dan saya ingin menggunakannya untuk nilai 100000 di kelas Formulir saya, saya dapat menulis:

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

Saya harus melakukan langkah setelah setiap perhitungan. Tetapi bagaimana jika saya melakukan semua perhitungan 100000 dalam metode eksternal. Kapan saya harus "melakukan langkah" jika saya tidak ingin metode ini bergantung pada bilah kemajuan? Saya bisa, misalnya, menulis

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

tetapi saya tidak ingin melakukan seperti itu.

Dmytro
sumber
4
Meneruskan objek delegasi ke metode.
Hans Passant

Jawaban:

112

Saya menyarankan Anda untuk melihat BackgroundWorker . Jika Anda memiliki loop sebesar itu di WinForm Anda, itu akan memblokir dan aplikasi Anda akan terlihat seperti hang.

Lihat BackgroundWorker.ReportProgress()untuk melihat bagaimana melaporkan kemajuan kembali ke UI thread.

Sebagai contoh:

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
sumber
5
Contoh yang bagus, tetapi ada kesalahan kecil dalam kode Anda. Anda perlu menyetel backgroundWorker.WorkerReportsProgress ke true. Periksa suntingan saya
Mana
1
@mana Asumsinya adalah bahwa BackgroundWorkerditambahkan melalui desainer dan dikonfigurasi di sana. Tapi ya, itu perlu dikonfigurasi untuk WorkerReportsProgressdisetel ke true.
Peter Ritchie
ah, saya buruk, tidak tahu bahwa Anda bisa mengaturnya di desainer
Mana
Ingin tahu sesuatu yang lain, contoh Anda hanya dihitung hingga 99 backgroundWorker.ReportProgress ((j * 100) / 100000); cara mendapatkan hitungan 100%
Mana
1
@mana Jika Anda ingin menunjukkan 100% dalam proses, lakukan di RunWorkerCompletedevent handler, jika DoWorkhandler Anda tidak melakukannya ..
Peter Ritchie
77

Sejak .NET 4.5 Anda dapat menggunakan kombinasi asinkron dan menunggu dengan Kemajuan untuk mengirim pembaruan ke utas UI:

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
}

Tugas saat ini merupakan cara yang disukai untuk menerapkan apa yang BackgroundWorkerdilakukannya.

Tugas dan Progressdijelaskan lebih detail di sini:

quasoft
sumber
2
Ini harus menjadi jawaban yang dipilih, IMO. Jawaban yang bagus!
JohnOpincar
Hampir hampir merupakan jawaban terbaik, kecuali bahwa ia menggunakan Task.Run alih-alih fungsi async normal
KansaiRobot
@KansaiRot Maksud Anda sebagai lawan await DoWorkAsync(progress);? Ini sangat disengaja, karena itu tidak akan menghasilkan thread tambahan yang berjalan. Hanya jika DoWorkAsyncakan memanggilnya sendiri awaitmisalnya menunggu pada operasi I / O, apakah button1_Clickfungsi akan dilanjutkan. Rangkaian UI utama diblokir selama durasi ini. Jika DoWorkAsynctidak benar-benar asinkron tetapi hanya banyak pernyataan sinkron, Anda tidak mendapatkan apa pun.
Wolfzoon
System.Windows.Controls.ProgressBar tidak berisi bidang "Langkah". Ini harus dihapus dari contoh; terutama karena itu tidak digunakan.
Robert Tausig
2
@RobertTausig Memang benar bahwa Stephanya tersedia di bilah kemajuan WinForms dan tidak diperlukan di sini, tetapi itu ada dalam kode contoh pertanyaan (diberi tag winforms), jadi mungkin itu bisa tetap.
quasoft
4

Halo, ada tutorial berguna tentang mutiara Dot Net: http://www.dotnetperls.com/progressbar

Dalam kesepakatan dengan Peter, Anda perlu menggunakan sejumlah threading atau program akan hang, agak menggagalkan tujuannya.

Contoh yang menggunakan ProgressBar dan 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
sumber
1

Ada yang Taskada, Ini adalah penggunaan unnesscery BackgroundWorker, Tasklebih sederhana. sebagai contoh:

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

Selesai! Kemudian Anda dapat menggunakan kembali ProgressDialog di mana saja:

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
sumber
Harap jangan memposting jawaban yang identik untuk beberapa pertanyaan . Posting satu jawaban bagus, lalu pilih / tandai untuk menutup pertanyaan lain sebagai duplikat. Jika pertanyaannya bukan duplikat, sesuaikan jawaban Anda dengan pertanyaan tersebut .
Martijn Pieters