views:

86

answers:

3

I'm working on a simple vector drawing app in C#/.Net. The drawing is done in a panel, but I'm not using the OnPaint() event for all of it - in fact the OnPaint() even just calls another method which actually draws everything in the document.

I tried to add double buffering, but when I set DoubleBuffered to true, the flicker issue is even worse. Why is this? If I want to double buffer the control, do I absolutely have to do all my drawing in the OnPaint() event, with the supplied Graphics object, instead of using Panel.CreateGraphics() and then drawing to that?

EDIT: This is the basic code I am using.

private void doc_Paint(object sender, PaintEventArgs e)
{
    g = doc.CreateGraphics();
    Render(ScaleFactor, Offset);
}    

private void Render(float ScaleFactor, PointF offset)
{
    foreach (Line X in Document.Lines) { DrawLine(X.PointA, X.PointB, X.Color, X.LineWidth); }
}
private void DrawLine(PointF A, PointF B, Color Color, float Width)
{
    Pen p = new Pen(Color, Width);
    PointF PA = new PointF(((A.X + Offset.X) * ScaleFactor), ((A.Y + Offset.Y) * ScaleFactor));
    PointF PB = new PointF(((B.X + Offset.X) * ScaleFactor), ((B.Y + Offset.Y) * ScaleFactor));
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    g.DrawLine(p, PA, PB);
}

The general idea is that the two variables, ScaleFactor and Offset, refer to the zoom level and pan level in the UI. g is a Graphics object.

+1  A: 

Personally I don't bother with the DoubleBuffered setting. I just draw everything to a bitmap and then in the paint event draw that bitmap on the screen.

Bitmap BackBuffer;

private void MainFormSplitContainerPanel1Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(MainFormSplitContainer.Panel1.BackColor);
    if (BackBuffer != null)
        e.Graphics.DrawImage(BackBuffer, positionX, positionY, SizeX, SizeY);
}
Brandi
Would this be an easy change to make? Could I just swap in a reference to the bitmap wherever I have a reference to Panel.Graphics?
iansinke
Think so. Basically in my apps, I have functions that will assign or draw things to the Bitmap that is not associated with the paint function. Just remember you will not see the changes til the paint function is called, so when you want to "refresh" the image, call MainFormSplitContainer.Panel1.Invalidate();
Brandi
This is not a good idea. The buffer that Windows Forms provides is quite a bit more efficient.
Hans Passant
@Hans: I wasn't aware of this. I always just figured that's what it was doing behind the scenes anyway, and this way gives more direct control. Do you have any references I could read on the matter?
Brandi
The Reference Source that Microsoft provides would be one. Doesn't really tell you what's best, I discovered this by profiling.
Hans Passant
I was going to say there are plenty of resources out there that tell you to do it this way, and I've never heard that it was less efficient before now.
Brandi
+8  A: 
g = doc.CreateGraphics();

That's the mistake. Double-buffering can only work if you draw into the buffer. The one that e.Graphics references. Fix:

g = e.Graphics;

Beware that Panel doesn't have double-buffering turned on by default. You'll need to derive your own. Paste this into a new class:

using System;
using System.Windows.Forms;

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

Compile. Drop it from the top of the toolbox.

Hans Passant
+1 for the `BufferedPanel` class. I know at least two programmers who have written exactly the same code independently. This should really be part of the framework.
nikie
+1, also not the best of ideas to persist it, better to pass it to Render as an argument.
sixlettervariables
A: 

If you don't mind an ummanaged solution look here:

http://stackoverflow.com/questions/2682025/disable-painting-of-the-vscrollbar-in-a-system-windows-forms-richtextbox (the accepted answer)

That often worked for me.

SchlaWiener