C#のRefreshとは?画面が更新されない原因とInvalidate・Updateとの違いをわかりやすく解説

はじめに

C#でWindows Formsアプリを作っていると、「値を変更したのに画面が変わらない」「Refreshを呼んでいるのに表示が更新されない」「InvalidateやUpdateとの違いがわからない」と悩むことがあります。

特に、Form、Panel、PictureBox、DataGridViewなどを使っている場合、Refresh()を呼べば何でも更新されるように思えます。しかし、C#のRefreshはあくまで画面を再描画するためのメソッドであり、データそのものを再取得したり、バインドされた値を自動で作り直したりするものではありません。

この記事では、C#のRefreshとは何か、画面が更新されない原因、InvalidateUpdateとの違い、そして実際の使い分けをサンプルコード付きでわかりやすく解説します。

1. C#のRefreshとは?まず知っておきたい基本

1-1. Refreshはコントロールをすぐ再描画するためのメソッド

C#のWindows FormsにおけるRefresh()は、コントロールを強制的に再描画するためのメソッドです。

たとえば、次のように呼び出します。

C#
this.Refresh();

または、特定のコントロールに対して呼び出します。

C#
panel1.Refresh();
pictureBox1.Refresh();
dataGridView1.Refresh();

Refresh()を呼ぶと、そのコントロールの表示領域が無効化され、すぐに再描画が実行されます。つまり、「このコントロールは描き直しが必要です」と指定したうえで、可能な限り即座に再描画させる処理です。

ただし、ここで重要なのは、Refresh()表示を描き直すだけという点です。内部のデータや変数、データソースの内容を自動で更新するわけではありません。

1-2. Refreshが使われる主な対象:Form・Panel・PictureBox・DataGridViewなど

Refresh()は、Windows Formsの多くのコントロールで使用できます。代表的な対象は次のようなものです。

C#
Form
Panel
PictureBox
DataGridView
Label
TextBox
UserControl

たとえば、Form全体を再描画したい場合は次のように書きます。

C#
this.Refresh();

Panelだけを再描画したい場合は、次のように対象を限定します。

C#
panel1.Refresh();

PictureBoxの画像を差し替えたあとに再描画したい場合は、次のように書くことがあります。

C#
pictureBox1.Image = newImage;
pictureBox1.Refresh();

DataGridViewの場合も、見た目を再描画したいだけであれば次のように呼び出せます。

C#
dataGridView1.Refresh();

ただし、DataGridViewのデータ自体が変わらない場合、Refresh()を呼んでも表示内容は変わらないことがあります。これは、Refresh()がデータの再取得ではなく、画面の再描画を行うメソッドだからです。

1-3. 「画面の再描画」と「データの更新」は別物

C#のRefreshで最も誤解されやすいのが、画面の再描画データの更新を同じものとして考えてしまうことです。

画面の再描画とは、現在コントロールが持っている情報をもとに、画面上にもう一度描き直す処理です。

一方、データの更新とは、変数、リスト、データベース、DataTable、BindingSourceなどの中身を変更する処理です。

たとえば、次のようなコードを考えます。

C#
label1.Text = "処理中...";
label1.Refresh();

この場合、label1.Textの値を変更したうえでRefresh()しているため、ラベルの表示は「処理中...」に更新されます。

しかし、次のようなDataGridViewでは注意が必要です。

C#
dataGridView1.Refresh();

このコードはDataGridViewを再描画するだけです。データソースの中身を変更していなければ、表示内容は変わりません。

つまり、Refresh()は「最新データを取得する」メソッドではなく、「現在の状態を画面に描き直す」メソッドです。

1-4. Refreshで解決できるケースと解決できないケース

Refresh()で解決できるのは、主に描画の反映が遅れているケースです。

たとえば、次のような場合です。

C#
label1.Text = "処理中...";
label1.Refresh();

HeavyProcess();

label1.Text = "完了";

重い処理の前にラベルの表示を変えたい場合、Refresh()を呼ぶことで「処理中...」という表示を先に反映できることがあります。

一方で、次のようなケースはRefresh()だけでは解決できません。

C#
list[0].Name = "変更後";
dataGridView1.Refresh();

DataGridViewにバインドしているリストが変更通知に対応していない場合、Refresh()してもセルの値が変わらないことがあります。この場合は、BindingSource.ResetBindings()を呼ぶ、BindingList<T>を使う、プロパティ変更通知を実装するなどの対応が必要です。

また、UIスレッドを長時間ブロックしている場合も、Refresh()だけでは根本的な解決になりません。その場合は、async/awaitや別スレッド処理、Invokeなどを使ってUIが固まらないように設計する必要があります。

2. C#で画面が更新される仕組み

2-1. Windows Formsの描画処理とPaintイベントの関係

Windows Formsでは、コントロールの描画は主にPaintイベントで行われます。

たとえば、Panelに図形を描画したい場合は次のようにします。

C#
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.Blue, 10, 10, 100, 50);
}

このPaintイベントは、コントロールを描き直す必要があるときに呼び出されます。

たとえば、次のようなタイミングです。

C#
・フォームが初めて表示されたとき
・ウィンドウが最小化から復元されたとき
・他のウィンドウに隠れていた部分が再表示されたとき
・InvalidateやRefreshが呼ばれたとき

つまり、C#で自前描画を行う場合、図形や文字を直接ボタンクリック内で描くのではなく、基本的にはPaintイベント内で描画するのが正しい考え方です。

2-2. コントロールは必要なタイミングで再描画される

Windows Formsのコントロールは、通常は自動的に再描画されます。

たとえば、LabelのTextを変更した場合、多くのケースでは明示的にRefresh()を呼ばなくても表示は変わります。

C#
label1.Text = "更新しました";

このコードだけで、通常はラベルの表示が更新されます。

TextBoxも同様です。

C#
textBox1.Text = "新しい文字列";

このような標準コントロールでは、プロパティ変更時に内部で再描画が必要と判断されるため、多くの場合、Refresh()は不要です。

ただし、重い処理を連続して実行している場合や、自前描画を行っている場合は、再描画のタイミングを意識する必要があります。

2-3. 画面更新が遅れる理由はメッセージループにある

Windows Formsアプリは、UIスレッド上でメッセージループを回しながら動作しています。

ボタンクリック、キー入力、マウス操作、画面描画などは、すべてUIスレッドで順番に処理されます。

たとえば、次のコードを考えます。

C#
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "処理中...";

Thread.Sleep(5000);

label1.Text = "完了";
}

このコードでは、label1.Textに「処理中...」を設定しています。しかし、その直後にThread.Sleep(5000)でUIスレッドを止めているため、画面が再描画される前に処理がブロックされます。

その結果、ユーザーから見ると「処理中...」が表示されず、5秒後にいきなり「完了」と表示されるように見えることがあります。

これは、Textプロパティが変わっていないのではなく、画面を描き直すための処理がUIスレッドで実行できていないことが原因です。

2-4. Refreshを呼ぶと内部で何が起きるのか

Refresh()を呼ぶと、対象コントロールは再描画が必要な状態になり、その後すぐに再描画処理が実行されます。

イメージとしては、次のような処理です。

C#
control.Invalidate();
control.Update();

より具体的には、Invalidateで「この領域は再描画が必要」とマークし、Updateで保留中の再描画を実行します。

そのため、Refresh()は次のような意味に近いです。

C#
control.Invalidate(true);
control.Update();

Invalidateだけでは、再描画はすぐに実行されるとは限りません。あくまで「あとで描き直してほしい」と予約する処理です。

一方、Refresh()は無効化したあとにすぐ描画しようとするため、画面更新を即時に反映したい場合に使われます。

3. Refresh・Invalidate・Updateの違い

3-1. Refreshは「無効化して即時再描画」する

Refresh()は、コントロールを無効化して、すぐに再描画させるメソッドです。

C#
panel1.Refresh();

このコードを実行すると、panel1の表示領域が再描画対象になり、すぐにPaintイベントが発生します。

自前描画をしている場合、Refresh()を呼ぶことでPaintイベント内の描画処理が再実行されます。

C#
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawEllipse(Pens.Red, 20, 20, 100, 100);
}

つまり、Refresh()は「とにかく今すぐ描き直したい」という場面で使います。

ただし、即時再描画はコストが高くなりやすいため、頻繁に呼び出すのは避けるべきです。

3-2. Invalidateは「再描画が必要」と予約する

Invalidate()は、コントロールに対して「再描画が必要です」と通知するメソッドです。

C#
panel1.Invalidate();

ただし、Invalidate()を呼んでも、その場ですぐに再描画されるとは限りません。

Invalidate()は再描画を予約するだけで、実際の描画はメッセージループのタイミングで行われます。

そのため、次のようなコードでは、Invalidate()の直後に必ず画面が変わるとは限りません。

C#
panel1.Invalidate();

大量の描画更新がある場合は、Refresh()よりもInvalidate()の方が適しています。なぜなら、複数回の更新をまとめて再描画できるため、パフォーマンス面で有利だからです。

3-3. Updateは「予約済みの再描画」を実行する

Update()は、すでに無効化されている領域がある場合に、その再描画を実行するメソッドです。

C#
panel1.Update();

ただし、Update()だけを呼んでも、再描画対象として無効化されていなければ何も起きないことがあります。

つまり、Update()は単体で使うよりも、Invalidate()と組み合わせて使うことが多いです。

C#
panel1.Invalidate();
panel1.Update();

このように書くと、「再描画が必要」と予約したあと、その再描画をすぐに実行できます。

3-4. Invalidate+UpdateとRefreshの関係

Refresh()は、Invalidate()Update()を組み合わせた処理に近いものです。

C#
panel1.Refresh();

これは、概念的には次のコードに近いです。

C#
panel1.Invalidate();
panel1.Update();

ただし、Refresh()は子コントロールも含めて再描画対象にする挙動を持つため、完全に同じ意味として乱暴に置き換えるのではなく、「RefreshはInvalidateとUpdateをまとめて呼ぶようなもの」と理解するとよいでしょう。

使い分けとしては、即時反映したい場合はRefresh()、通常の再描画予約でよい場合はInvalidate()、予約済みの描画をすぐ処理したい場合はUpdate()です。

3-5. どれを使うべきかの使い分け早見表

メソッド役割すぐ再描画されるか主な用途
Refresh無効化して即時再描画されやすい今すぐ画面を描き直したい
Invalidate再描画を予約すぐとは限らない自前描画の更新、通常の再描画要求
Update予約済みの再描画を実行無効領域があれば実行Invalidate後に即時反映したい
Invalidate + Update再描画予約後に即実行されやすいRefreshに近い制御をしたい

基本的には、まずInvalidate()を使うことを検討します。どうしても即時に描画を反映したい場合だけ、Refresh()Invalidate()Update()を使うとよいでしょう。

4. C#でRefreshしても画面が更新されない主な原因

4-1. UIスレッドを重い処理でブロックしている

Refresh()しても画面が更新されない原因として最も多いのが、UIスレッドを重い処理でブロックしているケースです。

たとえば、次のコードです。

C#
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "処理中...";
label1.Refresh();

for (int i = 0; i < 1000000000; i++)
{
// 重い処理
}

label1.Text = "完了";
}

label1.Refresh()によって一時的に表示が反映される場合もありますが、重い処理が続くとフォーム全体が固まり、ユーザー操作を受け付けなくなります。

このような場合、Refresh()で無理に画面を更新するよりも、重い処理をUIスレッドから分離することが重要です。

C#
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "処理中...";

await Task.Run(() =>
{
// 重い処理
Thread.Sleep(5000);
});

label1.Text = "完了";
}

このようにasync/awaitを使うと、UIスレッドを固めずに処理を進められます。

4-2. 値は変わっているがPaintイベントで描画していない

自前描画をしている場合、変数の値だけ変更しても画面は自動的に変わりません。

たとえば、次のようなコードです。

C#
private int x = 10;

private void button1_Click(object sender, EventArgs e)
{
x = 100;
}

この時点では、変数xの値は変わっています。しかし、画面上に描かれている図形が自動的に移動するわけではありません。

正しくは、Paintイベントで変数を使って描画し、値を変更したあとにInvalidate()またはRefresh()を呼びます。

C#
private int x = 10;

private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillEllipse(Brushes.Blue, x, 20, 50, 50);
}

private void button1_Click(object sender, EventArgs e)
{
x = 100;
panel1.Invalidate();
}

このように、描画内容を変えたい場合は、描画処理をPaintイベントに集約することが大切です。

4-3. データソースの変更がコントロールに通知されていない

DataGridViewでよくあるのが、データソースの中身は変更したのに画面に反映されないケースです。

たとえば、通常のList<T>をDataGridViewにバインドしている場合です。

C#
private List<Person> people = new List<Person>();

private void Form1_Load(object sender, EventArgs e)
{
people.Add(new Person { Name = "田中", Age = 30 });
dataGridView1.DataSource = people;
}

private void button1_Click(object sender, EventArgs e)
{
people[0].Name = "佐藤";
dataGridView1.Refresh();
}

この場合、people[0].Nameは変わっていますが、DataGridViewに変更通知が届かないため、表示が変わらないことがあります。

このような場合は、BindingSourceを使ってResetBindings()を呼びます。

C#
private BindingSource bindingSource = new BindingSource();
private List<Person> people = new List<Person>();

private void Form1_Load(object sender, EventArgs e)
{
people.Add(new Person { Name = "田中", Age = 30 });

bindingSource.DataSource = people;
dataGridView1.DataSource = bindingSource;
}

private void button1_Click(object sender, EventArgs e)
{
people[0].Name = "佐藤";
bindingSource.ResetBindings(false);
}

DataGridViewの値が変わらない場合、Refresh()ではなく、データバインディングの通知や再設定を確認しましょう。

4-4. 別スレッドからUIを直接更新している

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

別スレッドから直接Refresh()やText変更を行うと、例外が発生したり、表示が不安定になったりします。

悪い例は次のようなコードです。

C#
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
label1.Text = "処理中...";
label1.Refresh();
});
}

このコードでは、Task.Run内の別スレッドからlabel1を直接操作しています。

正しくは、InvokeBeginInvokeを使ってUIスレッドに処理を戻します。

C#
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
this.Invoke((Action)(() =>
{
label1.Text = "処理中...";
label1.Refresh();
}));
});
}

ただし、async/awaitを使える場面では、より自然に書けます。

C#
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "処理中...";

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

label1.Text = "完了";
}

4-5. 更新対象のコントロールを間違えている

Refresh()しているのに画面が変わらない場合、単純に更新対象のコントロールを間違えていることもあります。

たとえば、Panelの中に描画しているのにFormをRefreshしているケースです。

C#
this.Refresh();

PanelのPaintイベントで描画しているなら、Panelを更新対象にするべきです。

C#
panel1.Refresh();

DataGridViewを更新したいなら、DataGridViewに対して処理します。

C#
dataGridView1.Refresh();

ただし、親コントロールのRefreshで子コントロールが再描画されることもありますが、意図を明確にするためには、実際に表示を変えたいコントロールを対象にするのが安全です。

4-6. Refreshの直後に別の処理で表示内容を上書きしている

Refresh()自体は動いていても、その直後に別の処理で表示内容を上書きしているケースもあります。

たとえば、次のようなコードです。

C#
label1.Text = "処理中...";
label1.Refresh();

label1.Text = "";

この場合、一瞬「処理中...」が表示されても、すぐ空文字で上書きされるため、ユーザーには見えないことがあります。

また、Timer、別イベント、データバインディング、フォームの再初期化処理などで表示が上書きされている場合もあります。

画面が更新されないと感じたら、Refresh()だけでなく、その前後でどの処理が値を書き換えているかを確認しましょう。

5. 画面が更新されないときの対処法

5-1. 重い処理の前後でRefreshを呼ぶ

簡単な対処として、重い処理の前に表示を変更し、Refresh()で反映させる方法があります。

C#
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "処理中...";
label1.Refresh();

HeavyProcess();

label1.Text = "完了";
}

この方法は、短い処理や簡単な画面反映には使えます。

ただし、重い処理の間はUIスレッドが止まるため、フォームを移動できない、ボタンが押せない、画面が固まるといった問題が残ります。

そのため、根本的にはasync/awaitなどを使ってUIスレッドをブロックしない設計にする方が適しています。

5-2. Invalidateを使ってPaintイベントで再描画する

自前描画をしている場合は、Refresh()よりもInvalidate()を使う方が自然です。

C#
private int size = 50;

private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Green, 10, 10, size, size);
}

private void button1_Click(object sender, EventArgs e)
{
size += 10;
panel1.Invalidate();
}

このコードでは、ボタンを押すたびにsizeの値が変わり、panel1.Invalidate()によってPanelが再描画されます。

Invalidate()は即時再描画ではありませんが、通常のUI更新では十分です。むしろ、無駄な再描画を減らせるため、パフォーマンス面ではRefresh()より適していることが多いです。

5-3. Application.DoEventsを使う場合の注意点

Application.DoEvents()を使うと、現在たまっているWindowsメッセージを処理できます。そのため、重い処理中に画面更新を反映できることがあります。

C#
label1.Text = "処理中...";
Application.DoEvents();

HeavyProcess();

label1.Text = "完了";

ただし、Application.DoEvents()は注意して使う必要があります。

DoEvents()を呼ぶと、処理の途中でもボタンクリックなどの別イベントが実行される可能性があります。その結果、同じ処理が二重に実行されたり、想定外の状態変更が起きたりすることがあります。

そのため、Application.DoEvents()は一時的な対処としては使えますが、基本的にはasync/awaitやバックグラウンド処理で解決する方が安全です。

5-4. async/awaitでUIを固めずに更新する

重い処理中に画面が更新されない場合、最もおすすめしやすい方法はasync/awaitを使うことです。

C#
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
label1.Text = "処理中...";

await Task.Run(() =>
{
HeavyProcess();
});

label1.Text = "完了";
button1.Enabled = true;
}

このコードでは、重い処理をTask.Runで別スレッドに逃がし、UIスレッドをブロックしません。

そのため、label1.Text = "処理中...";の表示が自然に反映され、フォームも固まりにくくなります。

UIを更新する処理はawaitの前後に書くことで、基本的にUIスレッド上で安全に実行できます。

5-5. Invoke・BeginInvokeでUIスレッドに処理を戻す

別スレッドからUIを更新したい場合は、InvokeまたはBeginInvokeを使います。

C#
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
Thread.Sleep(1000);

this.Invoke((Action)(() =>
{
label1.Text = "更新しました";
}));
});
}

Invokeは、UIスレッドで処理が完了するまで待ちます。

一方、BeginInvokeは、UIスレッドに処理を依頼してすぐ戻ります。

C#
this.BeginInvoke((Action)(() =>
{
label1.Text = "更新しました";
}));

画面更新が必要な処理は、必ずUIスレッドで行うようにしましょう。別スレッドから直接Refresh()を呼ぶのは避けるべきです。

5-6. BindingSourceやDataSourceを再設定する

DataGridViewの表示が更新されない場合は、Refresh()ではなく、バインディング側の更新を確認します。

BindingSourceを使っている場合は、次のようにResetBindings()を呼びます。

C#
bindingSource.ResetBindings(false);

データソースを再設定する方法もあります。

C#
dataGridView1.DataSource = null;
dataGridView1.DataSource = people;

ただし、毎回DataSourceを再設定すると、選択状態やスクロール位置がリセットされることがあります。そのため、通常はBindingSourceを使う方が扱いやすいです。

また、行の追加や削除を画面に反映したい場合は、List<T>ではなくBindingList<T>を使うと便利です。

C#
private BindingList<Person> people = new BindingList<Person>();

BindingList<T>はリスト変更を通知できるため、DataGridViewとの相性がよいです。

6. コントロール別のRefresh使用例

6-1. FormをRefreshして画面全体を再描画する

Form全体を再描画したい場合は、Form自身に対してRefresh()を呼びます。

C#
this.Refresh();

たとえば、Formの背景色を変更したあと、すぐ反映したい場合です。

C#
private void button1_Click(object sender, EventArgs e)
{
this.BackColor = Color.LightBlue;
this.Refresh();
}

ただし、BackColorの変更は通常自動的に再描画されるため、この程度であればRefresh()は必須ではありません。

Form全体を強制的に再描画すると、子コントロールも含めて描画コストが大きくなることがあります。必要な範囲だけを更新するなら、対象コントロールを絞った方がよいです。

6-2. PanelやUserControlをRefreshする

PanelやUserControlに独自描画をしている場合、Refresh()Invalidate()を使うことがあります。

C#
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 0, 0, 100, 100);
}

private void button1_Click(object sender, EventArgs e)
{
panel1.Refresh();
}

ただし、通常はRefresh()よりもInvalidate()を使う方が適しています。

C#
panel1.Invalidate();

即時再描画が必要な場面でなければ、Invalidate()で十分です。

UserControlでも考え方は同じです。

C#
myUserControl1.Invalidate();

自作コントロールでは、状態を変更したあとにInvalidate()を呼ぶ設計にしておくと、描画処理をきれいに保てます。

6-3. PictureBoxで画像を差し替えたときにRefreshする

PictureBoxの画像を差し替える場合、通常はImageプロパティを変更するだけで表示が更新されます。

C#
pictureBox1.Image = Image.FromFile("sample.png");

多くの場合、これだけで再描画されます。

ただし、画像の差し替え直後に即座に反映したい場合や、独自描画と組み合わせている場合はRefresh()を呼ぶことがあります。

C#
pictureBox1.Image = Image.FromFile("sample.png");
pictureBox1.Refresh();

また、同じImageオブジェクトの内容を内部的に変更した場合は、PictureBoxが変更を検知できないことがあります。その場合は、Invalidate()Refresh()で再描画を促します。

C#
pictureBox1.Invalidate();

6-4. DataGridViewで表示内容を更新する

DataGridViewでRefresh()を使う場合は、表示の再描画だけが目的であることを理解しておく必要があります。

C#
dataGridView1.Refresh();

セルの見た目や描画を更新したいだけなら、これで十分な場合があります。

しかし、データソースの中身を変更した場合は、次のようにバインディング側を更新する必要があります。

C#
people[0].Name = "佐藤";
bindingSource.ResetBindings(false);

また、DataSourceを再設定する方法もあります。

C#
dataGridView1.DataSource = null;
dataGridView1.DataSource = people;

DataGridViewで「Refreshしても値が変わらない」と感じる場合、多くはRefresh()の問題ではなく、データソースの変更通知の問題です。

6-5. LabelやTextBoxの表示変更でRefreshが不要なケース

LabelやTextBoxでは、通常、Textプロパティを変更するだけで表示が更新されます。

C#
label1.Text = "更新されました";
textBox1.Text = "新しい値";

このような場合、基本的にRefresh()は不要です。

C#
label1.Text = "更新されました";
label1.Refresh();

上記のように書いても間違いではありませんが、多くの場合は冗長です。

ただし、Text変更の直後に重い同期処理を実行する場合は、表示が反映される前にUIスレッドが止まることがあります。

C#
label1.Text = "処理中...";
label1.Refresh();

HeavyProcess();

このようなケースでは一時的にRefresh()が役立つことがありますが、根本的には非同期処理でUIを固めない設計にするのが理想です。

7. Refreshのサンプルコード

7-1. 基本的なRefreshの使い方

最も基本的なRefresh()の使い方は、表示を変更したあとに対象コントロールを再描画することです。

C#
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "更新しました";
label1.Refresh();
}

ただし、LabelのText変更では通常Refresh()は不要です。

よりRefresh()の意味がわかりやすいのは、Panelの再描画です。

C#
private void button1_Click(object sender, EventArgs e)
{
panel1.Refresh();
}

PanelにPaintイベントが設定されていれば、Refresh()によって再描画されます。

7-2. InvalidateとUpdateを組み合わせた再描画例

Invalidate()Update()を組み合わせると、再描画を予約したあと、すぐに実行できます。

C#
private void button1_Click(object sender, EventArgs e)
{
panel1.Invalidate();
panel1.Update();
}

これはRefresh()に近い動きです。

C#
private void button1_Click(object sender, EventArgs e)
{
panel1.Refresh();
}

ただし、細かく制御したい場合は、Invalidate()Update()を分けて使うことがあります。

たとえば、特定の領域だけ再描画したい場合です。

C#
private void button1_Click(object sender, EventArgs e)
{
Rectangle rect = new Rectangle(10, 10, 100, 100);
panel1.Invalidate(rect);
panel1.Update();
}

このようにすれば、Panel全体ではなく、指定した範囲の再描画を要求できます。

7-3. Paintイベントで図形を再描画する例

自前描画では、描画処理をPaintイベントに書きます。

C#
private int circleX = 20;

private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillEllipse(Brushes.Red, circleX, 30, 50, 50);
}

private void button1_Click(object sender, EventArgs e)
{
circleX += 20;
panel1.Invalidate();
}

このコードでは、ボタンを押すたびに円のX座標が変わり、Panelが再描画されます。

即時に再描画したい場合はRefresh()でも構いません。

C#
private void button1_Click(object sender, EventArgs e)
{
circleX += 20;
panel1.Refresh();
}

ただし、通常はInvalidate()で十分です。頻繁に描画する場合ほど、Refresh()の多用は避けた方がよいでしょう。

7-4. 重い処理中に画面更新を反映させる例

重い処理の前に画面表示を変えたい場合、単純には次のように書けます。

C#
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "処理中...";
label1.Refresh();

Thread.Sleep(3000);

label1.Text = "完了";
}

このコードでは、Thread.Sleepの前にRefresh()を呼んでいるため、「処理中...」が表示されやすくなります。

しかし、Thread.Sleep中はUIが固まります。より良い書き方は次のような非同期処理です。

C#
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
label1.Text = "処理中...";

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

label1.Text = "完了";
button1.Enabled = true;
}

この方法なら、UIスレッドをブロックしないため、画面更新も自然に反映されます。

7-5. DataGridViewの表示を更新する例

DataGridViewでデータを更新する場合は、BindingSourceを使うと扱いやすくなります。

C#
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
C#
private BindingSource bindingSource = new BindingSource();
private List<Person> people = new List<Person>();

private void Form1_Load(object sender, EventArgs e)
{
people.Add(new Person { Name = "田中", Age = 30 });
people.Add(new Person { Name = "鈴木", Age = 25 });

bindingSource.DataSource = people;
dataGridView1.DataSource = bindingSource;
}

値を変更したあと、ResetBindings()を呼びます。

C#
private void button1_Click(object sender, EventArgs e)
{
people[0].Name = "佐藤";
bindingSource.ResetBindings(false);
}

この場合、dataGridView1.Refresh()よりもbindingSource.ResetBindings(false)の方が重要です。

行の追加や削除が多い場合は、BindingList<T>の利用も検討できます。

C#
private BindingList<Person> people = new BindingList<Person>();
C#
private void button1_Click(object sender, EventArgs e)
{
people.Add(new Person { Name = "高橋", Age = 40 });
}

BindingList<T>を使うと、追加や削除がDataGridViewに反映されやすくなります。

8. Refreshを使うときの注意点

8-1. Refreshの多用はパフォーマンス低下につながる

Refresh()は即時再描画を行うため、便利ですがコストの高い処理です。

特に、フォーム全体や複雑なコントロールに対して何度もRefresh()を呼ぶと、画面がちらついたり、アプリの動作が重くなったりします。

たとえば、次のようなコードは避けるべきです。

C#
for (int i = 0; i < 1000; i++)
{
panel1.Refresh();
}

このように連続して再描画すると、描画処理が何度も発生して無駄が多くなります。

通常は、状態をまとめて変更したあとに一度だけInvalidate()する方が効率的です。

C#
for (int i = 0; i < 1000; i++)
{
// 状態だけ変更する
}

panel1.Invalidate();

8-2. ループ内で連続してRefreshしない

進捗表示などでループ内からRefresh()を呼びたくなることがあります。

C#
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
label1.Refresh();

HeavyProcess();
}

このようなコードは、画面更新のために処理全体が重くなりやすいです。

進捗表示を行う場合は、async/awaitProgress<T>を使う方が安全です。

C#
private async void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(value =>
{
label1.Text = value + "%";
});

await Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
((IProgress<int>)progress).Report(i);
Thread.Sleep(50);
}
});
}

このようにすれば、UIスレッドを固めずに進捗を表示できます。

8-3. データ更新の目的でRefreshを使わない

Refresh()は、データ更新のためのメソッドではありません。

たとえば、次のように考えるのは誤りです。

C#
dataGridView1.Refresh(); // データベースから最新データを取得するわけではない

データベースから最新データを取得したい場合は、再検索や再読み込みの処理が必要です。

C#
var data = LoadDataFromDatabase();
dataGridView1.DataSource = data;

バインド済みのデータを更新したい場合は、BindingSource.ResetBindings()などを使います。

C#
bindingSource.ResetBindings(false);

つまり、データの更新と画面の再描画は分けて考える必要があります。

8-4. WPFやASP.NETの「更新」と混同しない

C#といっても、Windows Forms、WPF、ASP.NETでは画面更新の仕組みが異なります。

Windows FormsのRefresh()は、コントロールを再描画するためのメソッドです。

一方、WPFではInvalidateVisual()、データバインディング、ObservableCollection<T>INotifyPropertyChangedなどが重要になります。

ASP.NETでは、そもそもサーバー側でHTMLを生成し、ブラウザに返す仕組みなので、Windows FormsのRefresh()とは考え方が異なります。

そのため、「C# refresh」と検索して出てきた情報でも、Windows Formsの話なのか、WPFの話なのか、ASP.NETの話なのかを区別する必要があります。

この記事で解説しているRefresh()は、主にWindows Formsのコントロールにおける再描画の話です。

8-5. 根本原因を確認してからRefreshを使う

画面が更新されないときに、とりあえずRefresh()を追加するだけでは、根本解決にならないことがあります。

確認すべきポイントは次のとおりです。

確認ポイント見るべき内容
UIスレッドが止まっていないか重い処理を同期実行していないか
描画処理の場所は正しいかPaintイベントで描いているか
データは本当に変わっているか変数やデータソースの中身を確認する
変更通知は届いているかBindingSourceやBindingListを使っているか
UIスレッドで更新しているかInvokeが必要な処理ではないか
対象コントロールは正しいかFormではなくPanelを更新すべきではないか

Refresh()は便利ですが、万能ではありません。まず原因を切り分けたうえで、必要な場面にだけ使うことが大切です。

9. よくある質問

9-1. RefreshとUpdateはどちらを使えばよい?

すぐに再描画したいだけなら、基本的にはRefresh()を使えばわかりやすいです。

C#
panel1.Refresh();

ただし、Update()は単体では再描画対象がない場合に何も起きないことがあります。

C#
panel1.Update();

Update()は、通常Invalidate()と組み合わせて使います。

C#
panel1.Invalidate();
panel1.Update();

通常の再描画要求ならInvalidate()、即時反映したいならRefresh()またはInvalidate()Update()と考えるとよいでしょう。

9-2. Invalidateだけではなぜすぐ更新されない?

Invalidate()は、再描画を「予約」するメソッドだからです。

C#
panel1.Invalidate();

このコードは、「panel1は再描画が必要です」と通知するだけで、その場で必ず描画するわけではありません。

実際の再描画は、Windows Formsのメッセージループの中で適切なタイミングで行われます。

すぐに描画したい場合は、次のようにUpdate()を組み合わせます。

C#
panel1.Invalidate();
panel1.Update();

または、Refresh()を使います。

C#
panel1.Refresh();

9-3. DataGridViewでRefreshしても値が変わらないのはなぜ?

DataGridViewのRefresh()は、表示を再描画するだけで、データソースの内容を再読み込みするわけではないためです。

たとえば、List<T>の中身を変更しても、DataGridViewに変更通知が届かなければ表示は変わらないことがあります。

この場合は、BindingSource.ResetBindings()を使います。

C#
bindingSource.ResetBindings(false);

または、DataSourceを再設定します。

C#
dataGridView1.DataSource = null;
dataGridView1.DataSource = people;

行の追加や削除を反映したい場合は、BindingList<T>の利用も検討しましょう。

9-4. 別スレッドからRefreshしてもよい?

基本的には、別スレッドから直接Refresh()してはいけません。

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

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

C#
Task.Run(() =>
{
label1.Refresh();
});

正しくは、InvokeBeginInvokeを使ってUIスレッドに戻します。

C#
Task.Run(() =>
{
this.Invoke((Action)(() =>
{
label1.Refresh();
}));
});

ただし、UI更新を伴う処理では、async/awaitを使った方がシンプルに書けることが多いです。

9-5. Refreshを呼ばなくても画面が更新されるのはなぜ?

多くの標準コントロールは、プロパティが変更されたときに自動的に再描画を要求するためです。

たとえば、次のコードでは通常Refresh()は不要です。

C#
label1.Text = "こんにちは";
textBox1.Text = "入力済み";
button1.Enabled = false;

これらのプロパティ変更では、コントロール側が必要に応じて再描画を行います。

そのため、通常のUI更新で毎回Refresh()を呼ぶ必要はありません。

Refresh()が必要になるのは、自前描画をしている場合、即時反映したい場合、重い処理の前に一時的に表示を変えたい場合などです。

まとめ

C#のRefresh()は、Windows Formsのコントロールを強制的に再描画するためのメソッドです。Form、Panel、PictureBox、DataGridViewなどに対して使えますが、あくまで画面を描き直すためのものであり、データそのものを更新するメソッドではありません。

Refresh()は、概念的にはInvalidate()で再描画を要求し、Update()で即時に描画する処理に近いものです。

一方、Invalidate()は再描画の予約、Update()は予約済み再描画の実行です。通常はInvalidate()を使い、どうしても即時反映したい場合にRefresh()を使うとよいでしょう。

画面が更新されない原因としては、UIスレッドのブロック、Paintイベントで描画していない、データソースの変更通知が届いていない、別スレッドからUIを操作している、更新対象のコントロールを間違えているなどが考えられます。

特にDataGridViewでは、Refresh()を呼んでもデータが変わらないことがあります。その場合は、BindingSource.ResetBindings()BindingList<T>、DataSourceの再設定など、データバインディング側の対応が必要です。

Refresh()は便利なメソッドですが、多用するとパフォーマンス低下やちらつきの原因になります。画面が更新されないときは、まず原因を切り分けたうえで、RefreshInvalidateUpdateasync/awaitInvokeなどを適切に使い分けましょう。