Double buffering throws exception at high load

697 Views Asked by At

I am testing my windows forms user control. When application load is high (say application takes more than 1 GB), it throws Out of memory or Argument invalid exceptions.

Here is the code to draw the control with buffer,

protected override void OnPaint(PaintEventArgs e)
{
      if (m_buffer == null || m_Redraw)
      {
           if (m_buffer != null)
               m_buffer.Dispose();
            m_buffer = new Bitmap(Width, Height);

            using (Graphics g = Graphics.FromImage(m_buffer))
            {
                DrawUserControl(g, ClientRectangle);
            }
            m_Redraw= false;                
       }
       e.Graphics.DrawImage(m_buffer, Point.Empty);
       base.OnPaint(e);
}

Out of memory exception occurs at e.Graphics.DrawImage(m_buffer, Point.Empty);

Argument invalid exception occurs at new Bitmap(Width, Height);

Note: Exception occurs only if application load is more that 1 GB (say 1.5 GB with 2GB RAM).

Painting control without buffering does not throw any exception but causes flickering effect. Here is code without buffering

protected override void OnPaint(PaintEventArgs e)
{
     this.SuspendLayout();
     DrawUserControl(e.Graphics, ClientRectangle);
     this.ResumeLayout();
     base.OnPaint(e);
}

I want my control to render without flickering under high load. I don't want the application to break due to my control. Please share your suggestions

Edit related to buffering:

1) Buffer image will not be created during all paint events. It will be created during the first time and if control needs to be repainted. Otherwise, existing buffer image will be drawn on the control. This avoids unwanted repainting of the control

2) If control needs to be repainted, a new bitmap will be used because size of the existing buffer image and current size of the control may vary. Even if I use existing image, Argument is not valid exception thrown

Tried using BufferedGraphicsContext

As per @taffer's answer, I tried using BufferedGraphicsContext. It also throws Out of memory exception. I'm posting the stack trace below

System.ComponentModel.Win32Exception (0x80004005): Not enough storage is available to process this command at System.Drawing.BufferedGraphicsContext.CreateCompatibleDIB(IntPtr hdc, IntPtr hpal, Int32 ulWidth, Int32 ulHeight, IntPtr& ppvBits)

at System.Drawing.BufferedGraphicsContext.CreateBuffer(IntPtr src, Int32 offsetX, Int32 offsetY, Int32 width, Int32 height)

at System.Drawing.BufferedGraphicsContext.AllocBuffer(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)

at System.Drawing.BufferedGraphicsContext.AllocBufferInTempManager(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle) at System.Drawing.BufferedGraphicsContext.Allocate(Graphics targetGraphics, Rectangle targetRectangle)

1

There are 1 best solutions below

2
György Kőszeg On

Firstly, why do you call SuspendLayout and ResumeLayout in OnPaint? It is required only if a container control (eg. Panel) contains child controls with docking/alignments and you want to prevent automatic re-alignments when the container is resized in more than one steps.

Secondly, there are many better built-in ways for double-buffering.

Option 1:

For Forms, Panels and other usual controls in most cases it is enough to set the DoubleBuffered property. It is protected so you should create a derived class:

public class DoubleBufferedPanel : Panel
{
    public DoubleBufferedPanel()
    {
        DoubleBuffered = true;
    }
}

Option 2:

Call SetStyle:

public class DoubleBufferedPanel : Panel
{
    public DoubleBufferedPanel()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
    }
}

Option 3:

There are some cases when the options above cannot be used (for example, fading animations of Windows cannot be used with double buffering). Here you can use the BufferedGraphics class in OnPaint:

protected override void OnPaint(PaintEventArgs e)
{
    // creating a buffered context
    using (BufferedGraphicsContext context = new BufferedGraphicsContext())
    {
        // creating a buffer for the original Graphics
        Rectangle rect = new Rectangle(Point.Empty, Control.ClientSize);
        using (BufferedGraphics bg = context.Allocate(e.Graphics, rect))
        {
            using (PaintEventArgs be = new PaintEventArgs(bg.Graphics, e.ClipRectangle))
            {
                // Draw your control here onto the buffer (applied to your post)
                DrawUserControl(be.Graphics, rect);
                base.OnPaint(be);
            }

            // copying the buffer onto the original Graphics
            bg.Render(e.Graphics);
        }
    }
}