7

I have a panel that I've subclassed to and have set DoubleBuffered true, I constantly need to refresh the drawing but it flickers and have no idea why.

private delegate void MyDelegate();

public void heartBeat()
    {
        while (true)
        {
            if (map.processNubots(rules))
            {
                if (this.InvokeRequired)
                {
                    this.Invoke((MyDelegate)delegate
                    {
                        //drawPanel.SuspendLayout();
                        drawPanel.Refresh();
                        displayGrid();
                        //drawPanel.ResumeLayout();
                    });
                }
                Thread.Sleep(500);
            }
            else
            {
                break;
            }
        }
    }

    public void displayGrid()
    {
        int i = 0;
        foreach (DictionaryEntry pair in map)
        {
            Monomer current = (Monomer)pair.Value;
            drawMonomers(current.getLocation(), current.getState());
            i++;
        }
    }

    public void drawMonomers(Point location, string state)
    {
        ...

        SolidBrush sb = new SolidBrush(mycolor);
        SolidBrush sbt = new SolidBrush(Color.Black);
        Graphics g = drawPanel.CreateGraphics();
        Font text = new Font("Arial", scale / 2);
        Pen pen = new Pen(Color.Black, 1);
        pen.Alignment = PenAlignment.Inset;
        g.FillEllipse(sb, offSet + ((location.Y * scale) / 2) + (location.X * scale), offSet + (-location.Y * scale), scale, scale);
        g.DrawEllipse(pen, offSet + ((location.Y * scale) / 2) + (location.X * scale), offSet + (-location.Y * scale), scale, scale);
        g.DrawString(state, text, sbt, (offSet + ((location.Y * scale) / 2) + (location.X * scale)) + scale / 6, (offSet + (-location.Y * scale)) + scale / 6);

        sb.Dispose();
        sbt.Dispose();
        pen.Dispose();
    }

So after every "computation" and have added something to my imaginary grid, I need to update the panel to show this new item on my grid. I have tried invalidating the panel right before the displayGrid() function but it seems to cause even more flickering.

The heartbeat() function is currently being called on a separate thread.

Here is my new Panel class.

public class Display : Panel
{
    public Display()
    {
        this.DoubleBuffered = true;

    }
}
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
David Chavez
  • 125
  • 1
  • 1
  • 9
  • does every elipse change every computation? – Tony Hopkinson Jun 02 '13 at 13:05
  • No it doesn't, however, at this point I'm redrawing all of them. I do need to make a future optimization of only changing what needs to be updated. But it starts flickering maybe on the 20th or so ellipse and that seems to soon to be related to that. – David Chavez Jun 02 '13 at 13:11

3 Answers3

12
    Graphics g = drawPanel.CreateGraphics();

Using CreateGraphics() and turning on double-buffering is the worst possible combination. CreateGraphics() gives you a Graphics object that draws directly to the screen. Double-buffering sets up a Graphics object that draws to a bitmap, the buffer used in double-buffering. Then renders the bitmap to the screen at the end of the paint cycle.

So what happens in your code is that you draw the screen directly, something you can barely see but visible if it is slow enough. Then right after that the buffer that you never draw into gets painted. Which wipes out what you drew before. The net effect is heavy flicker with your paint output visible for only a handful of milliseconds.

Using CreateGraphics() was the mistake. You always want to render through the e.Graphics object that you get from the Paint event so you'll render to the buffer. Pass that Graphics object to your drawMonomers() method. Thus:

public void drawMonomers(Graphics g, Point location, string state) {
   // Etc...
}

private void Display1_Paint(object sender, PaintEventArgs e) {
   //...
   drawMonomers(e.Graphics, loc, state);
}

In general, CreateGraphics() has very limited usefulness. You only ever use it when you want to draw to the screen directly and you can afford for whatever you draw to disappear. That is typically only useful in the kind of program that has a render loop that constantly runs, producing new output at a high rate like 20+ frames per second. Like a video game.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • In my program I must compute some interactions before I drawMonomers, how would I be able to keep processing, then redrawing using this method? – David Chavez Jun 02 '13 at 15:51
  • 1
    Store whatever you compute in fields of your class so you'll have them readily available when it is time to paint. If that computation hasn't completed yet then don't call drawMonomers() yet. Call the panel's Invalidate() method as soon as that's done. – Hans Passant Jun 02 '13 at 15:56
  • this computation is looping. compute objects in grid -> paint, compute objects in grid -> paint. As I understand the Paint is only done when it first loads the panel. – David Chavez Jun 02 '13 at 15:59
  • Painting is done when required. Or when you force it, like you did in your code snippet by calling Refresh(). – Hans Passant Jun 02 '13 at 15:59
2

Try replacing the Panel with a PictureBox. This worked for me.

-1

Adding the AllPaintingInWmPaint style will prevent the background from being redrawn.

I've run across this post before and found it quite helpful. How do I enable double-buffering of a control using C# (Windows forms)?

It might be overkill, but it works. One thing I've noticed with users is, if it appears to be running smoother and faster it is. (even if it really does take a little longer)

Community
  • 1
  • 1
Brad Bruce
  • 7,638
  • 3
  • 39
  • 60