Search code examples
c#user-controlsdrawing2d

Custom control - first draws OK, second doesn't


I found some code somewhere for a custom groupbox, drawn with rounded corners, a gradient background and a drop shadow. I thought it looked pretty good, so I adjusted it to do the same with a label control.

The bizarre thing is that when I drop one of these controls onto a form designer, the default properties are set and it draws just fine - I put multiple copies on the form, and they are all fine.

However, when I try to add several controls at run-time, only the first version is actually drawn properly. The others end-up as just blank white boxes.

I really don't know what to make of this.

Code for the label is as follows (sorry - quite long!)

public class LabelEx : Label
{
    private System.Drawing.Color _BorderColor = Color.FromArgb(141, 178, 227);
    private float _BorderWidth = 1;
    private System.Drawing.Color _BackgroundColor = Color.White;
    private System.Drawing.Color _BackgroundColorGradient = Color.FromArgb(227, 235, 246);
    private ControlGradientMode _BackgroundGradientMode = ControlGradientMode.Vertical;

    private int _CornerRadius = 5;
    private RoundedControlCorners _Corners = RoundedControlCorners.All;
    private int _DropShadowThickness = 3;
    private bool _DropShadowVisible = true;
    private System.Drawing.Color _ShadowColor = Color.FromArgb(50, Color.Black);

    private ControlStyles _LabelStyle = ControlStyles.Extended;

    public enum ControlStyles
    {
        Standard,
        Extended
    }

    public LabelEx()
    {
        //InitializeComponent();
    }

    /// <summary>Gets or sets the radius of the corners of the control.</summary>
    //[Category("Appearance"), Description("This feature will round the corners of the control.")]
    public int CornerRadius
    {
        get { return _CornerRadius; }
        set
        {
            if (value > 35)
            {
                _CornerRadius = 35;
            }
            else
            {
                if (value < 1)
                {
                    _CornerRadius = 1;
                }
                else
                {
                    _CornerRadius = value;
                }
            }
            Invalidate();
        }
    }
    /// <summary>Turns on or off the control shadowing.</summary>
    //[Category("Appearance"), Description("This feature will turn on control shadowing.")]
    public bool DropShadowVisible
    {
        get { return _DropShadowVisible; }
        set
        {
            _DropShadowVisible = value;
            Invalidate();
        }
    }
    /// <summary>Gets or sets the color of the control's border.</summary>
    //[Category("Appearance"), Description("This feature will allow you to change the color of the control's border.")]
    public System.Drawing.Color BorderColor
    {
        get { return _BorderColor; }
        set
        {
            _BorderColor = value;
            Invalidate();
        }
    }
    /// <summary>Gets or Sets the control's border size.</summary>
    //[Category("Appearance"), Description("This feature will allow you to set the control's border size.")]
    public float BorderWidth
    {
        get { return _BorderWidth; }
        set
        {
            if (value > 10)
            {
                _BorderWidth = 10;
            }
            else
            {
                if (value < 1)
                {
                    _BorderWidth = 1;
                }
                else
                {
                    _BorderWidth = value;

                }
            }

            Invalidate();
        }
    }
    /// <summary>Gets or sets the size of the shadow border thickness.</summary>
   // [Category("Appearance"), Description("This feature will change the size of the shadow border.")]
    public int DropShadowThickness
    {
        get { return _DropShadowThickness; }
        set
        {
            if (value > 10)
            {
                _DropShadowThickness = 10;
            }
            else
            {
                if (value < 1)
                {
                    _DropShadowThickness = 1;
                }
                else
                {
                    _DropShadowThickness = value;
                }
            }
            Invalidate();
        }
    }
    /// <summary>Gets or sets the background color to use. This color can also be used in combination with BackgroundColorGradient for a gradient paint.</summary>
    // [Category("Appearance"), Description("This feature will change the group control color. This color can also be used in combination with BackgroundColorGradient for a gradient paint.")]
    public System.Drawing.Color BackgroundColor
    {
        get { return _BackgroundColor; }
        set
        {
            _BackgroundColor = value;
            Invalidate();
        }
    }
    /// <summary>Specifies the second color when using the gradient mode.</summary>
    // [Category("Appearance"), Description("This feature can be used in combination with BackgroundColor to create a gradient background.")]
    public System.Drawing.Color BackgroundColorGradient
    {
        get { return _BackgroundColorGradient; }
        set
        {
            _BackgroundColorGradient = value;
            Invalidate();
        }
    }

    /// <summary>This property secifies the type of gradient to use when painting the background.</summary>
    // [Category("Appearance"), Description("This feature turns on background gradient painting.")]
    public ControlGradientMode BackgroundGradientMode
    {
        get { return _BackgroundGradientMode; }
        set
        {
            _BackgroundGradientMode = value;
            Invalidate();
        }
    }

    /// <summary>Specifies which corners to apply rounding for</summary>
    public RoundedControlCorners Corners
    {
        get { return _Corners; }
        set
        {
            _Corners = value;
            Invalidate();
        }
    }

    /// <summary>Gets or sets the Style for the GroupBox.  Standard duplicates the stock groupbox.  Extended allows the customization ability</summary>
    // [Category("Appearance"), Description("Gets or sets the Style for the GroupBox.  Standard duplicates the stock groupbox.  Extended allows the customization ability")]
    // [DefaultValue(ControlStyles.Extended)]
    public ControlStyles LabelStyle
    {
        get { return _LabelStyle; }
        set
        {
            _LabelStyle = value;
            Invalidate();
        }
    }

    /// <summary>
    /// Function to paint the label as per what we want.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPaint(PaintEventArgs e)
    {
        //Just use a normal label if the "standard" is set.
        if (_LabelStyle == ControlStyles.Standard)
        {
            base.OnPaint(e);
            return;
        }

        //we must set the smoothing mode to anti alias or high quality(They are the same) in order to get the
        //nice rounded corders on our control

        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

        //first lets set the rectangles we will be drawing in.  If the drop shadow is visible then we need to 
        //reduce the size of the main rectangle and create a second one that is offset by the shadow thickness.
        Rectangle shadowRec = default(Rectangle);
        Rectangle frameRec = default(Rectangle);
        if (DropShadowVisible)
        {
            shadowRec = Rectangle.FromLTRB(DropShadowThickness, DropShadowThickness, this.ClientRectangle.Right - 1, this.ClientRectangle.Bottom - 1);
            frameRec = Rectangle.FromLTRB(0, 0, this.ClientRectangle.Right - DropShadowThickness - 2, this.ClientRectangle.Bottom - DropShadowThickness - 2);
        }
        else
        {
            frameRec = Rectangle.FromLTRB(this.ClientRectangle.Left, this.ClientRectangle.Top, this.ClientRectangle.Right - 1, this.ClientRectangle.Bottom - 1);
        }

        if (frameRec.Height <= 0 | frameRec.Width <= 0)
            return;

        //draw the drop shadow using the shadow path rectangle
        if (DropShadowVisible)
        {
            using (GraphicsPath path = GraphicsFunctions.RoundRectangle(shadowRec, _CornerRadius, _Corners))
            {
                using (SolidBrush b = new SolidBrush(_ShadowColor))
                {
                    e.Graphics.FillPath(b, path);
                }
            }
        }

        //draw the rest of the control in the main frame path
        using (GraphicsPath path = GraphicsFunctions.RoundRectangle(frameRec, _CornerRadius, _Corners))
        {
            //paint the background inside the frame
            if (BackgroundGradientMode == ControlGradientMode.None)
            {
                //solid background
                using (SolidBrush b = new SolidBrush(BackgroundColor))
                {
                    e.Graphics.FillPath(b, path);
                }
            }
            else
            {
                //gradient
                if (BackgroundGradientMode == ControlGradientMode.VerticalGlow)
                {
                    using (LinearGradientBrush b = new LinearGradientBrush(new Point(0, frameRec.Top + (frameRec.Height / 2)), new Point(0, frameRec.Bottom - 1), BackgroundColorGradient, BackgroundColor))
                    {

                        b.WrapMode = WrapMode.TileFlipXY;
                        e.Graphics.FillPath(b, path);
                    }
                }
                else if (BackgroundGradientMode == ControlGradientMode.HorizontalGlow)
                {
                    using (LinearGradientBrush b = new LinearGradientBrush(new Point(frameRec.Left + (frameRec.Width / 2), 0), new Point(frameRec.Right - 1, 0), BackgroundColorGradient, BackgroundColor))
                    {

                        b.WrapMode = WrapMode.TileFlipXY;
                        e.Graphics.FillPath(b, path);
                    }
                }
                else
                {
                    using (LinearGradientBrush br = new LinearGradientBrush(frameRec, BackgroundColor, BackgroundColorGradient, (LinearGradientMode)BackgroundGradientMode))
                    {
                        e.Graphics.FillPath(br, path);
                    }
                }

                //
                using (SolidBrush b = new SolidBrush(ForeColor))
                {
                    StringFormat sf = new StringFormat();
                    //Rectangle textRec = default(Rectangle);


                    switch (this.TextAlign)
                    {
                        case ContentAlignment.BottomCenter:
                            sf.Alignment = StringAlignment.Center;
                            sf.LineAlignment = StringAlignment.Far;
                            break;
                        case ContentAlignment.BottomLeft:
                            sf.Alignment = StringAlignment.Near;
                            sf.LineAlignment = StringAlignment.Far;
                            break;
                        case ContentAlignment.BottomRight:
                            sf.Alignment = StringAlignment.Far;
                            sf.LineAlignment = StringAlignment.Far;
                            break;
                        case ContentAlignment.MiddleCenter:
                            sf.Alignment = StringAlignment.Center;
                            sf.LineAlignment = StringAlignment.Center;
                            break;
                        case ContentAlignment.MiddleLeft:
                            sf.Alignment = StringAlignment.Near;
                            sf.LineAlignment = StringAlignment.Center;
                            break;
                        case ContentAlignment.MiddleRight:
                            sf.Alignment = StringAlignment.Far;
                            sf.LineAlignment = StringAlignment.Center;
                            break;
                        case ContentAlignment.TopCenter:
                            sf.Alignment = StringAlignment.Center;
                            sf.LineAlignment = StringAlignment.Near;
                            break;
                        case ContentAlignment.TopLeft:
                            sf.Alignment = StringAlignment.Near;
                            sf.LineAlignment = StringAlignment.Near;
                            break;
                        case ContentAlignment.TopRight:
                            sf.Alignment = StringAlignment.Far;
                            sf.LineAlignment = StringAlignment.Near;
                            break;
                    }


                    sf.Alignment = StringAlignment.Center;
                    e.Graphics.DrawString(this.Text, this.Font, b, (RectangleF)frameRec, sf);
                    Invalidate();
                }
                Invalidate();
            }

            e.Graphics.ResetTransform();
            //draw the border
            using (Pen p = new Pen(BorderColor, BorderWidth))
            {
                e.Graphics.DrawPath(p, path);
            }
        }
    }
}

It has a dependency on this:

[Flags()]
public enum RoundedControlCorners
{
    None = 0,
    NorthWest = 2,
    NorthEast = 4,
    SouthEast = 8,
    SouthWest = 16,
    All = NorthWest | NorthEast | SouthEast | SouthWest,
    North = NorthWest | NorthEast,
    South = SouthEast | SouthWest,
    East = NorthEast | SouthEast,
    West = NorthWest | SouthWest
}

public enum ControlGradientMode
{
    /// <summary>Specifies no gradient mode.</summary>
    None = 4,

    /// <summary>Specifies a gradient from upper right to lower left.</summary>
    BackwardDiagonal = LinearGradientMode.BackwardDiagonal,

    /// <summary>Specifies a gradient from upper left to lower right.</summary>
    ForwardDiagonal = LinearGradientMode.ForwardDiagonal,

    /// <summary>Specifies a gradient from left to right.</summary>
    Horizontal = LinearGradientMode.Horizontal,

    /// <summary>Specifies a gradient from top to bottom.</summary>
    Vertical = LinearGradientMode.Vertical,

    VerticalGlow = 5,
    HorizontalGlow = 6
}

public static class GraphicsFunctions
{
    public static GraphicsPath RoundRectangle(Rectangle r, int radius, RoundedControlCorners corners)
    {
        GraphicsPath path = new GraphicsPath();
        if (r.Width <= 0 | r.Height <= 0)
            return path;

        int d = radius * 2;

        int nw = ((corners & RoundedControlCorners.NorthWest) == RoundedControlCorners.NorthWest ? d : 0);
        int ne = ((corners & RoundedControlCorners.NorthEast) == RoundedControlCorners.NorthEast ? d : 0);
        int se = ((corners & RoundedControlCorners.SouthEast) == RoundedControlCorners.SouthEast ? d : 0);
        int sw = ((corners & RoundedControlCorners.SouthWest) == RoundedControlCorners.SouthWest ? d : 0);

        //path.AddLine(r.Left + nw, r.Top, r.Right - ne, r.Top);
        path.AddLine(r.Left + nw, r.Top, r.Right - ne, r.Top);

        if (ne > 0)
        {
            path.AddArc(Rectangle.FromLTRB(r.Right - ne, r.Top, r.Right, r.Top + ne), -90, 90);
        }

        path.AddLine(r.Right, r.Top + ne, r.Right, r.Bottom - se);

        if (se > 0)
        {
            path.AddArc(Rectangle.FromLTRB(r.Right - se, r.Bottom - se, r.Right, r.Bottom), 0, 90);
        }

        path.AddLine(r.Right - se, r.Bottom, r.Left + sw, r.Bottom);

        if (sw > 0)
        {
            path.AddArc(Rectangle.FromLTRB(r.Left, r.Bottom - sw, r.Left + sw, r.Bottom), 90, 90);
        }

        path.AddLine(r.Left, r.Bottom - sw, r.Left, r.Top + nw);

        if (nw > 0)
        {
            path.AddArc(Rectangle.FromLTRB(r.Left, r.Top, r.Left + nw, r.Top + nw), 180, 90);
        }

        path.CloseFigure();
        return path;
    }
}

Code I am using to add it to a form:

LabelEx oLabel1 = new LabelEx();
LabelEx oLabel2 = new LabelEx();
LabelEx oLabel3 = new LabelEx();

oLabel1.Top = 50;
oLabel2.Top = 100;
oLabel3.Top = 150;

oLabel1.Height = 25;
oLabel2.Height = 25;

oLabel1.Text = "Hello";
oLabel2.Text = "There";

scBase.Panel2.Controls.Add(oLabel1);
scBase.Panel2.Controls.Add(oLabel2);

Solution

  • protected override void OnPaint(PaintEventArgs e)
    {
       //...
                Invalidate();
    }
    

    A good practice for any programmer is to always have Task Manager running. I always keep it minimized to the notification area. Gives a birds-eye view on what's happening on my machine, a quick click to activate it.

    That quickly identified what's wrong with your program, it is burning 100% core. That's because you call Invalidate() in the OnPaint() method, immediately invalidating what you've painted. That has several side-effects, for one you see your program never going idle and keep running code on a processor core. Because Windows keeps generating paint events over and over again.

    For another, only one label control can ever get painted. The first one, the lowest in the Z-order. Because after it is done painting, the next paint request is again for the same label because of the Invalidate() call.

    Delete the Invalidate() calls to fix your problem, there are two.