views:

3518

answers:

3

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?

+30  A: 

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

ng5000
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
Thanks - super use for extension methods as well :)
ng5000
Great Answer.I didnt know this
NightCoder
+2  A: 

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

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

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

    internal 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.

Ozgur Ozcitak
Thanks, I'm using this in VB.NET and it's sweet! :D
Camilo Martin
A: 

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
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
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