C#プログレスバーの使い方|WinFormsで進捗表示・更新できない原因までサンプルコードで解説

はじめに

C#で時間のかかる処理を実装するとき、ユーザーに「処理中です」「あとどれくらいで終わります」と伝えるために役立つのがプログレスバーです。特にWinFormsアプリでは、ProgressBarコントロールを使うことで、ファイル処理、データ読み込み、CSV出力、画像変換、API連携などの進捗を視覚的に表示できます。

ただし、C#のプログレスバーは単にValueを変更すれば必ず画面に反映されるわけではありません。重い処理をUIスレッドで実行していると画面が固まり、「プログレスバーが更新されない」「100%にならない」「別スレッドから操作してエラーになる」といった問題が起こります。

この記事では、C#のWinFormsにおけるプログレスバーの基本的な使い方から、更新されない原因、async/awaitBackgroundWorkerを使った正しい更新方法、進捗率が分からない処理で使うMarquee表示まで、サンプルコード付きで解説します。

1. C#のプログレスバーとは?WinFormsで進捗表示に使う基本

C#のWinFormsで使うプログレスバーは、System.Windows.Forms.ProgressBarクラスとして用意されているコントロールです。フォーム上に横長のバーを表示し、処理の進み具合を視覚的に表現できます。

たとえば、100件のデータを処理する場合、1件処理するごとにプログレスバーを1%ずつ進めれば、ユーザーは処理状況を把握しやすくなります。

1-1. ProgressBarコントロールでできること

ProgressBarコントロールでは、主に次のようなことができます。

処理の進捗を0〜100%のように表示する

処理件数に応じてバーを少しずつ進める

処理中だけプログレスバーを表示する

進捗率が不明な処理ではアニメーション表示にする

ラベルと組み合わせて「50%完了」のように表示する

キャンセルボタンと組み合わせて処理を中断できる画面を作る

WinFormsでは、プログレスバー自体に文字を表示する機能は標準ではありません。そのため、進捗率を文字で見せたい場合は、Labelを併用するか、カスタム描画を使います。

1-2. 進捗率が分かる処理と分からない処理の違い

プログレスバーには、大きく分けて2種類の使い方があります。

1つ目は、処理全体の件数や量が分かっている場合です。たとえば、100件のデータを処理する、50個のファイルをコピーする、10ステップの処理を順番に実行する、といったケースです。この場合は、MinimumMaximumValueを使って具体的な進捗を表示できます。

2つ目は、処理がいつ終わるか分からない場合です。たとえば、外部APIの応答待ち、データベース接続待ち、ネットワーク処理、件数が事前に分からない検索処理などです。この場合は、正確な進捗率を出せないため、Marqueeスタイルを使って「処理中であること」だけを伝える表示にします。

1-3. WinForms・WPF・コンソールアプリでの使い方の違い

C#でプログレスバーを使う方法は、アプリの種類によって異なります。

WinFormsでは、System.Windows.Forms.ProgressBarをフォーム上に配置して使います。この記事で中心に扱うのはこの方法です。

WPFでは、System.Windows.Controls.ProgressBarをXAML上に配置して使います。考え方は似ていますが、プロパティ名やUI更新の仕組みが少し異なります。

コンソールアプリには標準のGUIプログレスバーはありません。そのため、Console.Writeで文字ベースの進捗表示を作るか、外部ライブラリを使って表現します。

この記事では、C#のWinFormsでプログレスバーを使う方法に絞って解説します。

1-4. この記事で作るサンプルの完成イメージ

この記事では、次のようなWinFormsアプリを想定します。

フォーム上にプログレスバーを配置する

ボタンを押すと処理が開始される

処理中にプログレスバーが0%から100%まで進む

ラベルに「〇%完了」と表示する

処理完了後にメッセージを表示する

重い処理でも画面が固まらないようにする

最終的には、async/awaitIProgress<int>を使って、UIを固めず安全にプログレスバーを更新するコードを作ります。

2. WinFormsでProgressBarを配置する方法

WinFormsでプログレスバーを使うには、Visual Studioのデザイナーから配置する方法と、C#コードで動的に追加する方法があります。

初心者の場合は、まずデザイナーで配置する方法がおすすめです。位置やサイズを画面で確認しながら設定できるため、分かりやすく実装できます。

2-1. Visual Studioのデザイナーで配置する手順

Visual StudioでWinFormsプロジェクトを開き、フォームデザイナーを表示します。

ツールボックスからProgressBarを探し、フォーム上にドラッグ&ドロップします。配置後、プロパティウィンドウでNameMinimumMaximumValueなどを設定します。

たとえば、次のように設定します。

C#
Name: progressBar1
Minimum: 0
Maximum: 100
Value: 0
Style: Blocks

この状態で実行すると、フォーム上に空のプログレスバーが表示されます。処理に応じてprogressBar1.Valueを変更することで、バーを進められます。

2-2. コードからProgressBarを追加する方法

デザイナーを使わず、C#コードからProgressBarを作成してフォームに追加することもできます。

C#
using System;
using System.Drawing;
using System.Windows.Forms;

public partial class Form1 : Form
{
private ProgressBar progressBar1;

public Form1()
{
InitializeComponent();

progressBar1 = new ProgressBar();
progressBar1.Name = "progressBar1";
progressBar1.Location = new Point(20, 20);
progressBar1.Size = new Size(300, 25);
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;

this.Controls.Add(progressBar1);
}
}

コードから追加する方法は、画面部品を動的に生成したい場合や、複数のプログレスバーを条件に応じて表示したい場合に便利です。

2-3. Name・Location・Sizeなど最低限設定するプロパティ

プログレスバーを使うときに最低限理解しておきたいプロパティは次のとおりです。

Nameは、コードからプログレスバーを操作するときの名前です。一般的にはprogressBar1progressBarDownloadのように設定します。

Locationは、フォーム上の表示位置です。

Sizeは、プログレスバーの幅と高さです。

Minimumは、進捗の最小値です。通常は0にします。

Maximumは、進捗の最大値です。パーセント表示なら100、処理件数に合わせるなら件数を設定します。

Valueは、現在の進捗値です。Minimum以上、Maximum以下の値にする必要があります。

Styleは、バーの表示形式です。通常の進捗表示ではBlocksまたはContinuous、進捗不明の処理ではMarqueeを使います。

2-4. ProgressBarをフォームに表示・非表示する方法

処理中だけプログレスバーを表示したい場合は、Visibleプロパティを使います。

C#
progressBar1.Visible = true;   // 表示する
progressBar1.Visible = false; // 非表示にする

たとえば、処理開始時に表示して、処理完了後に非表示にする場合は次のように書きます。

C#
private async void button1_Click(object sender, EventArgs e)
{
progressBar1.Visible = true;
progressBar1.Value = 0;

await Task.Delay(1000);

progressBar1.Value = 100;
progressBar1.Visible = false;
}

ただし、完了直後にすぐ非表示にすると、ユーザーが100%になったことを確認できない場合があります。必要に応じて、完了メッセージを表示したり、少し待ってから非表示にしたりすると親切です。

3. C#プログレスバーの基本プロパティと使い方

C#のプログレスバーを正しく使うには、MinimumMaximumValueの関係を理解することが重要です。ここを間違えると、バーが進まない、エラーになる、100%まで表示されないといった問題が起きます。

3-1. Minimum・Maximum・Valueの意味

Minimumはプログレスバーの最小値、Maximumは最大値、Valueは現在値です。

C#
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;

この設定では、Valueが0なら0%、50なら50%、100なら100%のように扱えます。

処理件数に合わせる場合は、Maximumを件数に設定することもできます。

C#
int totalCount = 50;

progressBar1.Minimum = 0;
progressBar1.Maximum = totalCount;
progressBar1.Value = 0;

この場合、1件処理するごとにValueを1増やせば、50件完了した時点でプログレスバーが満タンになります。

3-2. StepとIncrementで進捗を増やす方法

ProgressBarには、進捗を一定量ずつ増やすためのStepプロパティとIncrementメソッドがあります。

C#
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
progressBar1.Step = 10;

progressBar1.PerformStep();

PerformStep()を呼ぶと、Stepに設定した値だけValueが増えます。上の例では、10ずつ進みます。

任意の値だけ増やしたい場合は、Incrementを使います。

C#
progressBar1.Increment(5);

このコードでは、現在のValueから5だけ進みます。処理ごとに一定量進めたい場合はStep、状況に応じて増加量を変えたい場合はIncrementが便利です。

3-3. StyleプロパティのBlocks・Continuous・Marqueeの違い

ProgressBar.Styleには、主に次の3種類があります。

C#
progressBar1.Style = ProgressBarStyle.Blocks;

Blocksは、ブロック状に区切られた見た目で進捗を表示します。

C#
progressBar1.Style = ProgressBarStyle.Continuous;

Continuousは、連続したバーとして進捗を表示します。ただし、OSやテーマによって見た目が大きく変わらない場合もあります。

C#
progressBar1.Style = ProgressBarStyle.Marquee;

Marqueeは、進捗率が分からない処理で使います。バーが左右に動くアニメーションになり、「処理中」であることを示します。Marqueeでは具体的なValueの進み具合は表示しません。

3-4. 進捗率をパーセントで計算する方法

処理件数から進捗率を計算する場合は、現在の処理件数を全体件数で割って100を掛けます。

C#
int current = 25;
int total = 100;

int percent = current * 100 / total;
progressBar1.Value = percent;

ただし、totalが0の場合はゼロ除算になるため、必ずチェックします。

C#
int percent = 0;

if (total > 0)
{
percent = current * 100 / total;
}

処理件数が多い場合や小数点を考慮したい場合は、doubleを使ってからintに変換します。

C#
int percent = (int)((double)current / total * 100);
progressBar1.Value = percent;

3-5. Valueが範囲外になったときのエラー対策

ProgressBar.Valueには、Minimum以上、Maximum以下の値しか設定できません。範囲外の値を設定すると例外が発生します。

たとえば、Maximumが100なのにValueへ120を設定するとエラーになります。

C#
progressBar1.Maximum = 100;
progressBar1.Value = 120; // エラー

安全に設定するには、値を範囲内に収めます。

C#
int value = 120;

value = Math.Max(progressBar1.Minimum, value);
value = Math.Min(progressBar1.Maximum, value);

progressBar1.Value = value;

メソッド化しておくと便利です。

C#
private void SetProgressValue(int value)
{
if (value < progressBar1.Minimum)
{
value = progressBar1.Minimum;
}

if (value > progressBar1.Maximum)
{
value = progressBar1.Maximum;
}

progressBar1.Value = value;
}

4. C#プログレスバーを更新する基本サンプルコード

ここからは、C#のWinFormsでプログレスバーを実際に更新するサンプルコードを紹介します。

最初は分かりやすさを優先して基本的なコードを示し、その後で画面が固まらない実装に進みます。

4-1. ボタンクリックでProgressBarを0から100まで進める例

まずは、ボタンをクリックしたらプログレスバーを0から100まで進めるシンプルな例です。

C#
private async void button1_Click(object sender, EventArgs e)
{
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;

for (int i = 0; i <= 100; i++)
{
progressBar1.Value = i;
await Task.Delay(30);
}

MessageBox.Show("完了しました。");
}

await Task.Delay(30)を使うことで、UIスレッドを完全に止めずに待機できます。Thread.Sleepを使うと画面更新が止まりやすいため、WinFormsのUI処理では基本的にTask.Delayを使うのがおすすめです。

4-2. for文・foreach文の処理件数に合わせて更新する例

実際の処理では、件数に応じてプログレスバーを更新することが多いです。

C#
private async void button1_Click(object sender, EventArgs e)
{
string[] files =
{
"file1.txt",
"file2.txt",
"file3.txt",
"file4.txt",
"file5.txt"
};

progressBar1.Minimum = 0;
progressBar1.Maximum = files.Length;
progressBar1.Value = 0;

foreach (string file in files)
{
// ファイル処理の代わり
await Task.Delay(500);

progressBar1.Value++;
}

MessageBox.Show("すべてのファイル処理が完了しました。");
}

この例では、5個のファイルを1つ処理するたびにValueを1増やしています。Maximumを処理件数にしておけば、パーセント計算をしなくても自然に進捗を表現できます。

4-3. Labelに「〇%完了」を表示する例

プログレスバーだけでなく、ラベルに進捗率を表示すると、ユーザーにとってより分かりやすくなります。

C#
private async void button1_Click(object sender, EventArgs e)
{
int total = 100;

progressBar1.Minimum = 0;
progressBar1.Maximum = total;
progressBar1.Value = 0;

for (int i = 0; i <= total; i++)
{
progressBar1.Value = i;

int percent = i * 100 / total;
label1.Text = $"{percent}% 完了";

await Task.Delay(30);
}

label1.Text = "処理が完了しました。";
}

処理件数をそのままMaximumに使う場合でも、ラベルにはパーセントを計算して表示できます。

4-4. 処理完了後にメッセージを表示する例

プログレスバーが100%になったあとにメッセージを表示する場合は、処理完了後にMessageBox.Showを呼びます。

C#
private async void button1_Click(object sender, EventArgs e)
{
progressBar1.Value = 0;
label1.Text = "処理を開始します。";

for (int i = 0; i <= 100; i++)
{
progressBar1.Value = i;
label1.Text = $"{i}% 完了";
await Task.Delay(20);
}

label1.Text = "完了しました。";
MessageBox.Show("処理が完了しました。", "完了", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

ユーザーに処理完了を明確に伝えたい場合は、プログレスバー、ラベル、メッセージボックスを組み合わせると効果的です。

4-5. ファイル処理・データ処理を想定した実用サンプル

次は、データ一覧を順番に処理する実用的なサンプルです。

C#
private async void button1_Click(object sender, EventArgs e)
{
List<string> items = new List<string>
{
"データ1",
"データ2",
"データ3",
"データ4",
"データ5"
};

progressBar1.Minimum = 0;
progressBar1.Maximum = items.Count;
progressBar1.Value = 0;

label1.Text = "処理中...";

for (int i = 0; i < items.Count; i++)
{
string item = items[i];

// 実際にはここでファイル処理やデータ変換などを行う
await Task.Delay(500);

progressBar1.Value = i + 1;

int percent = (int)((double)(i + 1) / items.Count * 100);
label1.Text = $"{percent}% 完了";
}

label1.Text = "すべての処理が完了しました。";
MessageBox.Show("完了しました。");
}

このように、Maximumを全体件数、Valueを完了件数にすると、処理件数に連動したプログレスバーを簡単に作れます。

5. プログレスバーが更新されない原因と解決方法

C#のWinFormsでよくある悩みが、「Valueを変更しているのにプログレスバーが更新されない」という問題です。

多くの場合、原因はプログレスバーではなく、UIスレッドの使い方にあります。

5-1. UIスレッドを長時間処理で止めている

WinFormsの画面描画やボタンクリックなどのUI処理は、基本的にUIスレッドで動いています。

そのUIスレッド上で重い処理を長時間実行すると、画面の再描画ができなくなります。その結果、コード上ではprogressBar1.Valueを変更していても、画面上ではプログレスバーが動いていないように見えます。

悪い例は次のとおりです。

C#
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 100; i++)
{
progressBar1.Value = i;

// 重い処理をUIスレッドで実行している
HeavyProcess();
}

MessageBox.Show("完了しました。");
}

このような場合は、重い処理をTask.Runで別スレッドに分けるか、非同期処理として実装する必要があります。

5-2. Thread.SleepをUIスレッドで使っている

Thread.SleepをUIスレッドで使うと、その間フォーム全体が停止します。ボタンも押せず、プログレスバーも再描画されません。

C#
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 100; i++)
{
progressBar1.Value = i;
Thread.Sleep(50); // UIが止まりやすい
}
}

WinFormsのUIイベント内で待機したい場合は、Thread.Sleepではなくawait Task.Delayを使います。

C#
private async void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 100; i++)
{
progressBar1.Value = i;
await Task.Delay(50);
}
}

await Task.Delayなら、待機中もUIスレッドが解放されるため、画面が更新されやすくなります。

5-3. Valueを変更しても画面が再描画されていない

Valueを変更しても、直後に重い処理を続けると画面が再描画されるタイミングがありません。

C#
progressBar1.Value = 50;
HeavyProcess();

この場合、Valueは変更されていますが、HeavyProcessが終わるまで画面が更新されないことがあります。

根本的な解決策は、重い処理をUIスレッドから切り離すことです。どうしても一時的に画面を更新したい場合にApplication.DoEvents()を使う方法もありますが、乱用はおすすめできません。

5-4. 100%になる前にフォームやProgressBarを非表示にしている

プログレスバーのValueを100にした直後、すぐにフォームを閉じたり、プログレスバーを非表示にしたりすると、ユーザーには100%になった瞬間が見えないことがあります。

C#
progressBar1.Value = 100;
progressBar1.Visible = false;

完了状態を見せたい場合は、ラベルで「完了しました」と表示したり、メッセージボックスを出したりします。

C#
progressBar1.Value = 100;
label1.Text = "完了しました。";
MessageBox.Show("処理が完了しました。");

必要であれば、少し待ってから非表示にします。

C#
progressBar1.Value = 100;
label1.Text = "完了しました。";

await Task.Delay(500);

progressBar1.Visible = false;

5-5. MaximumとValueの設定ミスで進捗が反映されない

Maximumの設定が処理件数と合っていないと、進捗が意図したように表示されません。

たとえば、100件処理するのにMaximumが1000になっていると、100件完了してもプログレスバーは10%程度しか進みません。

C#
progressBar1.Maximum = 1000;

for (int i = 0; i < 100; i++)
{
progressBar1.Value = i + 1;
}

処理件数に合わせる場合は、Maximumを件数に合わせます。

C#
progressBar1.Maximum = items.Count;
progressBar1.Value = 0;

パーセントで管理する場合は、Maximumを100にして、Valueには0〜100の値を設定します。

5-6. Application.DoEventsに頼りすぎる場合の注意点

Application.DoEvents()を使うと、現在たまっているWindowsメッセージを処理し、画面更新を促すことができます。

C#
for (int i = 0; i <= 100; i++)
{
progressBar1.Value = i;
Application.DoEvents();

HeavyProcess();
}

一見するとプログレスバーが更新されるため便利に見えますが、Application.DoEvents()に頼りすぎると、処理中に別のイベントが割り込んで予期しない動作をする可能性があります。

現在のC#開発では、Application.DoEvents()で無理に画面を更新するよりも、async/awaitTask.RunIProgress<T>を使ってUIを固めない設計にする方が安全です。

6. async/awaitでプログレスバーを正しく更新する方法

WinFormsでプログレスバーを安全に更新するなら、現在はasync/awaitを使う方法が扱いやすいです。

重い処理をバックグラウンドで実行し、進捗だけをUIスレッドに通知することで、画面を固めずにプログレスバーを更新できます。

6-1. async/awaitを使うべき理由

async/awaitを使うと、時間のかかる処理を実行している間もUIの応答性を保ちやすくなります。

たとえば、ボタンを押したあともフォームを移動できる、キャンセルボタンを押せる、ラベルやプログレスバーが更新される、といった自然な動作を実現できます。

WinFormsでプログレスバーが更新されない問題の多くは、UIスレッドをブロックしていることが原因です。async/awaitを使えば、この問題を避けやすくなります。

6-2. Task.Runで重い処理を別スレッドに分ける

CPUを使う重い処理や時間のかかる同期処理は、Task.Runで別スレッドに分けることができます。

C#
await Task.Run(() =>
{
// 重い処理
});

ただし、Task.Runの中から直接progressBar1.Valueを変更してはいけません。WinFormsのコントロールは、基本的に作成されたUIスレッドから操作する必要があります。

悪い例は次のとおりです。

C#
await Task.Run(() =>
{
progressBar1.Value = 50; // 別スレッドからUIを操作しているため危険
});

この問題を解決するには、IProgress<int>Progress<int>を使います。

6-3. IProgress<int>とProgress<int>でUIを安全に更新する

IProgress<int>は、バックグラウンド処理から進捗を通知するための仕組みです。WinFormsのUIスレッド上でProgress<int>を作成すれば、通知された値を安全にUIへ反映できます。

C#
IProgress<int> progress = new Progress<int>(value =>
{
progressBar1.Value = value;
label1.Text = $"{value}% 完了";
});

バックグラウンド処理側では、次のようにReportを呼びます。

C#
progress.Report(50);

これにより、重い処理は別スレッドで実行しながら、プログレスバーの更新はUIスレッドで安全に行えます。

6-4. async/await版のProgressBarサンプルコード

次は、async/awaitTask.RunIProgress<int>を使った実用的なサンプルです。

C#
private async void buttonStart_Click(object sender, EventArgs e)
{
buttonStart.Enabled = false;

progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
label1.Text = "処理を開始します。";

IProgress<int> progress = new Progress<int>(value =>
{
progressBar1.Value = value;
label1.Text = $"{value}% 完了";
});

try
{
await Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
// 重い処理の代わり
Thread.Sleep(50);

progress.Report(i);
}
});

label1.Text = "処理が完了しました。";
MessageBox.Show("完了しました。");
}
catch (Exception ex)
{
MessageBox.Show($"エラーが発生しました: {ex.Message}");
}
finally
{
buttonStart.Enabled = true;
}
}

このコードでは、Task.Runの中で重い処理を実行し、progress.Report(i)で進捗を通知しています。プログレスバーやラベルの更新はProgress<int>側で行うため、別スレッドから直接UIを操作する問題を避けられます。

6-5. ボタンの二重クリックを防ぐ実装例

処理中に開始ボタンを何度も押されると、同じ処理が重複して実行され、プログレスバーの表示もおかしくなることがあります。

そのため、処理開始時にボタンを無効化し、完了後に有効化します。

C#
private async void buttonStart_Click(object sender, EventArgs e)
{
buttonStart.Enabled = false;

try
{
progressBar1.Value = 0;
label1.Text = "処理中...";

await Task.Run(() =>
{
Thread.Sleep(3000);
});

progressBar1.Value = 100;
label1.Text = "完了しました。";
}
finally
{
buttonStart.Enabled = true;
}
}

finallyにボタンを戻す処理を書いておくと、途中で例外が発生してもボタンが無効のままになりにくくなります。

7. BackgroundWorkerでプログレスバーを更新する方法

BackgroundWorkerは、WinFormsで昔から使われているバックグラウンド処理用のコンポーネントです。現在はasync/awaitを使うことが多くなっていますが、既存プロジェクトではBackgroundWorkerが使われていることもあります。

7-1. BackgroundWorkerとは

BackgroundWorkerは、時間のかかる処理を別スレッドで実行しながら、進捗通知や完了通知をUI側へ返せるコンポーネントです。

主に次のイベントを使います。

DoWorkでバックグラウンド処理を実行する

ProgressChangedで進捗を受け取る

RunWorkerCompletedで完了時の処理を行う

プログレスバーを更新する場合は、ReportProgressで進捗率を通知し、ProgressChangedProgressBar.Valueを変更します。

7-2. WorkerReportsProgressを有効にする

BackgroundWorkerで進捗を通知するには、WorkerReportsProgresstrueにする必要があります。

C#
backgroundWorker1.WorkerReportsProgress = true;

Visual StudioのデザイナーでBackgroundWorkerを配置している場合は、プロパティウィンドウから設定することもできます。

7-3. ReportProgressで進捗を通知する

バックグラウンド処理中に進捗を通知するには、ReportProgressを使います。

C#
backgroundWorker1.ReportProgress(50);

この値は、ProgressChangedイベントのe.ProgressPercentageで受け取れます。

7-4. ProgressChangedでProgressBar.Valueを更新する

ProgressChangedイベントはUIスレッド側で実行されるため、ここでプログレスバーを更新できます。

C#
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text = $"{e.ProgressPercentage}% 完了";
}

DoWorkの中から直接progressBar1.Valueを変更するのではなく、必ずReportProgressを経由するのがポイントです。

7-5. BackgroundWorker版のサンプルコード

以下は、BackgroundWorkerでプログレスバーを更新するサンプルです。

C#
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;

backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}

private void buttonStart_Click(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
{
progressBar1.Value = 0;
label1.Text = "処理中...";
buttonStart.Enabled = false;

backgroundWorker1.RunWorkerAsync();
}
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
// 重い処理の代わり
Thread.Sleep(50);

backgroundWorker1.ReportProgress(i);
}
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text = $"{e.ProgressPercentage}% 完了";
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
buttonStart.Enabled = true;
label1.Text = "処理が完了しました。";

MessageBox.Show("完了しました。");
}

このコードでは、重い処理はDoWorkで実行し、プログレスバーの更新はProgressChangedで行っています。

7-6. async/awaitとBackgroundWorkerの使い分け

新しくWinFormsアプリを作る場合は、基本的にasync/awaitを使う方法がおすすめです。コードが読みやすく、非同期処理との相性もよいためです。

一方、既存のWinFormsアプリで既にBackgroundWorkerが使われている場合は、無理にすべて置き換える必要はありません。BackgroundWorkerでも、ReportProgressProgressChangedを正しく使えば、プログレスバーを安全に更新できます。

新規開発ならasync/await、既存コードの保守ならBackgroundWorkerも選択肢、と考えると分かりやすいです。

8. 進捗が分からない処理ではMarqueeを使う

すべての処理で正確な進捗率を出せるわけではありません。処理件数や完了予定が分からない場合は、Marqueeスタイルを使います。

Marqueeは、バーが左右に流れるように表示されるスタイルで、「何%完了」ではなく「現在処理中」であることを表します。

8-1. Marqueeスタイルが向いているケース

Marqueeスタイルは、次のようなケースに向いています。

外部APIの応答待ち

データベース接続待ち

ファイル検索中

ネットワーク通信中

処理件数が事前に分からないデータ読み込み

ログイン処理や認証処理

これらの処理では、無理に0〜100%の進捗を作るより、Marqueeで処理中であることを伝える方が自然です。

8-2. ProgressBar.StyleをMarqueeに設定する方法

Marquee表示にするには、Styleプロパティを設定します。

C#
progressBar1.Style = ProgressBarStyle.Marquee;

通常の進捗表示に戻す場合は、BlocksまたはContinuousに戻します。

C#
progressBar1.Style = ProgressBarStyle.Blocks;

Marqueeでは具体的な進捗値を表示しないため、Valueを増やして進める用途には向いていません。

8-3. MarqueeAnimationSpeedでアニメーション速度を調整する

Marqueeのアニメーション速度は、MarqueeAnimationSpeedで調整できます。

C#
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.MarqueeAnimationSpeed = 30;

値を大きくするとアニメーションが遅くなり、小さくすると速くなります。0にするとアニメーションが停止します。

処理中は適度な値にし、完了後は0にして止めるとよいでしょう。

C#
progressBar1.MarqueeAnimationSpeed = 30; // 動かす
progressBar1.MarqueeAnimationSpeed = 0; // 止める

8-4. 処理開始時に表示して完了時に停止するサンプル

進捗不明の処理でMarqueeを使うサンプルです。

C#
private async void buttonStart_Click(object sender, EventArgs e)
{
buttonStart.Enabled = false;

progressBar1.Visible = true;
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.MarqueeAnimationSpeed = 30;
label1.Text = "処理中です...";

try
{
await Task.Run(() =>
{
// 進捗率が分からない重い処理
Thread.Sleep(5000);
});

label1.Text = "処理が完了しました。";
MessageBox.Show("完了しました。");
}
finally
{
progressBar1.MarqueeAnimationSpeed = 0;
progressBar1.Style = ProgressBarStyle.Blocks;
progressBar1.Value = 0;
buttonStart.Enabled = true;
}
}

進捗率が分からない場合でも、プログレスバーが動いていれば、ユーザーはアプリが停止していないことを理解できます。

8-5. 通常の進捗表示とMarquee表示の切り替え方

処理によって、進捗率が分かる場合と分からない場合を切り替えたいことがあります。

C#
private void SetProgressMode(bool isUnknownProgress)
{
if (isUnknownProgress)
{
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.MarqueeAnimationSpeed = 30;
label1.Text = "処理中です...";
}
else
{
progressBar1.Style = ProgressBarStyle.Blocks;
progressBar1.MarqueeAnimationSpeed = 0;
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
label1.Text = "0% 完了";
}
}

進捗が分かる処理では通常のBlocks、分からない処理ではMarqueeを使うと、ユーザーにとって分かりやすい画面になります。

9. C#プログレスバーを見やすくする実装テクニック

プログレスバーは、ただ表示するだけでなく、ラベルやステータスバー、キャンセルボタンと組み合わせることで、より使いやすい画面になります。

9-1. Labelで進捗率や処理中メッセージを表示する

プログレスバーの近くにLabelを配置し、進捗率や現在の処理内容を表示すると親切です。

C#
label1.Text = "ファイルを読み込み中...";
label1.Text = "50% 完了";
label1.Text = "処理が完了しました。";

処理対象の名前を表示することもできます。

C#
label1.Text = $"{fileName} を処理中... {percent}% 完了";

プログレスバーだけでは何をしているのか分かりにくい場合があるため、ラベルとの併用は実用アプリでよく使われます。

9-2. StatusStripにToolStripProgressBarを表示する

フォーム下部のステータスバーに進捗を表示したい場合は、StatusStripToolStripProgressBarを使います。

C#
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = 100;
toolStripProgressBar1.Value = 50;

toolStripStatusLabel1.Text = "50% 完了";

画面のメイン領域を邪魔せずに進捗を表示したい場合に便利です。

たとえば、ファイルの読み込みや保存、検索処理などをステータスバーでさりげなく表示できます。

9-3. ProgressBarに文字を重ねて表示する方法

WinFormsの標準ProgressBarには、バーの上に文字を表示する機能はありません。

簡単に実現したい場合は、プログレスバーの近くにLabelを置く方法がおすすめです。どうしてもプログレスバー上に文字を重ねたい場合は、カスタムコントロールを作成して描画する方法があります。

簡易的な例は次のとおりです。

C#
public class TextProgressBar : ProgressBar
{
public string DisplayText { get; set; } = "";

public TextProgressBar()
{
this.SetStyle(ControlStyles.UserPaint, true);
}

protected override void OnPaint(PaintEventArgs e)
{
Rectangle rect = this.ClientRectangle;
Graphics g = e.Graphics;

double percent = (double)(Value - Minimum) / (Maximum - Minimum);
int width = (int)(rect.Width * percent);

Rectangle progressRect = new Rectangle(rect.X, rect.Y, width, rect.Height);

using (Brush brush = new SolidBrush(SystemColors.Highlight))
{
g.FillRectangle(brush, progressRect);
}

using (Brush brush = new SolidBrush(SystemColors.Control))
{
g.FillRectangle(brush, width, rect.Y, rect.Width - width, rect.Height);
}

TextRenderer.DrawText(
g,
DisplayText,
this.Font,
rect,
Color.Black,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter
);
}
}

このようなカスタム描画を行えば、プログレスバー上に「50%」のような文字を重ねられます。ただし、見た目やテーマ対応を細かく調整する必要があるため、まずはLabelとの併用から始めるのが簡単です。

9-4. 色や見た目を変更したい場合の注意点

WinFormsの標準ProgressBarは、OSのテーマに依存して表示されます。そのため、BackColorForeColorを設定しても、期待どおりに色が変わらない場合があります。

C#
progressBar1.ForeColor = Color.Green;

このように書いても、環境によって反映されないことがあります。

色や見た目を細かく変更したい場合は、次の方法を検討します。

カスタムコントロールを作る

Panelなどを使って独自のプログレスバーを作る

WPFを使ってスタイルを定義する

外部UIライブラリを使う

単純な進捗表示であれば標準のProgressBarで十分ですが、デザインを重視するアプリではカスタム実装が必要になることがあります。

9-5. キャンセルボタン付きの進捗画面を作る方法

時間のかかる処理では、キャンセルボタンを用意するとユーザーに親切です。

async/awaitでキャンセル処理を実装する場合は、CancellationTokenSourceを使います。

C#
private CancellationTokenSource cancellationTokenSource;

private async void buttonStart_Click(object sender, EventArgs e)
{
cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;

buttonStart.Enabled = false;
buttonCancel.Enabled = true;

progressBar1.Value = 0;
label1.Text = "処理中...";

IProgress<int> progress = new Progress<int>(value =>
{
progressBar1.Value = value;
label1.Text = $"{value}% 完了";
});

try
{
await Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
token.ThrowIfCancellationRequested();

Thread.Sleep(50);
progress.Report(i);
}
}, token);

label1.Text = "完了しました。";
}
catch (OperationCanceledException)
{
label1.Text = "キャンセルされました。";
}
finally
{
buttonStart.Enabled = true;
buttonCancel.Enabled = false;
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}

private void buttonCancel_Click(object sender, EventArgs e)
{
cancellationTokenSource?.Cancel();
}

キャンセル可能な処理にしておくと、時間のかかるファイル処理やデータ変換でもユーザーが操作しやすくなります。

10. C#プログレスバー実装でよくある質問

ここでは、C#のWinFormsでプログレスバーを実装するときによくある疑問をまとめます。

10-1. ProgressBarのValueが100なのに満タン表示されないのはなぜ?

Maximumが100より大きい可能性があります。

たとえば、Maximumが1000の場合、Valueが100でも10%の進捗にしかなりません。

C#
progressBar1.Maximum = 1000;
progressBar1.Value = 100;

パーセントとして扱いたい場合は、Maximumを100にします。

C#
progressBar1.Maximum = 100;
progressBar1.Value = 100;

また、Valueを100にした直後にフォームを閉じたり、プログレスバーを非表示にしたりすると、満タン表示が見えないこともあります。

10-2. 別スレッドからProgressBarを更新するとエラーになるのはなぜ?

WinFormsのコントロールは、基本的に作成されたUIスレッドから操作する必要があります。

Task.RunThreadの中から直接progressBar1.Valueを変更すると、スレッドが違うためエラーになることがあります。

安全に更新するには、IProgress<int>を使う方法がおすすめです。

C#
IProgress<int> progress = new Progress<int>(value =>
{
progressBar1.Value = value;
});

または、Invokeを使ってUIスレッドに処理を戻します。

C#
this.Invoke((Action)(() =>
{
progressBar1.Value = 50;
}));

ただし、現在の実装ではIProgress<int>を使う方がコードを整理しやすいです。

10-3. プログレスバーを縦向きに表示できる?

WinFormsの標準ProgressBarには、簡単に縦向き表示へ切り替えるプロパティはありません。

縦向きのプログレスバーを作りたい場合は、次のような方法があります。

カスタムコントロールを作成する

Panelを使って独自にバーを描画する

WPFで縦向きレイアウトを作る

外部UIライブラリを使う

標準のWinFormsだけで簡単に実装するなら、横向きのProgressBarを使うのが基本です。

10-4. WPFでも同じコードで使える?

WPFにもProgressBarはありますが、WinFormsと同じコードをそのまま使うことはできません。

WinFormsでは次のように書きます。

C#
progressBar1.Value = 50;

WPFでもValueという考え方はありますが、XAMLで配置し、データバインディングやDispatcherを使って更新することが多いです。

WinFormsのSystem.Windows.Forms.ProgressBarと、WPFのSystem.Windows.Controls.ProgressBarは別のコントロールとして考える必要があります。

10-5. 処理件数が不明な場合はどう表示すればよい?

処理件数が不明な場合は、Marqueeスタイルを使います。

C#
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.MarqueeAnimationSpeed = 30;

無理に疑似的なパーセントを表示すると、ユーザーに誤解を与えることがあります。完了時刻や件数が分からない処理では、「処理中です...」というラベルとMarquee表示を組み合わせるのが自然です。

C#
label1.Text = "処理中です。しばらくお待ちください。";

10-6. ProgressBarをリセットするにはどう書く?

プログレスバーを初期状態に戻すには、ValueMinimumに戻します。

C#
progressBar1.Value = progressBar1.Minimum;

一般的には、次のように書きます。

C#
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
label1.Text = "待機中";

Marquee表示から通常表示に戻す場合は、StyleMarqueeAnimationSpeedも戻します。

C#
progressBar1.MarqueeAnimationSpeed = 0;
progressBar1.Style = ProgressBarStyle.Blocks;
progressBar1.Value = 0;

これで、次の処理開始時にプログレスバーを正しい状態から使えます。

まとめ

C#のWinFormsでプログレスバーを使うには、ProgressBarコントロールをフォームに配置し、MinimumMaximumValueを設定して進捗を表現します。処理件数が分かっている場合は、全体件数をMaximumに設定し、処理完了ごとにValueを増やすのが基本です。

一方で、プログレスバーが更新されない場合は、UIスレッドを長時間処理で止めている可能性があります。Thread.SleepをUIスレッドで使ったり、重い処理をボタンクリックイベント内で直接実行したりすると、画面の再描画ができず、プログレスバーが動いていないように見えます。

現在のC#では、async/awaitTask.RunIProgress<int>を組み合わせることで、画面を固めず安全にプログレスバーを更新できます。既存のWinFormsアプリでは、BackgroundWorkerを使ってReportProgressProgressChangedで更新する方法も有効です。

また、進捗率が分からない処理では、無理にパーセントを表示せず、Marqueeスタイルを使って処理中であることを示すと分かりやすくなります。

C#のプログレスバー実装では、単にバーを表示するだけでなく、ラベル表示、完了メッセージ、二重クリック防止、キャンセル処理などを組み合わせることで、ユーザーにとって使いやすい画面を作れます。まずは基本のProgressBar.Value更新から始め、処理が重くなる場合はasync/awaitによる安全な実装に切り替えるのがおすすめです。