0

I'm trying to extend the C# Button class so that I can color the button, with up to 3 colours, according to the percentages provided. It appears to work apart from the button text is not shown. Please can somebody advise how to display the text? Also, I'm unsure as to whether Refresh() is the correct method to call in the Setter methods to trigger a redraw with the new percentage provided? Many thanks.

class TriColorsButton : Button
{

    private double percentOfLeftColor, percentOfRightColor;

    public TriColorsButton(String text, double percentOfLeftColor, double percentOfRightColor)
    {
        this.Text = text;
        this.percentOfLeftColor = percentOfLeftColor;
        this.percentOfRightColor = percentOfRightColor;
    }


    protected override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);

        Brush brush;
        RectangleF rectFirst, rectSecond, rectThird;
        Color c1, c2, c3;
        // create the first rectangle
        rectFirst = new RectangleF(0, 0, this.Width, this.Height);
        // create the second rectangle
        int callWidth = (int)(this.Width * (100 - percentOfRightColor) / 100);
        rectSecond = new RectangleF(0, 0, callWidth, this.Height);
        // create the third rectangle
        int leftWidth = (int)(this.Width * percentOfLeftColor / 100);
        rectThird = new RectangleF(0, 0, leftWidth, this.Height);

        // create the colors
        c1 = Constants.altFoldColor;
        c2 = Constants.altCallColor;
        c3 = Constants.altRaiseColor;

        // create the brush
        brush = new SolidBrush(c1);
        // fill the segment
        pe.Graphics.FillRectangle(brush, rectFirst);
        // create the brush
        brush = new SolidBrush(c2);
        // fill the segment
        pe.Graphics.FillRectangle(brush, rectSecond);
        // create the brush
        brush = new SolidBrush(c3);
        // fill the segment
        pe.Graphics.FillRectangle(brush, rectThird);

        // dispose of the brush
        brush.Dispose();

    }

    public void SetPercentOfLeftColor(double percentOfLeftColor)
    {
        this.percentOfLeftColor = percentOfLeftColor;
        Refresh();
    }

    public double GetPercentOfLeftColor()
    {
        return percentOfLeftColor;
    }

    public void SetPercentOfRightColor(double percentOfRightColor)
    {
        this.percentOfRightColor = percentOfRightColor;
        Refresh();
    }

    public double GetPercentOfRightColor()
    {
        return percentOfRightColor;
    }

}

Also, although this paints the colors that I want, I lose the original look and feel of a standard button. I'd like to retain the standard button look and feel such as the on-hover highlighting and border of a standard button. How can I achieve this?

EDIT: Upon suggestion I tried to draw a background image for the button instead. This had a strange effect of causing most of my form to go white. It would redraw and look better if I moved the form on screen but other updates wouldn't happen appropriately. This is what I tried:

protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);

            Image bmp = new Bitmap(Width, Height);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                Brush brush;
                RectangleF rectFirst, rectSecond, rectThird;
                Color c1, c2, c3;
                // create the first rectangle
                rectFirst = new RectangleF(0, 0, this.Width, this.Height);
                // create the second rectangle
                int callWidth = (int)(this.Width * (100 - percentOfRightColor) / 100);
                rectSecond = new RectangleF(0, 0, callWidth, this.Height);
                // create the third rectangle
                int leftWidth = (int)(this.Width * percentOfLeftColor / 100);
                rectThird = new RectangleF(0, 0, leftWidth, this.Height);

                // create the colors
                c1 = Constants.altFoldColor;
                c2 = Constants.altCallColor;
                c3 = Constants.altRaiseColor;
                // create the brush
                brush = new SolidBrush(c1);
                // fill the segment
                g.FillRectangle(brush, rectFirst);
                // create the brush
                brush = new SolidBrush(c2);
                // fill the segment
                g.FillRectangle(brush, rectSecond);
                // create the brush
                brush = new SolidBrush(c3);
                // fill the segment
                g.FillRectangle(brush, rectThird);

                // dispose of the brush
                brush.Dispose();
            }
            this.BackgroundImage = bmp;
            
        }

EDIT2: I figured out how to achieve what I wanted. I added a method that draws and sets the background and called it from the constructor. The OnPaint method no longer requires an override:

private void SetBackgroundImage()
        {
            Image bmp = new Bitmap(Width, Height);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                Brush brush;
                RectangleF rectFirst, rectSecond, rectThird;
                Color c1, c2, c3;
                // create the first rectangle
                rectFirst = new RectangleF(0, 0, this.Width, this.Height);
                // create the second rectangle
                int callWidth = (int)(this.Width * (100 - percentOfRightColor) / 100);
                rectSecond = new RectangleF(0, 0, callWidth, this.Height);
                // create the third rectangle
                int leftWidth = (int)(this.Width * percentOfLeftColor / 100);
                rectThird = new RectangleF(0, 0, leftWidth, this.Height);

                // create the colors
                c1 = Constants.altFoldColor;
                c2 = Constants.altCallColor;
                c3 = Constants.altRaiseColor;
                // create the brush
                brush = new SolidBrush(c1);
                // fill the segment
                g.FillRectangle(brush, rectFirst);
                // create the brush
                brush = new SolidBrush(c2);
                // fill the segment
                g.FillRectangle(brush, rectSecond);
                // create the brush
                brush = new SolidBrush(c3);
                // fill the segment
                g.FillRectangle(brush, rectThird);

                // dispose of the brush
                brush.Dispose();
            }
            this.BackgroundImage = bmp;
        }
Steve W
  • 1,108
  • 3
  • 13
  • 35
  • 2
    I cannot see any `DrawString` calls in your OnPaint method, so why would you expect any text on your button? – JonasH Jul 16 '21 at 11:00
  • Anything you draw gets drawn after ie over the original Text, so you need to do it yourself, best with TextRendered.DrawText.. – TaW Jul 16 '21 at 11:04
  • is there a draw text method in the Button class that I can call? – Steve W Jul 16 '21 at 11:32

1 Answers1

0

Overriding OnPaint in windows forms is mostly an all or nothing affair. I.e. if you need custom drawing, you need to draw everything, including the text. In this regard WPF is a bit better since it allows for better modularity.

This should be fairly easy to fix by inserting a call to Graphics.DrawString or TextRendered.DrawText at the end of your OnPaint method, where TextRendered.DrawText should be more consistent with other controls. You might also need some way to ensure sufficient contrast, like adding a drop shadow or a background rectangle. Note that drawing is done in the same order as the calls are made, i.e. later draw calls will be on top of earlier, so your call to base.OnPaint(pe); might be redundant if it is completely overdrawn anyway.

Also, I'm unsure as to whether Refresh() is the correct method to call in the Setter methods to trigger a redraw with the new percentage provided?

I usually use .Invalidate(). If I understand the documentation correctly, the difference is that Invalidate redraws the control at some later time, while Refresh redraws it immediately.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Hi Jonas. Thanks for your answer. I've got the text to paint now but lost the Button border, on hover highlighting, etc. I'd like to duplicate the original look of the Button, just with the coloured strips added. Any suggestions on how I can achieve that? – Steve W Jul 16 '21 at 13:01
  • @SteveW, maybe draw to a new image instead of drawing directly to the button and set it as background in code? – Mat J Jul 16 '21 at 13:23
  • Hi Mat. Would you be able to show me how please? – Steve W Jul 16 '21 at 14:24