2

I've a WinForms application on wich i have to draw some lines between controls. These lines need to be persistent, so i override the form OnPaint() event.

The problem is that, the re-draw of the lines aren't very smooth.

I'm creating the graphics as follows:

Graphics g;
g = this.CreateGraphics();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, this.ClientRectangle);

And drawing the lines as follows:

public void lineDraw(Control L1, Control L2) {            
    using (Pen pen = new Pen(Color.Black, 4)) {
        pen.StartCap = System.Drawing.Drawing2D.LineCap.Flat;
        pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
        int x1, x2, y1, y2;
        //choose x/y coordinates
        g.DrawLine(pen, x1, y1, x2, y2);
    }
}

Is there any property i can set to improve the smoothness of the drawn graphics?

guanabara
  • 590
  • 1
  • 9
  • 22

4 Answers4

4

Goal

An image is shown on a control (or form).

Invalidation

Any time the control (or form) is resized, minimalized/maximalized, partically obscured or moved around, it must be (partially) redrawn. When this happens the part of the control that must be redrawn is said to be invalidated.

When invalidated the control does something like this:

  1. Call OnPaintBackground: this fills the invalidated region with the background color.
  2. Call OnPaint: this draws the text and graphics on top of the background.

Why OnPaint causes flickering

You have overridden the OnPaint method of the control. Every time the control is redrawn you see a flash of the control with only its background color drawn in it. That is after OnPaintBackground has been called and before OnPaint has been called.

The solutions

  • If you have a static image (i.e. it never changes):

    1. In the Load event: create a new Bitmap object.
    2. Fill it with the background color and draw the lines and shapes on it.
    3. Assign this Bitmap object to the control's BackgroundImage property.
  • If you have a static image that must resize when the control resizes:

    1. Override the OnResize method and create the new Bitmap in there. Use the control's ClientSize property for the size of the Bitmap.
    2. Fill it with the background color and draw the lines and shapes on it.
    3. Assign this Bitmap object to the control's BackgroundImage property.
  • If you have an animated image:

    1. In the Load event set the DoubleBuffered property of the control to true. Setting this prevents the flicker you saw as it uses an invisible buffer to draw the control.
    2. Override the control's OnPaint method. Get the Graphics context of the control and draw the lines and shapes directly on the control.
    3. Create and enable a new Timer object and in its callback method call the control's Invalidate method followed by the Update method (as shown here). Set the timer to fire, for example, every 40 ms.
Community
  • 1
  • 1
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • I never knew that existed! +1 – Nolonar Feb 20 '13 at 17:04
  • It isn't blocky, but it blinks a lot every time the `OnPaint()` event is called. For example, when i move other control over the lines. In fact it makes sense, cause it haves to re-draw all the lines (can be many, but the aspect is the same for few lines) on each call. The `SmoothingMode.HighQuality` didn't solve the problem. Neither the `SmoothingMode.HighSpeed`. – guanabara Feb 20 '13 at 17:20
  • @Virtlink - You also might want to suggest using a timer to delay repaints so it doesn't go over the refresh rate. – Spencer Ruport Feb 20 '13 at 17:31
  • None of the solutions had good overall performance. The current solution was change the `Graphics.DrawLine()` to the `LineShape` object. – guanabara Feb 25 '13 at 09:51
  • @guanabara: I cannot imagine that my solution #1 would have poor performance. To be clear, it _does not use `OnPaint`_, and it sets the image only once when the size of the form is changed. – Daniel A.A. Pelsmaeker Feb 25 '13 at 11:01
  • @Virtlink, i did not try your solution, because its not suitable for my problem. To give you an overall picture, there are numerous situations where the `Graphics` object need to be redraw. This include, minimize/maximize window, form vertical/horizontal scroll, controls showing over the lines, etc etc. Set the image only when the form size changes is not a solution for this particular situation. – guanabara Feb 25 '13 at 15:00
  • @guanabara Indeed, I don't know your particular situation, but I infer from your post and comments that your graphics _do not change_ most of the time. So you should not draw it most of the time, and most certainly not recreate the bitmap most of the time. For example: minimize/maximize is the same as resizing, and scrolling can also be handled by the control itself without you recreating the image. If you want more help, you'll have to be more specific (edit) or start a new question (for a new issue). – Daniel A.A. Pelsmaeker Feb 25 '13 at 16:04
  • @Virtlink - you're right, my graphics do not change all the time. But if i don't draw them all the time, they just disappear when some events occurs (like resize,scroll,minimize/maximize,control override, etc). So, the solution was to use the `LineShape` class, to connect the controls in the form. This way, i got the same result, but with the advantage that is control, and i can do whatever i want with it. – guanabara Feb 26 '13 at 08:41
  • @guanabara You did not read my solution thoroughly enough, or you didn't understand it. You especially missed the part about setting the `BackgroundImage` property. I completely rewrote my post to explain everything. Your problem is solved by the first or second solution. You should not override `OnPaint`. If `LineShape` objects solve your problem, that's okay. – Daniel A.A. Pelsmaeker Feb 26 '13 at 11:37
2

You probably shouldn't use CreateGraphics here, and more importantly, don't use a local variable to store the graphic object. Use the graphic object obtained from the paint event and invalidate as needed.

protected override void OnPaint(PaintEventArgs e) {
  e.Graphics.Clear(Color.White);
  e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

  using (Pen pen = new Pen(Color.Black, 4)) {
    pen.StartCap = Drawing2D.LineCap.Flat;
    pen.EndCap = Drawing2D.LineCap.ArrowAnchor;
    int x1, x2, y1, y2;
    //choose x/y coordinates
    e.Graphics.DrawLine(pen, x1, y1, x2, y2);
  }

  base.OnPaint(e);
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
0

This's my way, its works for me

//FormMain.cs
private const float DisplayRatio = 6;

private Bitmap _bmpDisp; //use an in-memory bitmap to Persistent graphics
private Graphics _grpDisp4Ctl;
private Graphics _grpDisp4Bmp;
private Point _ptOldDsp;


private void FormMain_Shown(object sender, EventArgs e)
{
    _grpDisp4Ctl = CreateGraphics();
    _grpDisp4Ctl.SetHighQulity();

    _bmpDisp = new Bitmap(ClientSize.Width, ClientSize.Height);
    _grpDisp4Bmp = Graphics.FromImage(_bmpDisp);
    _grpDisp4Bmp.SetHighQulity();

    _ptOldDsp = new Point(
        (int)((MousePosition.X - SystemInformation.VirtualScreen.Left) / DisplayRatio),
        (int)((MousePosition.Y - SystemInformation.VirtualScreen.Top) / DisplayRatio)
    );
}

private void UpdateDisplay(MouseHookEvent mhep) //your implement
{        
    var ptNew = mhep.Position;
    ptNew.Offset(new Point(-SystemInformation.VirtualScreen.Left, -SystemInformation.VirtualScreen.Top));
    ptNew.X = (int)(ptNew.X / DisplayRatio);
    ptNew.Y = (int)(ptNew.Y / DisplayRatio);

    _grpDisp4Ctl.DrawLine(Pens.White, _ptOldDsp, ptNew); //draw smooth lines to mem and ui
    _grpDisp4Bmp.DrawLine(Pens.White, _ptOldDsp, ptNew);

    _ptOldDsp = ptNew;

}

private void FormMain_Paint(object sender, PaintEventArgs e)
{
    // like vb6's auto redraw :)
    e.Graphics.DrawImage(_bmpDisp, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
}

//common.cs
internal static void SetHighQulity(this Graphics g)
{
    g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
    g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
}
IlPADlI
  • 1,943
  • 18
  • 22
0

I know it's an older post, but you can also try setting DoubleBuffered property of the form to TRUE, read the following:

"Buffered graphics can reduce or eliminate flicker that is caused by progressive redrawing of parts of a displayed surface. Buffered graphics require that the updated graphics data is first written to a buffer. The data in the graphics buffer is then quickly written to displayed surface memory. The relatively quick switch of the displayed graphics memory typically reduces the flicker that can otherwise occur."

Norbert Forgacs
  • 605
  • 1
  • 8
  • 27