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

コントロールの描画を一時的に停止する

例えばListBoxコントロールに大量のアイテムを追加する時、何もしないとListBoxの再描画が何度も行われ、そのためにパフォーマンスが低下したり、画面がちらついたりすることがあります。このような場合は、アイテムを追加する間だけコントロールの描画を停止させます。こうすることで、パフォーマンスが向上し、ちらつきを抑えることができます。ここでは、コントロールの描画を一時的に停止する方法を紹介します。

BeginUpdate、EndUpdateメソッドを使用する方法

ListBox、ComboBox、ListView、TreeViewといったコントロールにはBeginUpdateとEndUpdateメソッドがあり、BeginUpdateメソッドでコントロールの描画を停止し、EndUpdateメソッドで再開することができます。

また、このようなコントロールのほとんどが、AddRangeメソッドを持っています。AddRangeメソッドを使うと、再描画することなく、複数のアイテムを一度に追加することができます。

これらのメソッドについて詳しい説明と使い方は、「ListBox(またはComboBox)に複数の項目をより速く追加する」をご覧ください。

WM_SETREDRAWメッセージを使用する方法

BeginUpdateとEndUpdateメソッドのないコントロールでは、WM_SETREDRAWメッセージによって描画を一時的に停止することができます。

例えば以下のようなメソッドを使えば、BeginControlUpdateで描画を停止し、EndControlUpdateで再開させることができます。このコードは、「How do I suspend painting for a control and its children?」を参考にして書きました。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms
'Imports System.Runtime.InteropServices

<DllImport("user32.dll")> _
Public Shared Function SendMessage(hWnd As HandleRef, _
    msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
Private Const WM_SETREDRAW As Integer = &HB

''' <summary>
''' コントロールの再描画を停止させる
''' </summary>
''' <param name="control">対象のコントロール</param>
Public Shared Sub BeginControlUpdate(control As Control)
    SendMessage(New HandleRef(control, control.Handle), _
                WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero)
End Sub

''' <summary>
''' コントロールの再描画を再開させる
''' </summary>
''' <param name="control">対象のコントロール</param>
Public Shared Sub EndControlUpdate(control As Control)
    SendMessage(New HandleRef(control, control.Handle), _
                WM_SETREDRAW, New IntPtr(1), IntPtr.Zero)
    control.Invalidate()
End Sub
C#
コードを隠すコードを選択
//using System.Windows.Forms;
//using System.Runtime.InteropServices;

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(
    HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam);
private const int WM_SETREDRAW = 0x000B;

/// <summary>
/// コントロールの再描画を停止させる
/// </summary>
/// <param name="control">対象のコントロール</param>
public static void BeginControlUpdate(Control control)
{
    SendMessage(new HandleRef(control, control.Handle),
        WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
}

/// <summary>
/// コントロールの再描画を再開させる
/// </summary>
/// <param name="control">対象のコントロール</param>
public static void EndControlUpdate(Control control)
{
    SendMessage(new HandleRef(control, control.Handle),
        WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
    control.Invalidate();
}

NativeWindow.DefWndProcメソッドを使用する方法

How do I suspend painting for a control and its children?」には、SendMessageの代わりにNativeWindow.DefWndProcメソッドを使用する方法も紹介されています。ただしMSDNのNativeWindow.DefWndProcメソッドの説明には、

「通常、DefWndProc メソッドは、ウィンドウ メッセージを受け取り、既定のウィンドウ プロシージャにメッセージを処理させる場合にだけ使用します。 ウィンドウ メッセージをウィンドウに送信するために DefWndProc を呼び出すことは避けてください。このような場合は、Win32 の SendMessage 関数を使用してください。」

と書かれています。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms

Private Const WM_SETREDRAW As Integer = &HB

''' <summary>
''' コントロールの再描画を停止させる
''' </summary>
''' <param name="control">対象のコントロール</param>
Public Shared Sub SuspendRedraw(control As Control)
    If control.IsHandleCreated Then
        Dim msg As Message = Message.Create(control.Handle, WM_SETREDRAW, _
                                            IntPtr.Zero, IntPtr.Zero)
        Dim window As NativeWindow = NativeWindow.FromHandle(control.Handle)
        window.DefWndProc(msg)
    End If
End Sub

''' <summary>
''' コントロールの再描画を再開させる
''' </summary>
''' <param name="control">対象のコントロール</param>
Public Shared Sub ResumeRedraw(control As Control)
    If control.IsHandleCreated Then
        Dim msg As Message = Message.Create(control.Handle, WM_SETREDRAW, _
                                            New IntPtr(1), IntPtr.Zero)
        Dim window As NativeWindow = NativeWindow.FromHandle(control.Handle)
        window.DefWndProc(msg)

        control.Invalidate()
    End If
End Sub
C#
コードを隠すコードを選択
//using System.Windows.Forms;

private const int WM_SETREDRAW = 0x000B;

/// <summary>
/// コントロールの再描画を停止させる
/// </summary>
/// <param name="control">対象のコントロール</param>
public static void SuspendRedraw(Control control)
{
    if(control.IsHandleCreated)
    {
        Message msg = Message.Create(
            control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msg);
    }
}

/// <summary>
/// コントロールの再描画を再開させる
/// </summary>
/// <param name="control">対象のコントロール</param>
public static void ResumeRedraw(Control control)
{
    if(control.IsHandleCreated)
    {
        Message msg = Message.Create(
            control.Handle, WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msg);

        control.Invalidate();
    }
}

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

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。