WinFormsプログレスバーの使用方法


101

外部ライブラリで行っている計算の進捗状況を表示したい。

たとえば、いくつかのcalculateメソッドがあり、Formクラスの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 2012

回答:


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に設定する必要があります。編集内容をチェック
Mana

1
@mana想定は、BackgroundWorkerがデザイナーを介して追加され、そこで構成されていることです。しかし、はい、それはしているように設定する必要がありますWorkerReportsProgressに設定しますtrue
Peter Ritchie

ああ、私の悪い人は、デザイナーで設定できることを知りませんでした
Mana

とはいえ、疑問に思っているのは、99個のbackgroundWorker.ReportProgress((j * 100)/ 100000);に数えることだけです。100%カウントを取得する方法
Mana

1
@manaあなたがそれを行う、進行中の100%を表示したい場合はRunWorkerCompleted、あなたの場合は、イベントハンドラDoWorkハンドラはそれをしない...
ピーター・リッチー

77

.NET 4.5以降では、UIスレッドに更新を送信するためにasyncawaitProgress組み合わせて使用​​できます。

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 2017

通常の非同期関数の代わりにTask.Runを使用していることを除いて、ほぼほぼ最高の答え
KansaiRobot '19

@KansaiRobot反対の意味await DoWorkAsync(progress);ですか?余分なスレッドが実行されることはないため、これは非常に意図的です。たとえばI / O操作を待機するためにDoWorkAsync独自のものawaitを呼び出す場合にのみ、button1_Click関数は続行します。この間、メインUIスレッドはブロックされます。場合はDoWorkAsync、本当に非同期しかし、同期の文だけ多くありませんが、あなたは何を得ることはありません。
Wolfzoon 2018

System.Windows.Controls.ProgressBarには、「Step」フィールドが含まれていません。例から削除する必要があります。特にとにかく使われていないので。
Robert Tausig

2
@RobertTausig確かにStep、WinFormsの進行状況バーでのみ利用可能であり、ここでは必要ありませんが、質問のサンプルコード(タグ付きwinforms)に含まれているため、そのまま残る可能性があります。
quasoft

4

ドットネットパールに関する便利なチュートリアルがあります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で、存在するBackgroundWorkerTaskより簡単です。例えば:

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

複数の質問に同じ回答を投稿しないでください。良い答えを1つ投稿してから、投票またはフラグを立てて、他の質問を重複として閉じます。質問が重複していない場合は、質問に対する回答を調整してください
Martijn Pieters
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.