Как использовать индикатор выполнения 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
Передайте объект делегата методу.
Ханс Пассан

Ответы:

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. Проверьте мою правку
Mana
1
@mana Предполагается, что BackgroundWorkerдобавляется через дизайнер и настраивается там. Но да, его нужно будет настроить, чтобы WorkerReportsProgressустановить значение true.
Питер Ричи
ах, бедняга, не знал, что можно в конструкторе установить
Mana
Интересно что-то еще, ваш пример учитывает только до 99 backgroundWorker.ReportProgress ((j * 100) / 100000); как получить 100% счет
Мана
1
@mana Если вы хотите показать 100% прогресса, сделайте это в RunWorkerCompletedобработчике событий, если ваш DoWorkобработчик этого не делает ..
Питер Ричи,
77

Начиная с .NET 4.5, вы можете использовать комбинацию async и await with 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самом деле это не асинхронный режим, а просто много синхронных операторов, вы ничего не получите.
Wolfzoon 01
System.Windows.Controls.ProgressBar не содержит поля «Шаг». Его следует убрать из примера; тем более, что он все равно не используется.
Роберт Таусиг
2
@RobertTausig Это правда, что Stepдоступно только в индикаторе выполнения WinForms и здесь не нужно, но оно присутствовало в примере кода вопроса (помеченные winforms), так что, возможно, это могло остаться.
quasoft
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();
TangMonk
источник
Пожалуйста , не публикуйте одинаковые ответы на несколько вопросов . Опубликуйте один хороший ответ, затем проголосуйте / отметьте, чтобы закрыть другие вопросы как дубликаты. Если вопрос не повторяется, адаптируйте свои ответы к вопросу .
Мартейн Питерс