Як використовувати панель прогресу WinForms?


101

Я хочу показати хід обчислень, які виконуються у зовнішній бібліотеці.

Наприклад, якщо у мене є якийсь метод обчислення, і я хочу використовувати його для 100000 значень у своєму класі Form, я можу написати:

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
Передайте методу делегат.
Ганс Пасант

Відповіді:


112

Я б запропонував вам поглянути на BackgroundWorker . Якщо у вас у програмі WinForm є цикл, який буде великим, він заблокується, і ваш додаток буде схожий на те, що він повішений.

Подивіться, BackgroundWorker.ReportProgress()щоб побачити, як звітувати про прогрес назад у потоці інтерфейсу користувача.

Наприклад:

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.
Пітер Річі

ах, мій поганий, не знав, що ти можеш встановити це в дизайнері
Мана

Якщо вам цікаво щось інше, ваш приклад нараховує лише 99 backgroundWorker.ReportProgress ((j * 100) / 100000); як отримати 100% підрахунок
Мана

1
@mana Якщо ви хочете показати 100% успіху, зробіть це в обробці RunWorkerCompletedподій, якщо ваш DoWorkобробник цього не зробить ..
Пітер Річі

77

Оскільки .NET 4.5 ви можете використовувати комбінацію асинхронізації та очікувати з програмою Progress для надсилання оновлень у потік інтерфейсу:

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
Це має бути обрана відповідь, ІМО. Чудова відповідь!
JohnOpincar

Майже майже найприємніша відповідь, за винятком того, що він використовує Task.Run замість звичайної функції асинхронізації
KansaiRobot

@KansaiRobot Ви маєте на увазі, на відміну від await DoWorkAsync(progress);? Це дуже багато за призначенням, оскільки це не призведе до запуску додаткової нитки. Тільки якщо DoWorkAsyncб назвав свою власну awaitнаприклад , для очікування на операції введення / виводу, чи буде button1_Clickфункція продовження. Основний потік інтерфейсу блокується протягом цієї тривалості. Якщо DoWorkAsyncце насправді не асинхронність, а просто багато синхронних висловлювань, ви нічого не отримуєте.
Вольфзун

System.Windows.Controls.ProgressBar не містить поля "Крок". Це слід зняти з прикладу; тим більше, що він все одно не використовується.
Роберт Таусіг

2
@RobertTausig Це правда, що Stepвона доступна лише на панелі прогресу WinForms і тут не потрібна, але вона була присутня в прикладі коду запитання (з тегами winforms), тому, можливо, це може залишитися.
квасофт

4

Привіт, є корисний підручник з перлин Dot Net: http://www.dotnetperls.com/progressbar

За погодженням з Пітером, вам потрібно скористатися деякою кількістю ниток, або програма просто зависне, дещо переможаючи мету.

Приклад, який використовує 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існує, використання нечестивості 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.