views:

269

answers:

5

I am doing a little graphing via the System.Drawing and im having a few problems.

I'm holding data in a Queue and i'm drawing(graphing) out that data onto three picture boxes

this method fills the picture box then scrolls the graph across.

so not to draw on top of the previous drawings (and graduly looking messier) i found 2 solutions to draw the graph.

  1. Call plot.Clear(BACKGOUNDCOLOR) before the draw loop [block commented]

although this causes a flicker to appear from the time it takes to do the actual drawing loop.

  1. call plot.DrawLine(channelPen[5], j, 140, j, 0); just before each drawline [commented]

although this causes the drawing to start ok then slow down very quickly to a crawl as if a wait command had been placed before the draw command.

Here is the Code for reference:

/*plotx.Clear(BACKGOUNDCOLOR)

ploty.Clear(BACKGOUNDCOLOR)

plotz.Clear(BACKGOUNDCOLOR)*/

for (int j = 1; j < 599; j++)
            {
                if (j > RealTimeBuffer.Count - 1) break;

                QueueEntity past = RealTimeBuffer.ElementAt(j - 1);
                QueueEntity current = RealTimeBuffer.ElementAt(j);

                if (j == 1)
                {
                    //plotx.DrawLine(channelPen[5], 0, 140, 0, 0);
                    //ploty.DrawLine(channelPen[5], 0, 140, 0, 0);
                    //plotz.DrawLine(channelPen[5], 0, 140, 0, 0);
                }

                //plotx.DrawLine(channelPen[5], j, 140, j, 0);
                plotx.DrawLine(channelPen[0], j - 1, (((past.accdata.X - 0x7FFF) / 256) + 64), j, (((current.accdata.X - 0x7FFF) / 256) + 64));

                //ploty.DrawLine(channelPen[5], j, 140, j, 0);
                ploty.DrawLine(channelPen[1], j - 1, (((past.accdata.Y - 0x7FFF) / 256) + 64), j, (((current.accdata.Y - 0x7FFF) / 256) + 64));

                //plotz.DrawLine(markerPen, j, 140, j, 0);
                plotz.DrawLine(channelPen[2], j - 1, (((past.accdata.Z - 0x7FFF) / 256) + 94), j, (((current.accdata.Z - 0x7FFF) / 256) + 94));


            }

Is there any tricks to avoid these overheads?

If not would there be any other/better solutions?

EDIT: See [Final Solution] bellow for solution code

+1  A: 

Try buffering your data to another bitmap before you put it in the picture box. That will eliminate the flicker.

For example:

// create this once for each graph
Bitmap buffer = new Bitmap({width}, {height});

// when the data changes
using (Graphics g = Graphics.FromImage(buffer))
{
    // ... draw using the graphics
}

// draw the buffer on the picture box's image

Are you doing the drawing on a separate thread? How many times are you drawing the data a second? Your program will feel slow if you are doing it on the main thread or are repeatedly drawing the entire bitmap a number of times a second.

Zach Johnson
Thanks for the quick reply and the edit with the tips.I got the bitmap buffering working and its smooth if I only run one graph but flickers with more.The interval between the first and second call was stopwatched' at 14ms.At the minute the drawing is done on the GUI thread but on your advice i will move it
Luke Mcneice
I also tried drawing the data for the 3 graphs into the one (still using the bitmap buffering) This also causes flickering although not as bas as in the beginning.
Luke Mcneice
@Luke: Are you handling the Paint event / OnPaint, or setting the PictureBox's Image property?
Zach Johnson
neither i beilve, unless what im doing implements one of those under the hood. I create the graphics and bitmap with: Graphics plotx;Graphics bufferedplotx;private Bitmap BufferedBitMapx = new Bitmap(599,140);Inintalize with:plotx = this.pictureBoxAcc.CreateGraphics();bufferedplotx = Graphics.FromImage(BufferedBitMapx);then on calling my drawing method, draw the data to the bitmap:bufferedplotx.Clear(Color.Black);LOOP{bufferedplotx.DrawLine(channelPen[0], j - 1, (((past.accdata.X - 0x7FFF) / 256) + 64), j, (((current.accdata.X - 0x7FFF) / 256) + 64));}
Luke Mcneice
plotx.Clear(Color.Black);plotx.DrawImage(BufferedBitMapx, new Point(0, 0));and thats it
Luke Mcneice
A: 

I would use a double buffer approach. Create a new bitmap in memory(the same size as the graph) create a graphics object of that bitmap.

Then draw whatever you want on that object.

After all the drawing is done then create another graphics object from the pictureBox and draw the in-memory-bitmap using DrawImage.

This should reduce the flickering since you draw everything ofscreen except one big draw that updates the whole image.

Hope it helps.

Charlie boy
As i mentioned to Zach I got the bitmap buffering working and its smooth if I only run one graph but flickers with more.
Luke Mcneice
A: 

Subclass your picture box and enable the double buffer option.

Public Class MyBufferedPictureBox Inherits System.Windows.Forms.PictureBox

Public Sub New()
    MyBase.New()

    Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
End Sub

End Class

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.setstyle.aspx

Black Frog
I haven't had great success using that flag. If I remember correctly, most of the controls are already double buffered by default. It would sure be great if it did work, though.
Zach Johnson
+3  A: 

PictureBox already has double-buffering turned on. The only way you could perceive flicker is when you draw directly to the screen instead of using the Paint event. It isn't clear whether you do from your snippet. Buffering yourself with a bitmap would work too but isn't as efficient as the double-buffering implemented by Windows Forms.

Call its Invalidate() method when the data changes, do the drawing in the Paint event (using e.Graphics) and it won't flicker.

Hans Passant
This works a treat, thank you
Luke Mcneice
A: 

[FINAL SOLUTION]

Using the paint event even with duplicate loops was fast enough!

In the End rather than duplicate the drawing loop in each event, I used the first event to also draw the other two graphs to bitmaps and then on their paint event, simply drew the bitmap.

So essentially the final solution was a combination of many of your answers.

Thank guys.

private void pictureBoxAccX_Paint(object sender, PaintEventArgs e)
        {
            bufferedploty.Clear(Color.Black);
            bufferedplotz.Clear(Color.Black);           

            for (int j = 1; j < 599; j++)
            {
                if (j > RealTimeBuffer.Count - 1) break;

                QueueEntity past = RealTimeBuffer.ElementAt(j - 1);
                QueueEntity current = RealTimeBuffer.ElementAt(j);

                e.Graphics.DrawLine(channelPen[0], j - 1, (((past.accdata.X - 0x7FFF) / 256) + 64), j, (((current.accdata.X - 0x7FFF) / 256) + 64));
                bufferedploty.DrawLine(channelPen[1], j - 1, (((past.accdata.Y - 0x7FFF) / 256) + 64), j, (((current.accdata.Y - 0x7FFF) / 256) + 64));
                bufferedplotz.DrawLine(channelPen[2], j - 1, (((past.accdata.Z - 0x7FFF) / 256) + 94), j, (((current.accdata.Z - 0x7FFF) / 256) + 94));
            }   
        }


        private void pictureBoxAccY_Paint(object sender, PaintEventArgs e)
        {        
            e.Graphics.DrawImage(BufferedBitMapy, new Point(0, 0));            
        }


        private void pictureBoxAccZ_Paint(object sender, PaintEventArgs e)
        {            
            e.Graphics.DrawImage(BufferedBitMapz, new Point(0, 0));
        }




private void AddAccPoints()
        {
            //Code for putting in New queue data Here...
            pictureBoxAccX.Invalidate();
            pictureBoxAccY.Invalidate();
            pictureBoxAccZ.Invalidate();
        }

EDIT: With this exact solution invalidating the controls can cause the event scheduling to be undefined and randomly stop drawing as the bitmap creation is done in one the event methods.

See here: http://stackoverflow.com/questions/2615167/onpaint-events-invalidated-changing-execution-order-after-a-period-normal-opera

Luke Mcneice