193

I have a control which I have to make large modifications to. I'd like to completely prevent it from redrawing while I do that - SuspendLayout and ResumeLayout aren't enough. How do I suspend painting for a control and its children?

Simon
  • 25,468
  • 44
  • 152
  • 266
  • 3
    can someone please explain me what is drawing or painting here in this context? (i am new to .net) atleast provide a link. – Mr_Green Oct 18 '12 at 16:56
  • 1
    Its really a shame (or laughable) that .Net has been out for over 15 years, and this is still a problem. If Microsoft spent as much time on fixing real problems like the screen flicker as say, [Get Windows X](https://www.google.com/search?q=get+windows+x+malware) malware, then this would have been fixed a long time ago. – jww Feb 15 '17 at 22:10
  • @jww They did fix it; it's called WPF. – philu Jun 22 '20 at 07:01
  • My best solution was to simply set control.Visible=false; then true; once I was done modifying my control. I tried the WM_SETREDRAW, SuspendLayout()...etc but nothing decreased the time it took(wm_setredraw did prevent flickering (but so does setting Visible=false). My main goal was not to reduce flicker, mine was to decrease the time it took to add tons of stuff to the TableLayoutPanel i had. Even with redraw disabled it took 60% the original time, Visible=false dropped this to 10% the original time and got rid of flicker. – diox8tony Aug 25 '22 at 16:32

10 Answers10

332

At my previous job, we struggled with getting our rich UI app to paint instantly and smoothly. We were using standard .Net controls, custom controls and devexpress controls.

After a lot of googling and reflector usage, I came across the WM_SETREDRAW win32 message. This really stops controls drawing whilst you update them and can be applied, IIRC to the parent/containing panel.

This is a very very simple class demonstrating how to use this message:

class DrawingControl
{
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);

    private const int WM_SETREDRAW = 11; 
    
    public static void SuspendDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
    }

    public static void ResumeDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
        parent.Refresh();
    }
}

There are fuller discussions on this - google for C# and WM_SETREDRAW, e.g.

C# Jitter

Suspending Layouts

And to whom it may concern, this is a similar example in VB:

Public Module Extensions
    <DllImport("user32.dll")>
    Private Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
    End Function

    Private Const WM_SETREDRAW As Integer = 11

    ' Extension methods for Control
    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, True, IntPtr.Zero)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub

    <Extension()>
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, IntPtr.Zero)
    End Sub

    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module
ng5000
  • 12,330
  • 10
  • 51
  • 64
  • 49
    What a great answer and a tremendous help! I made SuspendDrawing and ResumeDrawing extension methods for the Control class, so I can call them for any control in any context. – Zach Johnson Mar 27 '09 at 20:56
  • 2
    Had a tree-like control that would only refresh a node properly on rearranging its children if you collapsed then expanded it, which led to ugly flickering. This worked perfectly to get around it. Thanks! (Amusingly enough, the control already imported SendMessage, *and* defined WM_SETREDRAW, but didn't actually use it for anything. Now it does.) – neminem Jun 01 '11 at 22:22
  • 7
    This isn't particularly useful. This is exactly what the `Control` base class for *all* of the WinForms controls *already does* for the `BeginUpdate` and `EndUpdate` methods. Sending the message yourself is no better than using those methods to do the heavy lifting for you, and certainly can't produce different results. – Cody Gray - on strike Jun 04 '11 at 02:05
  • 13
    @Cody Gray -- TableLayoutPanels don't have BeginUpdate, for example. – TheBlastOne Jun 07 '11 at 11:52
  • 1
    Should I enclose existing SuspendLayout and ResumeLayout with SuspendDrawing and ResumeDrawing, or should I remove them ? – Larry Sep 05 '12 at 10:15
  • Very useful code, though, I am getting a black flickering at the end. Is there something I am doing wrong or there is something more to do here? Not an expert. – Indigo Jul 01 '13 at 11:14
  • 1
    @CodyGray Your comment was a good one - it exposed the typical inconsistencies in .NET. Forms and tabs don't have Being/EndUpdate either. If I wanted to muck around with all sorts of things on a window, why can't I just stop painting the whole thing? – darda May 04 '14 at 16:52
  • 2
    @pelesl Why not? The law of leaky abstractions. WinForms is a wrapper around the Win32 API, which makes perfect sense if you understand how it was designed and how it works. But it doesn't make a lot of sense if you try to understand it through the WinForms wrapper. If there were a Begin/EndUpdate method on the Form class, it wouldn't do what you would expect. It would only stop the form itself from updating, it would have no effect on child controls hosted by that form. In Win32, everything is a window. And all windows are responsible for their own drawing. – Cody Gray - on strike May 05 '14 at 07:10
  • 6
    Be careful if your code makes it possible to call these methods before the control has been displayed - the call to `Control.Handle` will force the window handle to be created and could affect performance. For example if you were moving a control on a form before it was displayed, if you call this `SuspendDrawing` beforehand, your move will be slower. Probably should have `if (!parent.IsHandleCreated) return` checks in both methods. – oatsoda Aug 15 '14 at 13:54
  • 1
    @CodyGray Please remove your +6 comment as it is misleading. `Control` only has `BeginUpdateInternal` and as the name betrays, it is in fact `internal`. You are dependent on the Windows Forms framework to expose it from different controls, and it actually only does so on **very** few. The fact this answer implements it the same "official" way is all the better. – MarioDS Feb 07 '18 at 10:40
  • Great, I inherited a poorly written WinForms app that does multiple refreshes across two forms during a single action. Complex to fix without re-writing much of it but though it's still slow at least with this I can make it stop flickering. – Steve Crane Apr 09 '18 at 13:38
  • Worked perfectly for me when I tried to resize/move controls on my form. Why doesn't Microsoft's `SuspendLayout` and `ResumeLayout` do what yours achieve? What did they make theirs seemingly useless, while you made yours functional? – Dan W Nov 12 '22 at 10:37
53

The following is the same solution of ng5000 but doesn't use P/Invoke.

public static class SuspendUpdate
{
    private const int WM_SETREDRAW = 0x000B;

    public static void Suspend(Control control)
    {
        Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgSuspendUpdate);
    }

    public static void Resume(Control control)
    {
        // Create a C "true" boolean as an IntPtr
        IntPtr wparam = new IntPtr(1);
        Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgResumeUpdate);

        control.Invalidate();
    }
}
ceztko
  • 14,736
  • 5
  • 58
  • 73
  • I've tried it, but in intermediate stage between Suspend and Resume, it's just invalidate. It may sound weird, but can it just hold its state before suspend in the transient state? – user Sep 17 '12 at 09:30
  • 2
    Suspend a control means that no drawing will be performed at all in the control area and you may get leftovers drawn from other windows/controls in that surface. You may try to use a control with DoubleBuffer set to true if you want the previous "state" to be drawn until the control is resumed (if I understood what you meant), but I can't guarantee that it will work. Anyway I think you are missing the point of using this technique: it's meant to avoid the user seeing a progressive and slow drawing of objects (better to see all of them appearing together). For other needs, use other techniques. – ceztko Sep 25 '12 at 17:16
  • 6
    Nice answer, but it would be much better to show where `Message` and `NativeWindow` are; searching documentation for a class named `Message` is not really all that entertaining. – darda May 04 '14 at 17:04
  • I call these functions on a form or tab page full of text boxes and the text boxes don't get painted after the call to `control.Invalidate()`. These calls seem to have some unintended consequences, because the `GotFocus` events are not firing in those text boxes if the control containing them is passed to your `Suspend` function. – darda May 04 '14 at 17:14
  • 1
    @pelesl 1) use automatic namespace import (click on symbol, ALT+Shift+F10), 2) children not being painted by Invalidate() is the expected behavior. You should suspend the parent control only for a short time span and resume it when all the relevant children are invalidated. – ceztko May 04 '14 at 20:55
  • 3
    `Invalidate()` does not work as good as `Refresh()` unless followed by one anyway. – Eugene Ryabtsev Dec 15 '15 at 11:35
  • Note to anyone coming across this in future, according to the documentation of NativeWindow.DefWndProc, this usage is incorrect, SendMessage should be used instead. – Geoff Apr 20 '19 at 22:37
  • @Geoff, we all read that documentation and we all know that we should normally use SendMessage instead. But SendMessage is not properly wrapped in .NET framework and, if I remember correctly, this is one of the cases where DefWndProc is safe to use for this need. Please, verify in the reference source code your claims because WinForms is an API that was deliberately left incomplete for advanced use cases so it's quite normal to came up with tricks that looks unsupported in the documentation but are pretty correct in reality – ceztko Apr 20 '19 at 22:50
  • I know WinForms is far from perfect (thus WPF and so forth, heh). Do you have anything to read which demonstrates your point? Saying to check the reference is all well and good, but without saying where results in your reply not being as helpful as it otherwise could be. Thanks! – Geoff Apr 20 '19 at 22:59
  • @Geoff good point. At that time when I answered it was about decompiling, now we have reference source. If you find something in it that makes this code unsafe, please point me and I will clearly state it in the answer. – ceztko Apr 20 '19 at 23:03
  • 1
    @ceztko: This isn't a case where the documentation is incomplete... it specifically says to not do it. "DefWndProc should not be called to send a window message to the window; call the Win32 SendMessage function instead." – Ben Voigt Sep 07 '22 at 19:19
  • @BenVoigt:the point was providing an answer that doesn't require P/Invoke, since `SendMessage` is not wrapped. Since a lot of time since I wrote this, I leave this comment for the future, if I have time to check it before editing the answer: the answer I supplied should be safe when performed on the creation affine thread (eg. MainThread). It's not when peformed from a different thread. In that case, an Invoke call is required. – ceztko Sep 07 '22 at 19:53
  • 1
    @ceztko: Whether it works or not is not a simple matter of the thread; it also matters whether the window class processes that message, or the window has a subclass procedure that does. Calling `DefWindowProc` bypasses both of those and can't be fixed by a cross-thread Invoke. – Ben Voigt Sep 07 '22 at 20:48
19

I usually use a little modified version of ngLink's answer.

public class MyControl : Control
{
    private int suspendCounter = 0;

    private void SuspendDrawing()
    {
        if(suspendCounter == 0) 
            SendMessage(this.Handle, WM_SETREDRAW, false, 0);
        suspendCounter++;
    }

    private void ResumeDrawing()
    {
        suspendCounter--; 
        if(suspendCounter == 0) 
        {
            SendMessage(this.Handle, WM_SETREDRAW, true, 0);
            this.Refresh();
        }
    }
}

This allows suspend/resume calls to be nested. You must make sure to match each SuspendDrawing with a ResumeDrawing. Hence, it wouldn't probably be a good idea to make them public.

Community
  • 1
  • 1
Ozgur Ozcitak
  • 10,409
  • 8
  • 46
  • 56
  • 4
    This helps to keep both calls balanced: `SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }`. Another option is to implement this in a `IDisposable` class and to enclose the drawing part in a `using`-statement. The handle would be passed to the constructor, which would suspend drawing. – Olivier Jacot-Descombes Jul 04 '14 at 20:32
  • Old question, I know, but sending "false" does not appear to work in recent versions of c#/VS. I had to change false to 0 and true to 1. – Maury Markowitz Sep 21 '15 at 20:20
  • @MauryMarkowitz does your `DllImport` declare `wParam` as `bool`? – Ozgur Ozcitak Sep 22 '15 at 07:21
  • Hi, downvoting this answer was an accident, sorry! – Geoff Apr 20 '19 at 22:53
14

To help with not forgetting to reenable drawing:

public static void SuspendDrawing(Control control, Action action)
{
    SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    action();
    SendMessage(control.Handle, WM_SETREDRAW, true, 0);
    control.Refresh();
}

usage:

SuspendDrawing(myControl, () =>
{
    somemethod();
});
Jonathan H
  • 138
  • 1
  • 5
10

Based on ng5000's answer, I like using this extension:

        #region Suspend
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
        private const int WM_SETREDRAW = 11;
        public static IDisposable BeginSuspendlock(this Control ctrl)
        {
            return new suspender(ctrl);
        }
        private class suspender : IDisposable
        {
            private Control _ctrl;
            public suspender(Control ctrl)
            {
                this._ctrl = ctrl;
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, false, 0);
            }
            public void Dispose()
            {
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, true, 0);
                this._ctrl.Refresh();
            }
        }
        #endregion

Use:

using (this.BeginSuspendlock())
{
    //update GUI
}
Koray
  • 1,768
  • 1
  • 27
  • 37
9

A nice solution without using interop:

As always, simply enable DoubleBuffered=true on your CustomControl. Then, if you have any containers like FlowLayoutPanel or TableLayoutPanel, derive a class from each of these types and in the constructors, enable double buffering. Now, simply use your derived Containers instead of the Windows.Forms Containers.

class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
    public TableLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}
Eugenio De Hoyos
  • 1,755
  • 16
  • 22
  • 2
    That's certainly a useful technique - one which I use quite often for ListViews - but it doesn't actually prevent the redraws from happening; they still happen offscreen. – Simon Mar 11 '10 at 09:36
  • 4
    You are right, it solves the flickering problem, not the off-screen redrawing problem specifically. When I was looking for a solution to the flickering, I came accross several related threads like this one, and when I found it, I might not have posted it in the most relevant thread. However, when most people want to suspend painting, they are probably referring to painting on-screen, which is often a more obvious problem than redundant off-screen painting, so I still think that other viewers might find this solution helpful in this thread. – Eugenio De Hoyos Mar 11 '10 at 19:19
  • Override OnPaint. –  Mar 05 '20 at 04:01
4

Here is a combination of ceztko's and ng5000's to bring a VB extensions version that doesn't use pinvoke

Imports System.Runtime.CompilerServices

Module ControlExtensions

Dim WM_SETREDRAW As Integer = 11

''' <summary>
''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SuspendPaint(ByVal ctrl As Windows.Forms.Control)

    Dim msgSuspendUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, System.IntPtr.Zero, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgSuspendUpdate)

End Sub

''' <summary>
''' Resume from SuspendPaint method
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub ResumePaint(ByVal ctrl As Windows.Forms.Control)

    Dim wparam As New System.IntPtr(1)
    Dim msgResumeUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, wparam, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgResumeUpdate)

    ctrl.Invalidate()

End Sub

End Module
goughy000
  • 668
  • 4
  • 13
  • 4
    I am working w/a WPF app that uses winforms intermixed with the wpf forms, and dealing with screen flicker. I am confused in how this code should be leveraged - would this go in the winform or wpf window? Or is this not suited for my particular situation? – Shawn J. Molloy Dec 18 '12 at 01:29
3

I know this is an old question, already answered, but here is my take on this; I refactored the suspension of updates into an IDisposable - that way I can enclose the statements I want to run in a using statement.

class SuspendDrawingUpdate : IDisposable
{
    private const int WM_SETREDRAW = 0x000B;
    private readonly Control _control;
    private readonly NativeWindow _window;

    public SuspendDrawingUpdate(Control control)
    {
        _control = control;

        var msgSuspendUpdate = Message.Create(_control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

        _window = NativeWindow.FromHandle(_control.Handle);
        _window.DefWndProc(ref msgSuspendUpdate);
    }

    public void Dispose()
    {
        var wparam = new IntPtr(1);  // Create a C "true" boolean as an IntPtr
        var msgResumeUpdate = Message.Create(_control.Handle, WM_SETREDRAW, wparam, IntPtr.Zero);

        _window.DefWndProc(ref msgResumeUpdate);

        _control.Invalidate();
    }
}
Scott Baker
  • 10,013
  • 17
  • 56
  • 102
2

This is even simpler, and perhaps hacky - as I can see a lot of GDI muscle on this thread, and is obviously only a good fit for certain scenarios. YMMV

In my scenario, I use what I'll refer to as a "Parent" UserControl - and during the Load event, I simply remove the control-to-be-manipulated from the Parent's .Controls collection, and the Parent's OnPaint takes care of completely painting the child control in whatever special way.. fully taking the child's paint capabilities offline.

Now, I hand off my child paint routine to an extension method based off this concept from Mike Gold for printing windows forms.

Here I'm needing a sub-set of labels to render perpendicular to the layout:

simple diagram of your Visual Studio IDE

Then, I exempt the child control from being painted, with this code in the ParentUserControl.Load event handler:

Private Sub ParentUserControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    SetStyle(ControlStyles.UserPaint, True)
    SetStyle(ControlStyles.AllPaintingInWmPaint, True)

    'exempt this control from standard painting: 
    Me.Controls.Remove(Me.HostedControlToBeRotated) 
End Sub

Then, in the same ParentUserControl, we paint the control-to-be-manipulated from the ground up:

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    'here, we will custom paint the HostedControlToBeRotated instance...

    'twist rendering mode 90 counter clockwise, and shift rendering over to right-most end 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.TranslateTransform(Me.Width - Me.HostedControlToBeRotated.Height, Me.Height)
    e.Graphics.RotateTransform(-90)
    MyCompany.Forms.CustomGDI.DrawControlAndChildren(Me.HostedControlToBeRotated, e.Graphics)

    e.Graphics.ResetTransform()
    e.Graphics.Dispose()

    GC.Collect()
End Sub

Once you host the ParentUserControl somewhere, e.g. a Windows Form - I'm finding that my Visual Studio 2015 renders the form correctly at Design Time as well as runtime: ParentUserControl hosted in a Windows Form or perhaps other user control

Now, since my particular manipulation rotates the child control 90 degrees, I'm sure all the hot spots and interactivity has been destroyed in that region - but, the problem I was solving was all for a package label that needed to preview and print, which worked out fine for me.

If there are ways to reintroduce the hot spots and control-ness to my purposely orphaned control - I'd love to learn about that someday (not for this scenario, of course, but.. just to learn). Of course, WPF supports such craziness OOTB.. but.. hey.. WinForms is so much fun still, amiright?

Community
  • 1
  • 1
bkwdesign
  • 1,953
  • 2
  • 28
  • 50
-4

Or just use Control.SuspendLayout() and Control.ResumeLayout().

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
JustJoost
  • 5
  • 2
  • 10
    Layout and Painting are two different thing: layout is how child controls are arranged in their container. – Larry Sep 22 '13 at 16:09