0

I have created a custom control for drawing a Dial type gauge but I've come across a strange issue. The form that I am using the gauge on has two of these gauges. One gauge works perfectly fine (the one docked on the left), but the one on the right literally does not paint, even in Visual Studio.

If I'm in Visual Studio and I change from a code tab to the tab with the gauge, that one gauge is never drawn and it still shows the text from the previous tab. If I click on the gauge, then the gauge suddenly draws.

This happens when the program is run as well but instead of not redrawing the space, it is just completely transparent, even erasing the form background.

Paint Issue

The OnPaint Code is like this:

protected override void OnPaint(PaintEventArgs e)
{
    int size = ClientSize.Height - GaugeThickness;

    float x = (GaugeThickness / 2) + OffsetX;
    float y = (GaugeThickness / 2) + OffsetY;

    if (this.HorizontalAlignment == HorizontalAlignment.Left)
    {
        x = ((size / 2) * -1) + OffsetX;
    }
    else if (this.HorizontalAlignment == HorizontalAlignment.Right)
    {
        x = (size / 2) + OffsetX;
    }

    if (this.VerticalAlignment == VerticalAlignment.Top)
    {
        y = ((size / 2) * -1) + OffsetY;
    }
    else if (this.VerticalAlignment == VerticalAlignment.Bottom)
    {
        y = (size / 2) + OffsetY;
    }

    PaintBackground(e, x, y);
    PaintText(e, x, y, size, size);
    PaintMarks(e, x, y);
    PaintNeedle(e, x, y);
}

protected virtual void PaintBackground(PaintEventArgs e, float x, float y)
{
    int size = ClientSize.Height - GaugeThickness;
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;


    //Draw background
    using (Pen p = new Pen(GaugeColor, GaugeThickness))
    {
        e.Graphics.DrawArc(p, x, y, size, size, StartAngle, SweepAngle);
    }
}

protected virtual void PaintText(PaintEventArgs e, float x, float y, int width, int height)
{
    TextFormatFlags horizontal = TextFormatFlags.HorizontalCenter;
    TextFormatFlags vertical = TextFormatFlags.VerticalCenter;

    if (this.TextAlignmentHorizontal == HorizontalAlignment.Left)
    {
        horizontal = TextFormatFlags.Left;
    }
    else if (this.TextAlignmentHorizontal == HorizontalAlignment.Right)
    {
        horizontal = TextFormatFlags.Right;
    }

    if (this.TextAlignmentVertical == VerticalAlignment.Top)
    {
        vertical = TextFormatFlags.Top;
    }
    else if (this.TextAlignmentVertical == VerticalAlignment.Bottom)
    {
        vertical = TextFormatFlags.Bottom;
    }
    Rectangle rect = new Rectangle((int)x, (int)y, width, height);
    e.Graphics.FillEllipse(new SolidBrush(Color.Transparent), rect);
    TextRenderer.DrawText(e.Graphics, this.Text, this.Font, rect,
        this.ForeColor, horizontal | vertical);
}

protected virtual void PaintMarks(PaintEventArgs e, float x, float y)
{
    int size = ClientSize.Height - GaugeThickness;
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    //Draw Marks
    float markLocation = 0.0f;
    float spacing = SweepAngle / 10;
    for (int i = 0; i < 11; i++)
    {
        using (Pen p = new Pen(MarkColor, MarkLength))
        {
            p.Alignment = PenAlignment.Inset;
            e.Graphics.DrawArc(p, x, y, size, size, markLocation + StartAngle, MarkThickness);
            markLocation += spacing;
        }
    }
}

protected virtual void PaintNeedle(PaintEventArgs e, float x, float y)
{
    int size = ClientSize.Height - GaugeThickness;
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    //Draw needle.
    if (Value >= MaxValue) Value = MaxValue;
    using (Pen p = new Pen(NeedleColor, NeedleLength))
    {
        p.Alignment = PenAlignment.Inset;
        e.Graphics.DrawArc(p, x, y, size, size, StartAngle + GetNeedleAngle(), NeedleSweep);
    }
    base.OnPaint(e);
}

I really don't have the slightest clue what would be causing the entire control to not paint at all until you click it.

I already checked Display.Designer.CS and both controls are added to "Display.Controls".

Does anyone see or know anything that I don't?

I ran Debug and the OnPaint function literally never gets called on the GPU gauge until the gauge loses/gains focus.

Here is my entire DialGauge class:

public class DialGauge : Control
{
    #region Properties
    [Browsable(true)]
    public float StartAngle { get => _startAngle; set { _startAngle = value; Invalidate(); } }

    [Browsable(true)]
    public float SweepAngle { get => _sweepAngle; set { _sweepAngle = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(100d)]
    public double MaxValue { get => _maxvalue; set { _maxvalue = value; Invalidate(); } }

    [Browsable(true)]
    public int GaugeThickness { get => _gaugethickness; set { _gaugethickness = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(150)]
    public int NeedleLength { get => _needlelength; set { _needlelength = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(5)]
    public float NeedleSweep { get => _needlesweep; set { _needlesweep = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(0.0)]
    public double Value { get => _value; set { _value = value; Invalidate(); } }

    [Browsable(true)]
    public Color GaugeColor { get => gaugecolor; set { gaugecolor = value; Invalidate(); } }

    [Browsable(true)]
    public Color NeedleColor { get => needlecolor; set { needlecolor = value; Invalidate(); } }

    [Browsable(true)]
    public float MarkLength { get => _marklength; set { _marklength = value; Invalidate(); } }

    [Browsable(true)]
    public Color MarkColor { get => _markcolor; set { _markcolor = value; Invalidate(); } }

    [Browsable(true)]
    public float MarkThickness { get => _markthickness; set { _markthickness = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(HorizontalAlignment.Center)]
    public HorizontalAlignment HorizontalAlignment { get => _horizontalalignment; set { _horizontalalignment = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(VerticalAlignment.Center)]
    public VerticalAlignment VerticalAlignment { get => _verticalalignment; set { _verticalalignment = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(0f)]
    public float OffsetX { get => _offset_x; set { _offset_x = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(0f)]
    public float OffsetY { get => _offset_y; set { _offset_y = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(HorizontalAlignment.Center)]
    public HorizontalAlignment TextAlignmentHorizontal { get => _texthorizontalalignment; set { _texthorizontalalignment = value; Invalidate(); } }

    [Browsable(true)]
    [DefaultValue(VerticalAlignment.Center)]
    public VerticalAlignment TextAlignmentVertical { get => _textverticalalignment; set { _textverticalalignment = value; Invalidate(); } }

    [Browsable(true)]
    public override string Text { get => base.Text; set { base.Text = value; Invalidate(); } }

    [Browsable(true)]
    public override Font Font { get => base.Font; set { base.Font = value; Invalidate(); } }

    protected float _startAngle;
    protected float _sweepAngle;
    protected double _maxvalue;
    protected int _gaugethickness;
    protected int _needlelength;
    protected float _needlesweep;
    protected double _value;
    protected Color gaugecolor;
    protected Color needlecolor;
    protected float _marklength;
    protected Color _markcolor;
    protected float _markthickness;
    protected HorizontalAlignment _horizontalalignment;
    protected VerticalAlignment _verticalalignment;
    protected HorizontalAlignment _texthorizontalalignment;
    protected VerticalAlignment _textverticalalignment;
    protected float _offset_x;
    protected float _offset_y;
    #endregion

    public DialGauge()
    {
        this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        DoubleBuffered = true;
    }

    #region PAINT
    protected override void OnPaint(PaintEventArgs e)
    {
        int size = ClientSize.Height - GaugeThickness;

        float x = (GaugeThickness / 2) + OffsetX;
        float y = (GaugeThickness / 2) + OffsetY;

        if (this.HorizontalAlignment == HorizontalAlignment.Left)
        {
            x = ((size / 2) * -1) + OffsetX;
        }
        else if (this.HorizontalAlignment == HorizontalAlignment.Right)
        {
            x = (size / 2) + OffsetX;
        }

        if (this.VerticalAlignment == VerticalAlignment.Top)
        {
            y = ((size / 2) * -1) + OffsetY;
        }
        else if (this.VerticalAlignment == VerticalAlignment.Bottom)
        {
            y = (size / 2) + OffsetY;
        }

        PaintBackground(e, x, y);
        PaintText(e, x, y, size, size);
        PaintMarks(e, x, y);
        PaintNeedle(e, x, y);
    }

    protected virtual void PaintBackground(PaintEventArgs e, float x, float y)
    {
        int size = ClientSize.Height - GaugeThickness;
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;


        //Draw background
        using (Pen p = new Pen(GaugeColor, GaugeThickness))
        {
            e.Graphics.DrawArc(p, x, y, size, size, StartAngle, SweepAngle);
        }
    }

    protected virtual void PaintText(PaintEventArgs e, float x, float y, int width, int height)
    {
        TextFormatFlags horizontal = TextFormatFlags.HorizontalCenter;
        TextFormatFlags vertical = TextFormatFlags.VerticalCenter;

        if (this.TextAlignmentHorizontal == HorizontalAlignment.Left)
        {
            horizontal = TextFormatFlags.Left;
        }
        else if (this.TextAlignmentHorizontal == HorizontalAlignment.Right)
        {
            horizontal = TextFormatFlags.Right;
        }

        if (this.TextAlignmentVertical == VerticalAlignment.Top)
        {
            vertical = TextFormatFlags.Top;
        }
        else if (this.TextAlignmentVertical == VerticalAlignment.Bottom)
        {
            vertical = TextFormatFlags.Bottom;
        }
        Rectangle rect = new Rectangle((int)x, (int)y, width, height);
        e.Graphics.FillEllipse(new SolidBrush(Color.Transparent), rect);
        TextRenderer.DrawText(e.Graphics, this.Text, this.Font, rect,
            this.ForeColor, horizontal | vertical);
    }

    protected virtual void PaintMarks(PaintEventArgs e, float x, float y)
    {
        int size = ClientSize.Height - GaugeThickness;
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

        //Draw Marks
        float markLocation = 0.0f;
        float spacing = SweepAngle / 10;
        for (int i = 0; i < 11; i++)
        {
            using (Pen p = new Pen(MarkColor, MarkLength))
            {
                p.Alignment = PenAlignment.Inset;
                e.Graphics.DrawArc(p, x, y, size, size, markLocation + StartAngle, MarkThickness);
                markLocation += spacing;
            }
        }
    }

    protected virtual void PaintNeedle(PaintEventArgs e, float x, float y)
    {
        int size = ClientSize.Height - GaugeThickness;
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

        //Draw needle.
        if (Value >= MaxValue) Value = MaxValue;
        using (Pen p = new Pen(NeedleColor, NeedleLength))
        {
            p.Alignment = PenAlignment.Inset;
            e.Graphics.DrawArc(p, x, y, size, size, StartAngle + GetNeedleAngle(), NeedleSweep);
        }
        base.OnPaint(e);
    }
    #endregion

    protected float GetNeedleAngle()
    {
        float percentage = (float)(Value / MaxValue);
        float angle = SweepAngle * percentage;

        return angle;
    }
}
  • Been years since I did WebForms, but sounds like related to Invalidate since it paints whenever you give the control focus. Googling turned up some answers here. I'd read through and try to get an understanding of how invalidate is **supposed** to work before you start trying these workarounds, although you could try the easier ones just to determine if that really is the issue here. I remember some of the suggestions are dirty hacks and will cause flickering versus figuring out the right way: https://stackoverflow.com/q/6157263/84206 – AaronLS Jan 02 '19 at 18:59
  • You have to call Invalidate to get the paint message to be sent. Otherwise, it will only paint when there is a disruption, like a focus change, etc. – LarsTech Jan 02 '19 at 19:01
  • I should probably have added that every property is setup as "get => _property; set { _property = value; Invalidate(); } } – Alexis Shepard Jan 02 '19 at 19:04
  • Added the complete code for the DialGauge class. – Alexis Shepard Jan 02 '19 at 19:07
  • I think I'm confused at what I'm looking at. Is this two controls next to each other? Or two controls overlapping each other? Or one control that paints two gauges? – LarsTech Jan 02 '19 at 19:13
  • There is one form (Display), on this form there are TWO instances of the DialGauge control. there is CPU_Load and GPU_Load. CPU_Load works fine. GPU_Load does not. They do not overlap, they don't even touch. They are also the only two controls on the form. – Alexis Shepard Jan 02 '19 at 19:19
  • 1
    At design time, calling `Invalidate()` in not enough (while it is at run-time). You'll have to ask the Form to repaint itself to update your control. See here: [Translucent circle with text](https://stackoverflow.com/a/51435842/7444103), the `private void NotifyPropertyChanged(string PropertyName)` method. The Custom Control also calls `this.FindForm()?.Refresh();`. **Note that this is a simplified method**, it just exposes the *problem*. You actually need a custom designer attached to your Custom/User Control that acts differently at design-time and run-time. – Jimi Jan 02 '19 at 19:26
  • @Jimi I'm Working on implementing your suggestion and testing it out. – Alexis Shepard Jan 02 '19 at 20:16

0 Answers0