28

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.

These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.

I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?

On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.

Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144
  • You should also try suspending/resuming redraw operations...see my updated answer. – Adam Robinson May 07 '09 at 15:01
  • You definitely have a performance problem. I don't think that drawing gradients and quarters of circle should be that slow. – DonkeyMaster May 07 '09 at 16:22
  • Well, as I've said the UI library is not the fastest, but we also have lots of GDI+ drawing code to make the button look like we want it to look. It's not just a draw.arc x 4 and paint the surface with a Gradient. I guess we'll have to work on that too… but I was wondering if there was a way to speed it up. If it double buffers, it should display it fast when it "flips", shouldn't it? – Martin Marconcini May 07 '09 at 16:36
  • I am still investigating the issue, will report soon. Thanks for the ideas so far. – Martin Marconcini Jun 16 '09 at 16:26

10 Answers10

13

We've seen this problem too.

One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:

// Note that WM_SetRedraw = 0XB

// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

...

// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • Is that C++? I don't have UsafeSharedNativeMethods… am I missing a reference? (hehehe I sound like vcs compiler) ;) – Martin Marconcini May 07 '09 at 16:29
  • It may be C++, but it's the same concept that I included in the link in my answer. – Adam Robinson May 07 '09 at 16:40
  • 17
    It's not C++, it's a very standard way of naming your PInvoke class. You are supposed to create your own static class and add a PInvoke method called SendMessage. WindowMessages is also obviously a user-defined enum with the values specified correctly from the Win32 API. Finally, Adam, you didn't post this already, you just posted a link, and your attitude is pretty annoying. – Jon Grant Jun 16 '09 at 13:27
13

One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.

Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.

So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.

Hope this helps.

9

I'll approach your problem from a performance angle.

foreach loop that will create labels, add them to a panel (double buffered) and change their properties

If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])

Most of the time, all we do is draw the rounded corners and add gradient to the background

Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.

My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.

Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.

EDIT:

if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over

That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.

As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.

Doublebuffering in a nutshell: It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.

DonkeyMaster
  • 1,302
  • 4
  • 17
  • 36
  • If you want to go that route, I have a few more ideas. (Monday, because the 8th May is a holiday in France) – DonkeyMaster May 07 '09 at 16:37
  • Will think about all this. Tres bien ;) Not a holiday in Spain. I'll tell you some more about what I'm doing, maybe there's LOTS of room for improvement. ;) Thanks. – Martin Marconcini May 07 '09 at 19:16
  • 1
    That tip about creating the whole `Control[]` and setting it once with `AddRange` solved my problem perfectly. Thanks a lot for that, as I don't think I'd have tried that on my own for a good while ;) – julealgon Dec 27 '13 at 18:22
4

In addition to the DoubleBuffered property, also try adding this to your control's constructor:

SetStyle(ControlStyles.OptimizedDoubleBuffer | 
         ControlStyles.AllPaintingInWmPaint, true);

And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.

Community
  • 1
  • 1
Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • We're already doing that and also ControlStyles.UserPaint; I guess we've tried every possible combination. Results are always more or less the same :S – Martin Marconcini May 07 '09 at 14:57
  • I am experiencing the same behaviour that the OP of the other question has "it didn't help ... i also noticed that when popup menus overlay some of the button and force them to redraw, the redraw is particularly slow (eg you see the buttons border drawn, button filled with solid color, finally button draws it's picture, then follows the next button)" Not only is super slower now, but it also has a border… it's weird :S – Martin Marconcini May 07 '09 at 16:27
  • In your custom control drawing code are you redrawing the entire control every time, or just the portion that needs to be? – Adam Robinson May 07 '09 at 16:42
  • 1
    This appears to do the exact same thing as setting the `DoubleBuffered` property according to https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs. – Mike Henry Jul 29 '19 at 17:45
3

You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.

Community
  • 1
  • 1
Simon
  • 25,468
  • 44
  • 152
  • 266
2

Maybe first draw on a control-only 'visible' (private) buffer and then render it:

In your control

BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;

A function to install graphics

private void InstallGFX(bool forceInstall)
{
    if (forceInstall || gfxManager == null)
    {
        gfxManager = BufferedGraphicsManager.Current;
        gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
        gfx = gfxBuffer.Graphics;
    }
}

In its paint method

protected override void OnPaint(PaintEventArgs e)
{
    InstallGFX(false);
    // .. use GFX to draw
    gfxBuffer.Render(e.Graphics);
}

In its resize method

protected override void OnSizeChanged(EventArgs e)
{
    base.OnSizeChanged(e);
    InstallGFX(true); // To reallocate drawing space of new size
}

The code above has been somewhat tested.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).

Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.

There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.

  • I had fun playing with this flag, but in the end I could not work around a bug that would cause a CPU core to spin up to 100% and stay there for any form with open tabs. – Daniel Keogh Sep 28 '17 at 00:12
0

I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.

I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.

I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • Thanks Dennis, I'd love to switch to something like that but we use a custom drawn UI ("unique") that is the trademark of our application. Switching to "just another set of Windows Controls" won't do for us. Our interface is tactile-oriented + TabletPC, so we only use "a few controls" from WinCtl, but almost always doing custom drawing on top of that. – Martin Marconcini Jun 18 '09 at 11:28
  • @Martin, we did the same here. Bad choice, I think. We're looking at upgrading to WPF, but it's a lot of work.... – Benjol Dec 02 '10 at 06:11
  • @benjol yeah, changing this app to WPF is months of work! or more! :S – Martin Marconcini Dec 17 '10 at 15:07
0

I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.

I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace myNameSpace.Forms.UserControls
{
    public class TableLayoutPanelNoFlicker : TableLayoutPanel
    {
        public TableLayoutPanelNoFlicker()
        {
            this.DoubleBuffered = true;
        }
    }
}
Kevin
  • 3,574
  • 10
  • 38
  • 43
0

I have seen bad winforms flicker on forms where the controls referred to a missing font.

This is probably not common, but it's worth looking into if you've tried everything else.

jm.
  • 23,422
  • 22
  • 79
  • 93