UI development
control painting
programming
performance optimization
event handling

How do I suspend painting for a control and its children?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Suspending Painting for a Control and Its Children

When updating multiple UI elements at once, each individual change can trigger a repaint of the control. This causes visible flickering and wastes CPU cycles on intermediate states the user never needs to see. Suspending painting lets you batch all updates together so the control redraws only once, showing the final result.

This technique is most commonly needed in Windows Forms, but the concept applies to other UI frameworks as well. This article covers practical approaches in Windows Forms with working C# code, plus brief notes on WPF and web development equivalents.

Why Suspend Painting?

Every time you modify a property of a Windows Forms control (adding items to a ListBox, changing cell values in a DataGridView, repositioning child controls), the framework sends a WM_PAINT message that triggers a redraw. When you make dozens of changes in rapid succession, the user sees each intermediate state as a brief flicker.

Suspending painting addresses three problems:

  1. Visual flickering. The control does not repaint until you explicitly resume, so the user sees only the final state.
  2. Performance. Skipping intermediate repaints saves significant time when updating many elements.
  3. UI consistency. The user never sees a half-updated control where some elements reflect new data and others do not.

Method 1: SuspendLayout and ResumeLayout

The simplest built-in approach is SuspendLayout and ResumeLayout. These methods prevent the control from recalculating layout for its children until you are done making changes:

csharp
1panel.SuspendLayout();
2try
3{
4    // Add multiple controls or change their positions
5    for (int i = 0; i < 50; i++)
6    {
7        var label = new Label
8        {
9            Text = $"Item {i}",
10            Location = new System.Drawing.Point(10, i * 25),
11            AutoSize = true
12        };
13        panel.Controls.Add(label);
14    }
15}
16finally
17{
18    panel.ResumeLayout(true); // true = perform layout immediately
19}

SuspendLayout stops layout recalculation but does not fully suppress painting. Controls may still repaint individually as they are added. For full paint suppression, use the WM_SETREDRAW message.

Method 2: WM_SETREDRAW Message (Full Paint Suppression)

Sending the WM_SETREDRAW Windows message to a control completely disables or enables painting. This is the most effective way to eliminate flickering:

csharp
1using System;
2using System.Runtime.InteropServices;
3using System.Windows.Forms;
4
5public static class ControlPaintHelper
6{
7    [DllImport("user32.dll")]
8    private static extern int SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam);
9
10    private const int WM_SETREDRAW = 0x000B;
11
12    public static void SuspendDrawing(Control control)
13    {
14        SendMessage(control.Handle, WM_SETREDRAW, false, 0);
15    }
16
17    public static void ResumeDrawing(Control control)
18    {
19        SendMessage(control.Handle, WM_SETREDRAW, true, 0);
20        control.Refresh();
21    }
22}

Usage:

csharp
1ControlPaintHelper.SuspendDrawing(dataGridView);
2try
3{
4    // Perform bulk updates
5    for (int i = 0; i < 1000; i++)
6    {
7        dataGridView.Rows.Add($"Row {i}", i * 100, DateTime.Now);
8    }
9}
10finally
11{
12    ControlPaintHelper.ResumeDrawing(dataGridView);
13}

The finally block is critical. If an exception occurs during updates and you forget to resume drawing, the control will remain invisible until the application is closed or the message is sent again.

Method 3: BeginUpdate and EndUpdate

Several built-in controls like ListBox, ListView, TreeView, and ComboBox have their own BeginUpdate and EndUpdate methods that suspend painting:

csharp
1listBox.BeginUpdate();
2try
3{
4    listBox.Items.Clear();
5    for (int i = 0; i < 5000; i++)
6    {
7        listBox.Items.Add($"Item {i}");
8    }
9}
10finally
11{
12    listBox.EndUpdate();
13}

These methods are optimized for their specific controls and handle the WM_SETREDRAW message internally. Always prefer BeginUpdate/EndUpdate when the control supports it.

Method 4: Double Buffering

Double buffering draws the control to an off-screen buffer first, then copies the entire buffer to the screen in one operation. This eliminates flickering without needing to suspend and resume painting:

csharp
1public class DoubleBufferedPanel : Panel
2{
3    public DoubleBufferedPanel()
4    {
5        this.DoubleBuffered = true;
6        this.SetStyle(
7            ControlStyles.OptimizedDoubleBuffer
8            | ControlStyles.AllPaintingInWmPaint
9            | ControlStyles.UserPaint,
10            true
11        );
12        this.UpdateStyles();
13    }
14}

You can also enable double buffering on existing controls using reflection:

csharp
1public static void EnableDoubleBuffering(Control control)
2{
3    typeof(Control)
4        .GetProperty("DoubleBuffered",
5            System.Reflection.BindingFlags.NonPublic
6            | System.Reflection.BindingFlags.Instance)
7        ?.SetValue(control, true);
8}

Double buffering reduces flickering during normal painting but does not address the performance cost of layout recalculation during bulk updates. For large batch operations, combine double buffering with SuspendLayout or WM_SETREDRAW.

A Reusable IDisposable Pattern

Wrapping the suspend/resume logic in an IDisposable ensures that painting is always resumed, even if an exception occurs:

csharp
1public class SuspendPaintingScope : IDisposable
2{
3    private readonly Control _control;
4
5    [DllImport("user32.dll")]
6    private static extern int SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam);
7    private const int WM_SETREDRAW = 0x000B;
8
9    public SuspendPaintingScope(Control control)
10    {
11        _control = control;
12        SendMessage(_control.Handle, WM_SETREDRAW, false, 0);
13    }
14
15    public void Dispose()
16    {
17        SendMessage(_control.Handle, WM_SETREDRAW, true, 0);
18        _control.Refresh();
19    }
20}

Usage:

csharp
1using (new SuspendPaintingScope(flowLayoutPanel))
2{
3    flowLayoutPanel.Controls.Clear();
4    foreach (var item in items)
5    {
6        flowLayoutPanel.Controls.Add(CreateItemControl(item));
7    }
8}
9// Painting is automatically resumed here

Notes for Other Frameworks

WPF uses retained-mode rendering, so manual paint suspension is rarely needed. If you do need to batch visual updates, use Dispatcher.BeginInvoke with a lower priority or call InvalidateVisual() only after all changes are complete.

Web development handles this differently. Browsers batch DOM mutations and repaint once per frame. However, reading layout properties (like offsetWidth) between DOM writes forces a synchronous reflow. Avoid interleaving reads and writes. The CSS property will-change and the requestAnimationFrame API help optimize rendering performance.

Common Pitfalls

  • Forgetting to resume drawing. Always wrap suspend/resume in a try-finally or IDisposable. A control left in the suspended state will appear blank.
  • Calling Refresh vs Invalidate. Refresh forces an immediate synchronous repaint. Invalidate queues a repaint for the next message loop iteration. After resuming drawing, Refresh is usually the right choice to guarantee an immediate update.
  • Nesting suspend calls. WM_SETREDRAW is not reference-counted. Sending true once will enable painting regardless of how many false messages were sent. Avoid nesting suspend/resume calls across different methods.
  • Thread safety. UI controls must be updated from the UI thread. Calling SuspendDrawing from a background thread can cause handle-creation errors or deadlocks.
  • Confusing SuspendLayout with SuspendDrawing. SuspendLayout only prevents layout recalculation. It does not stop the control from repainting. For full flicker elimination, use WM_SETREDRAW.

Summary

Suspending painting prevents a control from redrawing during batch updates, eliminating flickering and improving performance. For controls that support it, use BeginUpdate/EndUpdate. For general controls in Windows Forms, send the WM_SETREDRAW message via P/Invoke, and always resume painting in a finally block or IDisposable wrapper. Combine this with SuspendLayout to also skip layout recalculation, and consider enabling double buffering for smooth rendering during normal use. The key rule is to always ensure painting is resumed, regardless of whether the update code succeeds or throws an exception.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.