Switch On The Code RSS Button - Click to Subscribe
Apr
25

WinForms Tutorial - Manage Your Own Double Buffering

Double buffering in WinForms, especially .NET 2.0 and up, is really nice. It gets rid of almost all flicker on your control, and all you have to do to turn it on is flip a simple property (you set the property DoubleBuffered to true. However, by making it so easy, .NET took away a lot of control over the double buffering process - and so in a select few instances, it is better to manage the double buffering of your control yourself. Today, I am going to walk through how to do that.

You might be wondering "what is double buffering?" It is a technique that reduces flicking and tearing cause by drawing while the display is updating. You end up with half drawn pieces flickering on the screen for fractions of a second before the full drawing is complete. In double buffering, you do all your drawing to a buffer, and then once it is all done, that buffer is drawn to the screen in a single fell swoop - so there is no point in time when the screen contains a half drawn scene.

Looking at that explanation, your probably wondering where the need for more control is. It is a pretty straightforward concept, and there doesn't seem to be much that you could change about the process. You'd be right. The control I'm talking about is more of a 'control over how/when .NET paints' then over the double buffering process itself. For example, say you had a custom drawn control, and the drawing operation was really expensive (like maybe a plot, and you had to calculate the pixel position of all the data points every time the plot gets drawn). The data behind the plot probably doesn't change all that much - in fact, it is asked to paint by Windows a lot more often than the actual display is changing. This is no fault of windows - its just how painting works. If a portion of a control is obscured, and then it becomes unobscured, it will be asked to repaint, because Windows doesn't remember what the control used to look like.

But painting in our case is expensive! We don't want to do those calculations more often then we absolutely have to. So we use double buffering, in a slightly twisted fashion. We keep a buffer around, and this buffer is what we actually paint to. Most of the time the buffer stays the same, and when Windows asks our control to paint, we just render the buffer to the screen. We keep a flag for when our underlying data actually does change, and when it does, we repaint the buffer. So we get the benefits of double buffering, and we only do the hard work when we really need to.

We are going to make a small app today that does exactly this. Its drawing routines are not complicated at all, so in reality it doesn't need to use this technique, but its nice and simple and easy to demo. Below you can see a screen shot. If you run the app, you can grab and drag around the text on the screen.


Ok, lets delve into the code. First I'm going to give you a framework of the class, and then we will add code to each method.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace BufferedGraphicsTest
{
  public class BufferedControl : UserControl
  {
    private bool _Dirty;
    private BufferedGraphicsContext _BufferContext;
    private BufferedGraphics _Buffer;

    public BufferedControl()
    { }

    private void SizeGraphicsBuffer()
    { }

    public bool Dirty
    { }

    protected override void OnPaintBackground(
      PaintEventArgs pevent)
    { }

    protected override void OnSizeChanged(EventArgs e)
    { }

    protected override void OnPaint(PaintEventArgs e)
    { }

    public virtual void Draw(Graphics graphics)
    { }

    protected override void Dispose(bool disposing)
    { }


    /* Code below here is just for the ability to
     * drag around the string.
     */


    private const string StrToDraw = "{ } Switch On The Code";

    private Rectangle _CurrentTextRectangle =
      new Rectangle(10, 10, 0, 0);
    private Font _TextFont = new Font("Tahoma", 12);
    private Point _OrigMousePoint = Point.Empty;
    private Point _OrigTextPoint = Point.Empty;
    private bool _Moving = false;

    protected override void OnMouseDown(MouseEventArgs e)
    { }

    protected override void OnMouseUp(MouseEventArgs e)
    { }

    protected override void OnMouseMove(MouseEventArgs e)
    { }

  }
}

Doesn't look to bad yet, eh? Those three private variables at the top are probably the most important part of the code so far. The boolean _Dirty will hold the flag that says if our buffer needs to be redrawn. The _BufferContext (which is a Buffered Graphics Context) is what we use to create our buffer, which is held in _Buffer (a Buffered Graphics object).

Ok, next up, the constructor:

public BufferedControl()
{
  _BufferContext = new BufferedGraphicsContext();
  SizeGraphicsBuffer();
  SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
  SetStyle(ControlStyles.DoubleBuffer, false);
}

We create our BufferedGraphicsContext here, and then size the graphics buffer itself (we will look at that function next). One thing that you need to remember to do when managing your own double buffering is make sure that the .NET double buffering is turned off - and you can do that through those two SetStyle calls.

private void SizeGraphicsBuffer()
{
  if (_Buffer != null)
  {
    _Buffer.Dispose();
    _Buffer = null;
  }

  if (_BufferContext == null)
    return;

  if (DisplayRectangle.Width <= 0)
    return;
       
  if (DisplayRectangle.Height <= 0)
    return;

  using (Graphics graphics = CreateGraphics())
    _Buffer = _BufferContext.Allocate(graphics,
      DisplayRectangle);

  Dirty = true;
}

Ok, there is our SizeGraphicsBuffer function. First off, if there was a buffer previous, we get rid of it (it is now probably the wrong size). If there is no BufferedGraphicsContext of if the size of the actual paintable area of the control is zero, we don't bother creating a new buffer. Otherwise, we create the Graphics object for the control, and call the Allocate function on the _BufferContext (using that graphics object, and the display rectangle). This will give us a buffer back of the right size. Finally we set Dirty to true, to cause a repaint - and we will see what that does next.

public bool Dirty
{
  get { return _Dirty; }
  set
  {
    if (!value)
      return;

    _Dirty = true;
    Invalidate();
  }
}

Pretty simple. If the value being set is true, we set _Dirty to true and invalidate the control (which means that the paint function will be called at some point in the future). We don't accept a false value here, it doesn't make sense to set _Dirty back to false through this function.

Next up, two short functions:

protected override void OnPaintBackground(
  PaintEventArgs pevent)
{ /* Do Nothing */ }

protected override void OnSizeChanged(EventArgs e)
{
  SizeGraphicsBuffer();
  base.OnSizeChanged(e);
}

We override OnPaintBackground and do nothing. This is because we don't want the normal OnPaintBackground code being called. We are doing all the work ourselves, and we don't want this to interfere. For OnSizeChanged, we do what you might expect - we resize the graphics buffer.

Now for the OnPaint function:

protected override void OnPaint(PaintEventArgs e)
{
  if (_Buffer == null)
  {
    Draw(e.Graphics);
    return;
  }

  if (_Dirty)
  {
    _Dirty = false;
    Draw(_Buffer.Graphics);
  }

  _Buffer.Render(e.Graphics);
}

First off, if the buffer doesn't exist, we don't want to fail. So instead, we paint straight to the screen, and then return out. Otherwise, if the _Dirty flag is set, we unset it, and draw to our buffer. And finally, we render our buffer to the screen. Actually pretty simple.

And in the Draw function, we do the actual drawing:

public virtual void Draw(Graphics graphics)
{
  if (ClientRectangle.Width <= 0)
    return;

  if (ClientRectangle.Height <= 0)
    return;

  using(SolidBrush backBrush = new SolidBrush(BackColor))
    graphics.FillRectangle(backBrush, ClientRectangle);
     
  if(_CurrentTextRectangle.Size == Size.Empty)
  {
    SizeF sf = graphics.MeasureString(StrToDraw,
      _TextFont);

    _CurrentTextRectangle.Width =
      (int)Math.Ceiling(sf.Width);

    _CurrentTextRectangle.Height =
      (int)Math.Ceiling(sf.Height);
  }

  graphics.DrawString("{ } Switch On The Code", _TextFont,
    Brushes.Green, _CurrentTextRectangle.Location);
}

And there shouldn't be anything surprising there - it is exactly what you would find in a normal paint function. The reason it is separated out from the OnPaint function is so that it can be called with either the graphics object of the control or of the buffer.

And last, but not least, the Dispose function:

protected override void Dispose(bool disposing)
{
  if (disposing)
  {
    if (_Buffer != null)
    {
      _Buffer.Dispose();
      _Buffer = null;
    }

    if (_BufferContext != null)
    {
      _BufferContext.Dispose();
      _BufferContext = null;
    }

    if (_TextFont != null)
    {
      _TextFont.Dispose();
      _TextFont = null;
    }
  }

  base.Dispose(disposing);
}

Your extremely standard, really boring, dispose function. Both the _Buffer and the _BufferContext are objects that need to be disposed, so we can't forget about them. And in this case, I have a font that I keep around for the text I'm painting, and I have to get rid of that as well.

That is it for the main double buffering code! What is left is the code that makes the text move around on the screen:

protected override void OnMouseDown(MouseEventArgs e)
{
  base.OnMouseDown(e);

  if (e.Button != MouseButtons.Left)
    return;

  _Moving = false;
  if (!_CurrentTextRectangle.Contains(e.Location))
    return;

  _OrigMousePoint = e.Location;
  _OrigTextPoint = _CurrentTextRectangle.Location;
  _Moving = true;
}

protected override void OnMouseUp(MouseEventArgs e)
{
  base.OnMouseUp(e);

  if (e.Button != MouseButtons.Left)
    return;

  _Moving = false;
}

protected override void OnMouseMove(MouseEventArgs e)
{
  base.OnMouseMove(e);

  if (!_Moving)
  {
    Cursor = _CurrentTextRectangle.Contains(e.Location) ?
      Cursors.Hand : Cursors.Default;
    return;
  }

  _CurrentTextRectangle.Location = new Point(
    _OrigTextPoint.X + (e.X - _OrigMousePoint.X),
    _OrigTextPoint.Y + (e.Y - _OrigMousePoint.Y));
  Dirty = true;
}

I'm not going to bother explaining these functions, since all they do is make the example more fun. Except for one point. See at the bottom of the OnMouseMove function, where Dirty gets set to true? That is because the underlying data has just changed. You have to always remember to set Dirty to true when data changes, or else it will never actually get updated on the screen!

Now that we have all this code in parts, lets throw it all together:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace BufferedGraphicsTest
{
  public class BufferedControl : UserControl
  {
    private bool _Dirty;
    private BufferedGraphicsContext _BufferContext;
    private BufferedGraphics _Buffer;
     
    public BufferedControl()
    {
      _BufferContext = new BufferedGraphicsContext();
      SizeGraphicsBuffer();
      SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
      SetStyle(ControlStyles.DoubleBuffer, false);
    }

    private void SizeGraphicsBuffer()
    {
      if (_Buffer != null)
      {
        _Buffer.Dispose();
        _Buffer = null;
      }

      if (_BufferContext == null)
        return;

      if (DisplayRectangle.Width <= 0)
        return;
       
      if (DisplayRectangle.Height <= 0)
        return;

      using (Graphics graphics = CreateGraphics())
        _Buffer = _BufferContext.Allocate(graphics,
          DisplayRectangle);

      Dirty = true;
    }

    public bool Dirty
    {
      get { return _Dirty; }
      set
      {
        if (!value)
          return;

        _Dirty = true;
        Invalidate();
      }
    }

    protected override void OnPaintBackground(
      PaintEventArgs pevent)
    { /* Do Nothing */ }

    protected override void OnSizeChanged(EventArgs e)
    {
      SizeGraphicsBuffer();
      base.OnSizeChanged(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
      if (_Buffer == null)
      {
        Draw(e.Graphics);
        return;
      }

      if (_Dirty)
      {
        _Dirty = false;
        Draw(_Buffer.Graphics);
      }

      _Buffer.Render(e.Graphics);
    }

    public virtual void Draw(Graphics graphics)
    {
      if (ClientRectangle.Width <= 0)
        return;

      if (ClientRectangle.Height <= 0)
        return;

      using(SolidBrush backBrush = new SolidBrush(BackColor))
        graphics.FillRectangle(backBrush, ClientRectangle);
     
      if(_CurrentTextRectangle.Size == Size.Empty)
      {
        SizeF sf = graphics.MeasureString(StrToDraw,
          _TextFont);

        _CurrentTextRectangle.Width =
          (int)Math.Ceiling(sf.Width);

        _CurrentTextRectangle.Height =
          (int)Math.Ceiling(sf.Height);
      }

      graphics.DrawString("{ } Switch On The Code", _TextFont,
        Brushes.Green, _CurrentTextRectangle.Location);
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing)
      {
        if (_Buffer != null)
        {
          _Buffer.Dispose();
          _Buffer = null;
        }

        if (_BufferContext != null)
        {
          _BufferContext.Dispose();
          _BufferContext = null;
        }

        if (_TextFont != null)
        {
          _TextFont.Dispose();
          _TextFont = null;
        }
      }

      base.Dispose(disposing);
    }

    /* Code below here is just for the ability to
     * drag around the string.
     */


    private const string StrToDraw = "{ } Switch On The Code";

    private Rectangle _CurrentTextRectangle =
      new Rectangle(10, 10, 0, 0);
    private Font _TextFont = new Font("Tahoma", 12);
    private Point _OrigMousePoint = Point.Empty;
    private Point _OrigTextPoint = Point.Empty;
    private bool _Moving = false;

    protected override void OnMouseDown(MouseEventArgs e)
    {
      base.OnMouseDown(e);

      if (e.Button != MouseButtons.Left)
        return;

      _Moving = false;
      if (!_CurrentTextRectangle.Contains(e.Location))
        return;

      _OrigMousePoint = e.Location;
      _OrigTextPoint = _CurrentTextRectangle.Location;
      _Moving = true;
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
      base.OnMouseUp(e);

      if (e.Button != MouseButtons.Left)
        return;

      _Moving = false;
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);

      if (!_Moving)
      {
        Cursor = _CurrentTextRectangle.Contains(e.Location) ?
          Cursors.Hand : Cursors.Default;
        return;
      }

      _CurrentTextRectangle.Location = new Point(
        _OrigTextPoint.X + (e.X - _OrigMousePoint.X),
        _OrigTextPoint.Y + (e.Y - _OrigMousePoint.Y));
      Dirty = true;
    }
  }
}

And there you go! Your own manually double buffered control. You can download the Visual Studio solution (and all the code) here. And as always, if you have any questions or comments, feel free to leave them below.

References




Posted in C#, All Tutorials by The Tallest |

6 Responses

  1. Josh Says:

    Hi there, interesting article. This seems pretty straightforward if you want to draw everything in the control yourself, but would this technique also help if you wanted to add double buffering support to built in WinForms controls that don’t already have it (for example, the native WinForms TrackBar control does not support double buffering)?

    I think I’m almost there in understanding how to do such a thing, but can’t quite make the final leap! What I’d be keen to do is create a wrapper class that let’s me add double buffering support to controls (custom or built in) that don’t already have it - is such a thing possible/practical?

  2. Johnny Says:

    Nice article.

    I need to double buffer the entire control collection rendering. I use my own transparent image controls on a form (the controls don’t paint a background). These images change frequently so the entire control collection needs to be redrawn often. This causes severe flicker as each control is redrawn in the form.

    How do I double buffer the rendering of all the form controls so that I don’t actually see each control being rendered in turn? I basically need each control to be rendered to an in memory graphics buffer, and then when done, render the graphics buffer.

    Thanks,
    Johnny

  3. The Tallest Says:

    Hmm…I’m not sure I can see an easy way around your problem. It is really hard to sync the painting of controls. Because even if you rendered each control to a single buffer, each control would still need to do the painting of itself on the screen from the contents of the buffer (because a parent control can’t draw on top of a child).

    You could try the Refresh method - which forces an immediate repaint (unlike Invalidate, which essentially just asks nicely for a repaint).

    Really, I try and avoid needing to use transparent controls - this particular problem has bitten me before, and there doesn’t seem to be a good way around it.

  4. Johnny Says:

    After more research, it looks like there is no way to do it. I would need to just use a single control and draw everything to the 1 control and implement my own OnClick and other events within the control when the user moves there mouse over a particular image.

    Thanks for the reply,
    Johnny

  5. Alessandro Says:

    Thank you for the really good article.
    I also have Johnny’s problem: I need to double buffer the graphics of a control (with its children) and some other objects that will be paint all over the form (possibly also over the control).
    I’ll really appreciate any help.
    Thank you.

  6. mmprogramming Says:

    I tried to use bufferedgraphics but it doesn’t do anything. The pain function does get called but after executing the render function, there’s still nothing in the picturebox. IT looks simple to implement but i can’t seem to get it to work for me.
    Please advise. Below is my test code. Thanks.

    public Form1()
    {
    InitializeComponent();

    //automatic double buffering
    //this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

    currentContext = BufferedGraphicsManager.Current;
    myBuffer = currentContext.Allocate(this.pictureBox1.CreateGraphics(),
    this.pictureBox1.DisplayRectangle);

    bmp = new Bitmap( “D:/SurveillanceSystem/Mimos/PnZ/PnZ/Images/langkawi.jpg” );

    // this.pictureBox1.Image = bmp;

    }

    private void pictureBox1_Paint(object sender, EventArgs e)
    {
    if (bmp != null)
    {
    myBuffer.Graphics.Clear(Color.White);
    myBuffer.Graphics.DrawImage(bmp, this.pictureBox1.DisplayRectangle, this.pictureBox1.DisplayRectangle, GraphicsUnit.Pixel);
    myBuffer.Render();
    }
    }

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Powered by WP Hashcash