Custom control draws incorrectly when moving another window over it.

This issue has seriously frustrated me.
I am overridding OnPaintBackground to draw a gradient background in my custom control.
It resizes great - very smooth, no flicker.

When I try to move another window across it, it will repaint itself repeatedly... but it will repaint the area that was hidden by the window as if it was the entire control. So if I am starting the gradient from blue to green...When I move a window over half of the control, and then start moving it off, it will start painting the blue in the middle of the control. So after I have the window off the control...the first half of the control looks normal. The second half looks like the beginning of the control drawn over itself about a million times.

I hope this makes sense


Another issue(maybe a clue!)... I am getting a ton of "Out Of Memory" exceptions during the OnPaint/OnPaintBackground (only when I am moving a window over the control). Other than that I do not get Exceptions.
(Yes I Disposed of all of my drawing objects at the end of the OnPaint method.)

I have set all of my styles.
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);

I have attempted to put my custom drawing code inside OnPaint and then instead in OnPaintBackground. Both cause the same problem.

I tried getting the Control's ClientRectangle instead of getting the e.ClipRectangle to draw in (during the onPaint method). No Luck...


I am seriously lost here. This control has to be ready in time for our next build, and everything works great except this. If anyone has any clue to what is happening, I would very much appreciate the help. Thanks!


Rick





Answer this question

Custom control draws incorrectly when moving another window over it.

  • Matt Warren - MSFT

    Ok, I looked over the code; copied and pasted the code into a new user control and this is what I noticed.
    First off, I didn't receive any error messages, but I was able to re-create the issue that my control (previous thread that you helped me on...) was experiencing (not redrawing when clipped by other windows, etc...) and I discovered the cause in this area of the code.
    using (GraphicsPath path = new GraphicsPath())
    {
    height = e.ClipRectangle.Height;
    right = e.ClipRectangle.Right - 2;
    top = e.ClipRectangle.Top + 2;
    left = e.ClipRectangle.Left + 1;
    bottom = e.ClipRectangle.Bottom - 2;
    points = new Point[]{
    new Point(left, bottom),
    new Point(right, bottom),
    new Point(right, top+value),
    new Point(right-value, top),
    new Point(left + value, top),
    new Point(left, top+value),
    new Point(left, bottom)};

    path.AddLines(points);

    using (Pen pen = new Pen(Color.LightSkyBlue, .5f))
    {
    graphic.DrawPath(pen, path);
    }
    path.Dispose();
    }
    You were referencing the ClipRectangle just like I was. In fact, it appeared that you had the same code twice with the 2 paths. I removed the 2nd instance and I also cleaned up some other code. I also found that by moving the definition of the LinearGradientBrush outside of the Paint method that everything worked great. Here is my version of your control in its entirety and it seems to work great.
    //CODE BEGIN
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Text;
    using System.Windows.Forms;
    using System.Drawing.Drawing2D;
    namespace DrawProject
    {
    public partial class HeaderPanel : UserControl
    {
    private Region HeaderRegion;
    private LinearGradientBrush brush;
    private string _Header = "Information";
    public string Header
    {
    get
    {
    return _Header;
    }
    set
    {
    this._Header = value;
    }
    }
    public HeaderPanel()
    {
    InitializeComponent();
    this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
    this.Location = new Point(10, 10);
    this.Font = new Font("Verdana", 8, FontStyle.Bold);
    }
    private void HeaderPanel_Paint(object sender, PaintEventArgs e)
    {
    try
    {
    Rectangle r = this.ClientRectangle;
    int height = r.Height;
    int right = r.Right - 2;
    int top = r.Top + 2;
    int left = r.Left + 2;
    int bottom = r.Bottom - 2;
    int value = 4;
    Point[] points = {
    new Point(left, bottom),
    new Point(right, bottom),
    new Point(right, top+value),
    new Point(right-value, top),
    new Point(left + value, top),
    new Point(left, top+value),
    new Point(left, bottom)};
    GraphicsPath path2 = new GraphicsPath();
    path2.AddLines(points);
    e.Graphics.DrawPath(new Pen(Color.LightSkyBlue, .5f), path2);
    Blend blend = new Blend();
    float[] relativeIntensities = { 0.0f, 0.008f, .50f };
    float[] relativePositions = { 0.0f, 0.42f, 1.0f };
    blend.Factors = relativeIntensities;
    blend.Positions = relativePositions;
    brush = new LinearGradientBrush(new Point(left, top + height / 2), new Point(right, top + height / 2), Color.White, SystemColors.GradientActiveCaption);
    brush.Blend = blend;
    e.Graphics.FillPath(brush, path2);
    brush.Dispose();
    path2.Dispose();
    e.Graphics.DrawString(this._Header, this.Font, new SolidBrush(Color.DarkSlateBlue), left + 10, top + (height / 2 - Font.Size / 2) - 6);
    System.Diagnostics.Debug.WriteLine("onPaint");
    }
    catch (Exception ex)
    {

    }
    }
    }
    }

    //CODE END

  • Steve Jackson

    I completed agree with you PJ. That is why I tried painting using the ClientRectangle of the control to paint.

    So instead of...

    Rectangle rect = e.ClipRectangle;

    I was using...

    Rectangle rect = this.ClientRectangle;

    This should get the area of the entire control Or am I doing it wrong

    The exceptions are being thrown randomly! No specific line, and no specific time.

    If I move the window over my control VERY slowly, no exception will be thrown. But...I was checking how many times OnPaint was being called when I move a window over my control. It was painting about 20 - 50 times per second (if not more). I am not sure if this is normal.

    Oh and by the way. I have over 2gig of memory free when I get this out of memory exception. (and yes I have the .NET 1.1 patch installed)

    What do you think



  • resolve

    If you use the invalidate method inside any of your OnPaint/OnPaintBackground methods, you will have an infinite loop.

    Invalidate causes OnPaint to be called, so if you put invalidate in OnPaint, it will call itself forever.

    Or am I understanding this incorrectly



  • Fran-1980

    Ah ***, it was because I was forgetting about the second declaration of the bounds!

    Thanks for the help!!!!! You the man!



  • nicolasdiogo

    csharpnewbie001 wrote:
    This should get the area of the entire control Or am I doing it wrong


    You are correct, but you can better use the visable clip if you have a very large control. Remember that this doesn't work with COM Interop, if your control is used trought COM.

    csharpnewbie001 wrote:
    The exceptions are being thrown randomly! No specific line, and no specific time.


    Than you have a memory leak.

    csharpnewbie001 wrote:
    If I move the window over my control VERY slowly, no exception will be thrown. But...I was checking how many times OnPaint was being called when I move a window over my control. It was painting about 20 - 50 times per second (if not more). I am not sure if this is normal.

    It is normal that the Paint event is called every time when needed, so when one pixel needed a repaint, this is called.

    But you should only paint the ClipRectangle, if you use a buffer! I geus you arent using a buffer, so i suggest you will implement this logic first, this will improve your speed when you have a havy painting operation.



  • DotNetHeaven

    Hmm...Here is my code. Its quite simple. Does anything stick out as a memory leak
    I am very new with using GDI+.

    To give you an example of what it is supposed to look like... (if you are using windows XP)
    Open MyComputer, and look at the left side of the window. Do you see those collapsable panels This header control is supposed to mimic the header style of those headers on those panels.

    public partial class HeaderPanel : UserControl
    {

    private Region HeaderRegion;

    private string _Header = "Information";
    public string Header
    {
    get
    {
    return _Header;
    }
    set
    {
    this._Header = value;
    }
    }


    public HeaderPanel()
    {

    InitializeComponent();
    this.Location = new Point(10, 10);
    this.Font = new Font("Verdana", 8, FontStyle.Bold);

    this.SetStyle(ControlStyles.UserPaint, true);
    this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    this.SetStyle(ControlStyles.ResizeRedraw, true);
    this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);

    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {


    try
    {
    base.OnPaintBackground(e);

    Graphics graphic = e.Graphics;
    int height = this.ClientRectangle.Height;
    int right = this.ClientRectangle.Right - 2;
    int top = this.ClientRectangle.Top + 2;
    int left = this.ClientRectangle.Left + 2;
    int bottom = this.ClientRectangle.Bottom - 2;
    int value = 4;
    Point[] points = {
    new Point(left, bottom),
    new Point(right, bottom),
    new Point(right, top+value),
    new Point(right-value, top),
    new Point(left + value, top),
    new Point(left, top+value),
    new Point(left, bottom)};
    GraphicsPath path2 = new GraphicsPath();

    path2.AddLines(points);



    using (GraphicsPath path = new GraphicsPath())
    {
    height = e.ClipRectangle.Height;
    right = e.ClipRectangle.Right - 2;
    top = e.ClipRectangle.Top + 2;
    left = e.ClipRectangle.Left + 1;
    bottom = e.ClipRectangle.Bottom - 2;
    points = new Point[]{
    new Point(left, bottom),
    new Point(right, bottom),
    new Point(right, top+value),
    new Point(right-value, top),
    new Point(left + value, top),
    new Point(left, top+value),
    new Point(left, bottom)};


    path.AddLines(points);

    using (Pen pen = new Pen(Color.LightSkyBlue, .5f))
    {
    graphic.DrawPath(pen, path);
    }
    path.Dispose();

    }


    Blend blend = new Blend();
    float[] relativeIntensities = { 0.0f, 0.008f, .50f };
    float[] relativePositions = { 0.0f, 0.42f, 1.0f };
    blend.Factors = relativeIntensities;
    blend.Positions = relativePositions;


    LinearGradientBrush brush = new LinearGradientBrush(new Point(left, top + height / 2), new Point(right, top + height / 2), Color.White, SystemColors.GradientActiveCaption);


    brush.Blend = blend;
    graphic.FillPath(brush, path2);
    brush.Dispose();
    path2.Dispose();

    using (SolidBrush textBrush = new SolidBrush(Color.DarkSlateBlue))
    {
    graphic.DrawString(this._Header, this.Font, textBrush, left + 10, top + (height / 2 - Font.Size / 2) - 6);
    }

    System.Diagnostics.Debug.WriteLine("onPaint");

    }
    catch (Exception ex)
    {

    }
    }
    }


  • Dan Ludwig

    If you paint a gradient you should not use the ClipRectangle that is provided by the event args but you should fully repaint everything, or you should calculate the gradient piece but that will be very hard. So you should fully repaint your visable surface.

    And for the Exception, where do you get it On a specific statement


  • Darren Goobey

    Maybe, but I'm not sure that can help you, you can try to use the Invalidate method


  • Custom control draws incorrectly when moving another window over it.