C#

プログレスバー(ProgressBar)の完了と同時にダイアログを表示する方法

実際の処理よりプログレスバーの動きが遅い

1.現象の確認

.NET Frameworkプログラム(VB.NET、C#)において、プログレスバー(System.Windows.Forms.ProgressBar)コンポーネントを使用し、経過ビジュアルでを表すことがよくある。
そして、処理が完了した時点で「完了メッセージダイアログ」を表示させたところ、プログレスバーが全て到達していないのに、先に「完了メッセージダイアログ」がでてしまうという現象がある。
下記のような現象である。

ずれている画面

 

これは実験的に作ってみた下記のような簡単なプログラムでも十分に確認することができる。

//***プログレスバーの動きと完了ダイアログが同期するか実験プログラム***
//
//   0~100までのカウントアップを行いその値でプログレスバーを更新
//   100になった時点で処理が終わるので官僚ダイアログを表示する
//   実験なので処理は特に行わずちょっとまつだけにした 
//********************************************************

//
// 画面ロード時
//
private void Form1_Load(object sender, EventArgs e)
{
    //プロパティーの最大・最小・開始の値をセット
    progressBar1.Minimum = 0;     
    progressBar1.Maximum = 100;
    progressBar1.Value = progressBar1.Minimum;
}
//
// ボタンをクリックした
//
private void btnStart_Click(object sender, EventArgs e)
{
    //最小値から10ずつカウントアップして最大値までバーの値更新を繰り返す
    for (int i = progressBar1.Minimum; i <= progressBar1.Maximum; i += 10)
    {
        System.Threading.Thread.Sleep(100);	// 今回はなにも処理がなく実験なのでちょっとまつだけ
        progressBar1.Value = i;                // カウント値をValueに代入して更新
    }
    //処理が完了しMAXになったのでメッセージダイアログを表示する
    MessageBox.Show("Progress end!!");
}

2.タイミングズレが起きる原因とその解決方法

Windows Vista以降では、アニメーション表示処理が実際のプログレスバーの画面描画の処理より遅れて実行されているため、処理が先に完了しても、描画がおいつかないことによってこのようなタイミングズレの現象が起きる。
解決対策としては、処理の間、ProgressBarのvalueプロパティーを実際の処理より1つUPした数値を代入し、直後に正しい値に代入しなおすだけである。
ただし、Max値を超えての代入はエラーとなるため、この場合は、強制的にMax値を増やし、同じ事を行う。
わかりにくいので下記をみていただければ…

private void Form1_Load(object sender, EventArgs e)
{

    progressBar1.Minimum = 0;
    progressBar1.Maximum = 100;
    progressBar1.Value = progressBar1.Minimum;
}

private void btnStart_Click(object sender, EventArgs e)
{
    for (int i = progressBar1.Minimum; i <= progressBar1.Maximum; i += 10)
    {
        System.Threading.Thread.Sleep(100); // ちょっと待つ
        //progressBar1.Value = i;
        if (i < progressBar1.Maximum)
        {
            // 指定する値がMaximumより小さい場合
            progressBar1.Value = i + 1;
            progressBar1.Value = i;
            System.Threading.Thread.Sleep(100);
        }
        else
        {
            // ValueがMaximumになった場合にはMaximum値も1つアップさせてvalueを更新する
            // これで一旦オーバーして戻る
            progressBar1.Maximum++;
            progressBar1.Value = i + 1;
            progressBar1.Value = i;
            progressBar1.Maximum--;
        }
    }
    MessageBox.Show("Progress end!!");
}

こうして完成した結果がこれ

画面イメージ2-b

※因みにこんな実験をしてみたが全て結果はNGであった。

①プログラムが使用するCPUを限定してみた
使用しているPCには、CPUのコアが4つあるため、処理速度を遅くする。
→.NETでプログラムが使用するCPUの数を1つに限定する

②Windows任せの描画ではなく自分のプログラムから強制的に画面更新する
→Valueプロパティの更新直後に毎回 PostMessageで描画更新を呼びす

③Valueプロパティ更新後にわざと処理の遅延をかける。
System.Threading.Thread.Sleepで遅延をかけた

タイトルとURLをコピーしました