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

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

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

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

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

これらのメソッドが具体的に何を行っているのかを調べるには、「Reflector for .NET」のような逆アセンブラを使う方法があります。実際に行ってみると、次のようなことが分かります。

  • Refreshメソッドは、Invalidateメソッド(invalidateChildrenパラメータはTrue)を呼び出した後、さらにUpdateメソッドを呼び出しています。
  • Invalidateメソッドは、invalidateChildrenパラメータがTrueの時(子コントロールを再描画する時)はRedrawWindow関数(flagsパラメータは、RDW_ERASE + RDW_INVALIDATE + RDW_ALLCHILDREN)を、それ以外ではInvalidateRect関数(bEraseパラメータは、コントロールスタイルにControlStyles.Opaqueが設定されている時はFalse、それ以外はTrue)を呼び出しています。
  • Updateメソッドは、UpdateWindow関数を呼び出しています。

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

違いを実際に見てみる

実際のコードで動作の違いを確認してみましょう。以下のサンプル(Windowsフォームアプリケーション)では、Windowsフォーム(Form1)にボタンを2つ(Button1, 2)とピクチャボックスを4つ(PictureBox1〜4)が配置されています。ピクチャボックスのPaintイベントではcounterフィールドの値を描画しています。そしてButton1のClickイベントハンドラ(Button1_Click)では1秒おきにcounterフィールドの値を増やして、その直後にPictureBox2はRefreshメソッド、PictureBox3はUpdateメソッド、PictureBox4はInvalidateメソッドを呼び出しています(PictureBox1は何もしません)。これをButton1_Clickから抜けることなく10回繰り返します。

このコードをビルド(.NET Framework 2.0、C#)して作成した実行ファイル「ControlInvalidateTest.exe」を置いておきますので、実際にお確かめください。

VB.NET
コードを隠すコードを選択
Public Class Form1
    'カウンターの値
    Private counter As Integer = 0

    'PictureBoxのPaintイベントハンドラ
    Private Sub PictureBox_Paint(sender As Object, e As PaintEventArgs) _
            Handles PictureBox1.Paint, _
            PictureBox2.Paint, _
            PictureBox3.Paint, _
            PictureBox4.Paint
        'カウンターを描画する
        e.Graphics.DrawString(counter.ToString(), Me.Font, Brushes.Black, 0, 0)
    End Sub

    'Button1のClickイベントハンドラ
    Private Sub Button1_Click(sender As Object, e As EventArgs) _
            Handles Button1.Click
        For i As Integer = 0 To 9
            'カウンターを増やす
            Me.counter += 1

            'PictureBox1では、何もしない
            'PictureBox2では、Refreshを呼び出す
            PictureBox2.Refresh()
            'PictureBox3では、Updateを呼び出す
            PictureBox3.Update()
            'PictureBox4では、Invalidateを呼び出す
            PictureBox4.Invalidate()

            '1秒間停止する
            System.Threading.Thread.Sleep(1000)
        Next
    End Sub

    'Button2のClickイベントハンドラ
    Private Sub Button2_Click(sender As Object, e As EventArgs) _
            Handles Button2.Click
        'カウンターをリセットする
        Me.counter = 0
        'PictureBoxを再描画する
        PictureBox1.Invalidate()
        PictureBox2.Invalidate()
        PictureBox3.Invalidate()
        PictureBox4.Invalidate()
    End Sub
End Class
C#
コードを隠すコードを選択
using System;
using System.Drawing;
using System.Windows.Forms;

namespace ControlInvalidateTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            //イベントにイベントハンドラをアタッチする
            Button1.Click += new EventHandler(Button1_Click);
            Button2.Click += new EventHandler(Button2_Click);
            PictureBox1.Paint += new PaintEventHandler(PictureBox_Paint);
            PictureBox2.Paint += new PaintEventHandler(PictureBox_Paint);
            PictureBox3.Paint += new PaintEventHandler(PictureBox_Paint);
            PictureBox4.Paint += new PaintEventHandler(PictureBox_Paint);
        }

        //カウンターの値
        private int counter = 0;

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

        //Button1のClickイベントハンドラ
        private void Button1_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < 10; i++)
            {
                //カウンターを増やす
                this.counter++;

                //PictureBox1では、何もしない
                //PictureBox2では、Refreshを呼び出す
                PictureBox2.Refresh();
                //PictureBox3では、Updateを呼び出す
                PictureBox3.Update();
                //PictureBox4では、Invalidateを呼び出す
                PictureBox4.Invalidate();

                //1秒間停止する
                System.Threading.Thread.Sleep(1000);
            }
        }

        //Button2のClickイベントハンドラ
        private void Button2_Click(object sender, EventArgs e)
        {
            //カウンターをリセットする
            this.counter = 0;
            //PictureBoxを再描画する
            PictureBox1.Invalidate();
            PictureBox2.Invalidate();
            PictureBox3.Invalidate();
            PictureBox4.Invalidate();
        }
    }
}

普通に実行させると、カウンターが1秒おきにインクリメントされて表示されるのは、Refreshメソッドを使ったPictureBox2だけです。Invalidateメソッドを使ったPictureBox4は、10秒後にカウンターの表示が更新されます。何もしなかったPictureBox1とUpdateメソッドを使ったPictureBox3は10秒経っても「0」のままです。しかし、フォームの上に別のウィンドウを重ねて隠してからどけたり、フォームを最小化してから元に戻したりすると、「10」と表示されます。

何もしなかった時とUpdateメソッドを使った時は同じに見えますが、Updateメソッドを使った時はフォームを別のウィンドウで隠してどけるというようなことを行うと、10秒経過する前でも更新されて表示されます。

注意:環境によっては、フォームを別のウィンドウで隠してからどけるといった方法では再描画されません。Windows Vista以降では、テーマをクラシックにしていなければ、この方法で再描画はできないようです。さらに、それ以外の動作も、様々な要因で変化する可能性があります。

実際の使い分け

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

補足:.NET Frameworkの標準コントロールで言うと、例えばPictureBoxコントロールではImageプロパティを変更すると内部でInvalidateメソッドが呼び出されています。
  • 履歴:
  • 2014/1/14 コードの書き換え、実行ファイルの公開、注意事項の追加など。

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

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。