例えばListBoxコントロールに大量のアイテムを追加する時、何もしないとListBoxの再描画が何度も行われ、そのためにパフォーマンスが低下したり、画面がちらついたりすることがあります。このような場合は、アイテムを追加する間だけコントロールの描画を停止させます。こうすることで、パフォーマンスが向上し、ちらつきを抑えることができます。ここでは、コントロールの描画を一時的に停止する方法を紹介します。
ListBox、ComboBox、ListView、TreeViewといったコントロールにはBeginUpdateとEndUpdateメソッドがあり、BeginUpdateメソッドでコントロールの描画を停止し、EndUpdateメソッドで再開することができます。
また、このようなコントロールのほとんどが、AddRangeメソッドを持っています。AddRangeメソッドを使うと、再描画することなく、複数のアイテムを一度に追加することができます。
これらのメソッドについて詳しい説明と使い方は、「ListBox(またはComboBox)に複数の項目をより速く追加する」をご覧ください。
BeginUpdateとEndUpdateメソッドのないコントロールでは、WM_SETREDRAWメッセージによって描画を一時的に停止することができます。
例えば以下のようなメソッドを使えば、BeginControlUpdateで描画を停止し、EndControlUpdateで再開させることができます。このコードは、「How do I suspend painting for a control and its children?」を参考にして書きました。
'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
//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(); }
「How do I suspend painting for a control and its children?」には、SendMessageの代わりにNativeWindow.DefWndProcメソッドを使用する方法も紹介されています。ただしMSDNのNativeWindow.DefWndProcメソッドの説明には、
「通常、DefWndProc メソッドは、ウィンドウ メッセージを受け取り、既定のウィンドウ プロシージャにメッセージを処理させる場合にだけ使用します。 ウィンドウ メッセージをウィンドウに送信するために DefWndProc を呼び出すことは避けてください。このような場合は、Win32 の SendMessage 関数を使用してください。」
と書かれています。
'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
//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(); } }