DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DOBON.NET

Refresh、Update、Invalidateメソッドの違い

ControlクラスのRefresh、Update、Invalidateメソッドは主にコントロールの再描画を促すために使用されますが、これらの違いはヘルプを読んだだけでは非常に分かりにくいです。

ヘルプによると、これらのメソッドは次のように説明されています。

  • Refresh メソッド : 強制的に、コントロールがクライアント領域を無効化し、直後にそのコントロール自体とその子コントロールを再描画するようにします。
  • Update メソッド : コントロールによって、クライアント領域内の無効化された領域が再描画されます。
  • Invalidate メソッド : コントロールの特定の領域を無効にし、そのコントロールに描画メッセージを送信します。

これらのメソッドが実際に何をしているのか調べるには、「Reflector for .NET」などの逆アセンブラを使ってDecompileすればよいでしょう。その結果、次のようなことが分かります。

まず、Refreshメソッドは、Invalidateメソッド(パラメータはTrue)を呼び出した後、さらにUpdateメソッドを呼び出しているだけです。

また、Invalidateメソッドは、Win32 APIのRedrawWindow(子コントロールを再描画する時)または、InvalidateRect関数を呼び出しており、Updateメソッドは、Win32 APIのUpdateWindow関数を呼び出しているらしいということが分かります。

つまり、Invalidateメソッドはアプリケーションキューが空になった時にコントロールを再描画し、Updateメソッドはアプリケーションキューが空でなくても再描画すべき領域があるならばすぐに再描画し、Refreshはアプリケーションキューが空でなくてもコントロールとその子コントロールをすぐに再描画するということになります。

言葉だけでは分かりにくいので、実際のコードでその動作を確認してみましょう。次のサンプルは、Windowsフォーム(Form1)にボタンを4つ配置し、これらをクリックすると、フォームに0から10までの数字が1秒おきにカウントアップして表示されるようにしたものです。4つのボタンはそれぞれカウンターをインクリメントした後にRefresh、Update、Invalidateメソッドを呼び出す(あるいは呼び出さない)という違いがあります。

[VB.NET]
'カウンターの値
Private counter As Integer = 0

'Button1のクリックイベントハンドラ
Private Sub Button1_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    Dim i As Integer
    For i = 0 To 9
        'カウンターを増やす
        counter += 1
        'フォームを再描画しない
        '1秒間停止する
        System.Threading.Thread.Sleep(1000)
    Next i
End Sub

'Button2のクリックイベントハンドラ
Private Sub Button2_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Button2.Click
    Dim i As Integer
    For i = 0 To 9
        'カウンターを増やす
        counter += 1
        'Invalidateを呼び出す
        Me.Invalidate()
        '1秒間停止する
        System.Threading.Thread.Sleep(1000)
    Next i
End Sub

'Button3のクリックイベントハンドラ
Private Sub Button3_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Button3.Click
    Dim i As Integer
    For i = 0 To 9
        'カウンターを増やす
        counter += 1
        'Updateを呼び出す
        Me.Update()
        '1秒間停止する
        System.Threading.Thread.Sleep(1000)
    Next i
End Sub

'Button4のクリックイベントハンドラ
Private Sub Button4_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Button4.Click
    Dim i As Integer
    For i = 0 To 9
        'カウンターを増やす
        counter += 1
        'Refreshを呼び出す
        Me.Refresh()
        '1秒間停止する
        System.Threading.Thread.Sleep(1000)
    Next i
End Sub

'PictureBox1のPaintイベントハンドラ
Private Sub PictureBox1_Paint(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.PaintEventArgs) _
        Handles MyBase.Paint
    'カウンターを描画する
    e.Graphics.DrawString(counter.ToString(), Me.Font, _
        Brushes.Black, 0, 0)
End Sub
[C#]
//カウンターの値
private int counter = 0;

//Button1のクリックイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        //カウンターを増やす
        counter++;
        //フォームを再描画しない
        //1秒間停止する
        System.Threading.Thread.Sleep(1000);
    }
}

//Button2のクリックイベントハンドラ
private void Button2_Click(object sender, System.EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        //カウンターを増やす
        counter++;
        //Invalidateを呼び出す
        this.Invalidate();
        //1秒間停止する
        System.Threading.Thread.Sleep(1000);
    }
}

//Button3のクリックイベントハンドラ
private void Button3_Click(object sender, System.EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        //カウンターを増やす
        counter++;
        //Updateを呼び出す
        this.Update();
        //1秒間停止する
        System.Threading.Thread.Sleep(1000);
    }
}

//Button4のクリックイベントハンドラ
private void Button4_Click(object sender, System.EventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        //カウンターを増やす
        counter++;
        //Refreshを呼び出す
        this.Refresh();
        //1秒間停止する
        System.Threading.Thread.Sleep(1000);
    }
}

//PictureBox1のPaintイベントハンドラ
private void PictureBox1_Paint(
    object sender, System.Windows.Forms.PaintEventArgs e)
{
    //カウンターを描画する
    e.Graphics.DrawString(
        counter.ToString(), this.Font, Brushes.Black, 0, 0);
}

まず、カウンターをインクリメントした後に何もしない時(Button1をクリックした時)は、フォームに表示された数字は全く変化しません。ただし、10秒後フォームを一回隠してもう一度表示するなどすれば、カウンターの値が表示されます。しかもこの10秒間は、フォーム全体はフリーズしたようになり、フォームの上に別のウィンドウを重ねても、フォームは一切再描画されません。

Invalidateメソッドを呼び出した時(Button2をクリックした時)も同様に10秒間変化しませんが、10秒後ようやくカウンターが更新されて表示されます。しかし、10秒間フォーム全体はフリーズしたようになります。

Updateメソッドを呼び出した時(Button3をクリックした時)も同じく何も変化しないように見えますが、フォームの上に別のウィンドウを重ね、またどけるというようなことを行うと、フォームは1秒おきに再描画されます。カウンターが表示される部分を別のウィンドウで隠せば、カウンターも更新されて表示されます。

Refreshメソッドを呼び出した時(Button4をクリックした時)は、何もしなくても1秒おきにフォーム全体が再描画され、カウンターも更新されて表示されます。

実際のこれらのメソッドの使い分けとしては、コントロールを再描画したいがすぐである必要がないときはInvalidateメソッドを使い、今すぐ再描画する必要があるときはRefreshメソッドを使うということになるでしょう。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。