ProgressBar コントロール

Last Updated 2011/09/21


ProgressBar コントロールにはバグがあります。Microsoft はバグとは認めないかもしれませんが、バグ同然であることは確実です。

ProgressBar コントロールをテストした人は分かると思いますが、バーを進捗させるために Value プロパティを設定してもコントロールは反応しません。このページではこの問題を解決します。


プログレスバーコントロールは、ステータスバーコントロール内に配置することが多いので、それを前提として説明します。次のコードではステータスバーを 3 つの区画に分け、2 番目の区画にプログレスバーを配置するものです。プログレスバーはデザイン時は非表示にしておいて、必要になった時点で表示状態にします。

<StatusBar>
  <StatusBarItem Name="statusItem1" Content="" Width="140" DockPanel.Dock="Right" />
  <Separator DockPanel.Dock="Right" />
  <StatusBarItem Name="statusItem2" Width="140" DockPanel.Dock="Right">
    <ProgressBar Name="progressBar" Visibility="Hidden" Width="134" Height="16" />
  </StatusBarItem>
  <Separator DockPanel.Dock="Right" />
  <StatusBarItem Name="statusItem3" Content="" />
</StatusBar>

以下は、プログレスバーを起動する分離コードです。

private void button1_Click(object sender, RoutedEventArgs e)
{
  progressBar.Maximum = 20;
  progressBar.Minimum = 0;
  progressBar.Value = 0;
  progressBar.Visibility = Visibility.Visible; // 表示状態にする

  Mouse.SetCursor(Cursors.Wait);

  for (int i = 0; i < 20; ++i)
  {
    Thread.Sleep(200); // 実際に処理するコード
    progressBar.Value += 1; // プログレスバーの進捗状況を進める
  }

  progressBar.Visibility = Visibility.Hidden; // 非表示にする
  Mouse.SetCursor(Cursors.Arrow);
}

これでいいはずなので、アプリケーションを起動するとプログレスバーが表示されないことに気付くはずです。私はハッキリ言っておきます。これはバグです。Microsoft は認めないかもしれませんが。

つまり、Value プロパティの変化にともなってコントロールの状態が変化することを期待されるコントロールが期待通りの動作をしないのはどう考えてもおかしい。では、その解決策は。

System.Windows.Forms.Application クラスには DoEvents メソッドがあって、こういう場合のために用意されています。そこで、上記のコードに手を加えれば問題は解決です。

  for (int i = 0; i < 20; ++i)
  {
    Thread.Sleep(200); // 実際に処理するコード
    progressBar.Value += 1; // プログレスバーの進捗状況を進める
    System.Windows.Forms.Application.DoEvents(); // ← 追加
  }

WPF アプリケーションで System.Windows.Forms.Application クラスの DoEvent メソッドを使うことに抵抗があるかもしれませんが、これで問題が解決することは間違いありません。

カスタムコントロールを作る手もあります。以下は、OnValueChanged メソッドをオーバーライドしただけのものですが、これで十分目的を達成できます。

public class ProgressBarEx : ProgressBar
{
  public ProgressBarEx()
  {
  }

  protected override void OnValueChanged(double oldValue, double newValue)
  {
    base.OnValueChanged(oldValue, newValue);
    System.Windows.Forms.Application.DoEvents();
  }
}