วิธีใช้แถบความคืบหน้า WinForms


101

ฉันต้องการแสดงความคืบหน้าของการคำนวณซึ่งกำลังดำเนินการในไลบรารีภายนอก

ตัวอย่างเช่นถ้าฉันมีวิธีการคำนวณและฉันต้องการใช้มันสำหรับ 100000 ค่าในคลาสฟอร์มของฉันฉันสามารถเขียน:

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

ฉันควรทำตามขั้นตอนหลังจากการคำนวณแต่ละครั้ง แต่ถ้าฉันทำการคำนวณทั้งหมด 100000 รายการด้วยวิธีภายนอก ฉันควร "ดำเนินการตามขั้นตอน" เมื่อใดหากไม่ต้องการให้วิธีนี้ขึ้นอยู่กับแถบความคืบหน้า ตัวอย่างเช่นฉันสามารถเขียน

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

แต่ฉันไม่อยากทำแบบนั้น


4
ส่งวัตถุผู้รับมอบสิทธิ์ไปยังเมธอด
Hans Passant

คำตอบ:


112

ผมขอแนะนำให้คุณมีลักษณะที่BackgroundWorker หากคุณมีห่วงขนาดใหญ่ใน WinForm ของคุณมันจะบล็อกและแอพของคุณจะดูเหมือนว่ามันถูกแขวนคอ

ดูBackgroundWorker.ReportProgress()วิธีรายงานความคืบหน้ากลับไปที่เธรด UI

ตัวอย่างเช่น:

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
เป็นตัวอย่างที่ดี แต่มีข้อผิดพลาดเล็กน้อยในโค้ดของคุณ คุณต้องตั้งค่า backgroundWorker.WorkerReportsProgress เป็น true ตรวจสอบการแก้ไขของฉัน
มานะ

1
@mana สมมติฐานคือBackgroundWorkerเพิ่มผ่านผู้ออกแบบและกำหนดค่าไว้ที่นั่น แต่ใช่ว่ามันจะต้องมีการกำหนดค่าที่จะมีการกำหนดให้WorkerReportsProgress true
Peter Ritchie

อาฉันไม่ดีไม่รู้ว่าคุณสามารถตั้งค่าได้ในนักออกแบบ
มานะ

สงสัยอย่างอื่น แต่ตัวอย่างของคุณจะนับเป็น backgroundWorker ได้ 99 ตัวเท่านั้น ReportProgress ((j * 100) / 100000); วิธีนับ 100%
มานา

1
@mana ถ้าคุณต้องการแสดงความคืบหน้า 100% ให้ทำในRunWorkerCompletedตัวจัดการเหตุการณ์หากDoWorkตัวจัดการของคุณไม่ทำ ..
Peter Ritchie

77

ตั้งแต่. NET 4.5 คุณสามารถใช้การรวมกันของasyncและรอร่วมกับProgressเพื่อส่งอัพเดตไปยังเธรด 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
}

ขณะนี้งานเป็นวิธีที่ดีที่สุดในการนำสิ่งที่BackgroundWorkerทำไปใช้

งานและProgressมีการอธิบายรายละเอียดเพิ่มเติมที่นี่:


2
นี่ควรเป็นคำตอบที่เลือก IMO ตอบโจทย์มาก!
JohnOpincar

เกือบจะเป็นคำตอบที่ดีที่สุดยกเว้นว่าใช้ Task.Run แทนฟังก์ชัน async ปกติ
KansaiRobot

@KansaiRobot คุณหมายถึงตรงข้ามกับawait DoWorkAsync(progress);? นี่เป็นจุดประสงค์อย่างมากเนื่องจากจะไม่ส่งผลให้เธรดเพิ่มเติมทำงาน เฉพาะในกรณีที่DoWorkAsyncจะเรียกว่าเป็นของตัวเองawaitเช่นรอการดำเนินการ I / O button1_Clickฟังก์ชันจะดำเนินต่อไป เธรด UI หลักถูกบล็อกในช่วงเวลานี้ หากDoWorkAsyncไม่ใช่ async จริง ๆ แต่มีเพียงคำสั่งซิงโครนัสจำนวนมากคุณจะไม่ได้รับอะไรเลย
Wolfzoon

System.Windows.Controls.ProgressBar ไม่มีฟิลด์ "Step" ควรลบออกจากตัวอย่าง โดยเฉพาะอย่างยิ่งเนื่องจากยังไม่ได้ใช้
Robert Tausig

2
@RobertTausig เป็นความจริงที่Stepมีเฉพาะในแถบความคืบหน้าของ WinForms และไม่จำเป็นต้องใช้ที่นี่ แต่มีอยู่ในโค้ดตัวอย่างของคำถาม (winforms ที่ติดแท็ก) ดังนั้นอาจอยู่ได้
quasoft

4

เฮ้มีบทช่วยสอนที่เป็นประโยชน์เกี่ยวกับไข่มุก Dot Net: http://www.dotnetperls.com/progressbar

ตามข้อตกลงกับ Peter คุณต้องใช้เธรดจำนวนหนึ่งมิฉะนั้นโปรแกรมจะหยุดทำงานซึ่งค่อนข้างจะเอาชนะจุดประสงค์

ตัวอย่างที่ใช้ ProgressBar และ 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

มีTaskอยู่แล้วมันเป็น unnesscery ใช้BackgroundWorker, Taskเป็นเรื่องง่ายมากขึ้น ตัวอย่างเช่น:

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

เสร็จแล้ว! จากนั้นคุณสามารถใช้ ProgressDialog ซ้ำได้ทุกที่:

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

กรุณาทำไม่ได้คำตอบเหมือนกันโพสต์ไปหลายคำถาม โพสต์คำตอบที่ดีหนึ่งคำตอบจากนั้นโหวต / ตั้งค่าสถานะเพื่อปิดคำถามอื่นที่ซ้ำกัน หากคำถามไม่ซ้ำกันให้ปรับแต่งคำตอบของคุณให้เหมาะกับคำถามนั้น
Martijn Pieters
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.