C#: Overriding OnPaint on ProgressBar not working?

14,485

Solution 1

Your problem is that you're passing in Bounds as your Rectangle parameter. Bounds contains the Height and Width of your control, which is what you want, but it also contains the Top and Left properties of your control, relative to the parent form, so your "Hello" is being offset on the control by however much your control is offset on its parent form.

Replace Bounds with new Rectangle(0, 0, this.Width, this.Height) and you should see your "Hello".

Solution 2

You could override WndProc and catch the WmPaint message.

The example below paints the Text property of the progressbar in its center.

public class StatusProgressBar : ProgressBar
{
    const int WmPaint = 15;

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        switch (m.Msg)
        {
            case WmPaint:
                using (var graphics = Graphics.FromHwnd(Handle))
                {
                    var textSize = graphics.MeasureString(Text, Font);

                    using(var textBrush = new SolidBrush(ForeColor))
                        graphics.DrawString(Text, Font, textBrush, (Width / 2) - (textSize.Width / 2), (Height / 2) - (textSize.Height / 2));
                }
                break;
        }
    }
}

Solution 3

I needed to do this myself and I thought that I would post a simplified example of my solution since I could not find any examples. It is actually pretty simple if you use the ProgressBarRenderer class:

class MyProgressBar : ProgressBar
{
    public MyProgressBar()
    {
        this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Rectangle rect = this.ClientRectangle;
        Graphics g = e.Graphics;

        ProgressBarRenderer.DrawHorizontalBar( g, rect );
        rect.Inflate(-3, -3);
        if ( this.Value > 0 )
        {
            Rectangle clip = new Rectangle( rect.X, rect.Y, ( int )Math.Round( ( ( float )this.Value / this.Maximum ) * rect.Width ), rect.Height );
            ProgressBarRenderer.DrawHorizontalChunks(g, clip);
        }

        // assumes this.Maximum == 100
        string text = this.Value.ToString( ) + '%';

        using ( Font f = new Font( FontFamily.GenericMonospace, 10 ) )
        {
            SizeF strLen = g.MeasureString( text, f );
            Point location = new Point( ( int )( ( rect.Width / 2 ) - ( strLen.Width / 2 ) ), ( int )( ( rect.Height / 2 ) - ( strLen.Height / 2 ) ) );
            g.DrawString( text, f, Brushes.Black, location ); 
        }
    }
}

Solution 4

It seems that if you call 'SetStyle(ControlStyles.UserPaint, true)' the standard OnPaint method implemented for ProgressBar could not be invoked (using base.OnPaint(e) does not work at all). The strangest thing is that even if you actually create a UserControl, and try to draw draw some text upon the progress bar... it doesn't seem to work too... Of course you may place a Label on top of it... but I suppose it is not actually what you wanted to achieve.

Ok, it seems that I have managed to solve this problem. It is although a little complicated. First you need to create a transparent Label control. Code below:

public class TransparentLabel : System.Windows.Forms.Label
{
    public TransparentLabel()
    {
        this.SetStyle(ControlStyles.Opaque, true);
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x20;
            return cp;
        }
    }
}

Second thing is to create UserControl, place a ProgressBar on it (Dock=Fill) - this will be the control that we will use instead of standard ProgressBar. Code:

public partial class UserControl2 : UserControl
{
    public UserControl2()
    {
        InitializeComponent();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        this.progressBar1.SendToBack();
        this.transparentLabel1.BringToFront();
        this.transparentLabel1.Text = this.progressBar1.Value.ToString();
        this.transparentLabel1.Invalidate();
   }

    public int Value
    {
        get { return this.progressBar1.Value; }
        set
        {
            this.progressBar1.Value = value; 
        }
    }
}

The strange thing with ProgressBar is that it 'overdraws' the controls that are being placed upon it, so it is needed to send progressbar to back, and bring the label control to front. I haven't found more elegant solution at the moment. This works, the label is being displayed on the progressbar, the background of the label control is transparent, so I think it looks like you wanted it to look :)

I may share my sample code if you wish...

Oh, btw. this strange behaviour of ProgressBar control that I have mentioned, is responsible for that it is not possible to use Graphics object to draw anything on a control that derives from ProgressBar. The text (or whatever you draw using Graphics object) is actually being drawn but... behind the ProgressBar control (if you take a closer look, you may see this user drawn things flickering when the Value of the ProgressBar changes and it need to repaint itself).

Share:
14,485
Lemon
Author by

Lemon

Software Developer, Geek, HSP, SDA, ..., open, honest, careful, perfectionist, ... Currently into indoor rowing and rock climbing, just to mention something non-computer-related... Not the best at bragging about myself... so... not sure what more to write... 🤔

Updated on June 13, 2022

Comments

  • Lemon
    Lemon almost 2 years

    Was thinking it should be pretty easy to create a ProgressBar that drew some text upon itself. However, I am not quite sure what is happening here...

    I added the following two overrides:

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            base.OnPaintBackground(pevent);
            var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.SingleLine | TextFormatFlags.WordEllipsis;
            TextRenderer.DrawText(pevent.Graphics, "Hello", Font, Bounds, Color.Black, flags);
        }
    
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.SingleLine | TextFormatFlags.WordEllipsis;
            TextRenderer.DrawText(e.Graphics, "Hello", Font, Bounds, Color.Black, flags);
        }
    

    However, I get no text, and the methods doesn't even seem to be called. What is going on here?


    Update: Thanks to the two answers so far, I have gotten it to actually call the OnPaint by using SetStyle(ControlStyles.UserPaint, true), and I have gotten it to draw the text in the right place by sending in new Rectangle(0, 0, Width, Height) instead of Bounds.

    I do get text now, but the ProgressBar is gone... and the point was kind of to have the text on top of the ProgressBar. Any idea how I can solve this?

  • Lemon
    Lemon over 14 years
    Well, it actually is what I want to achieve. Problem is that when I put a label on top of it, the progressbar is hidden. I tried to create a transparent label, but that didn't work either, cause it disappeared once the progressbar started to change.
  • Maciek Talaska
    Maciek Talaska over 14 years
    If you create a UserControl, try to place a ProgressBar first (and maybe 'SendItBack') then place a Label control onto ProgressBar. Should work. The problem is, how to make Label's background transparent - I am working on a solution for it at the moment...
  • Lemon
    Lemon over 14 years
    Hm, now that is interesting... will have to try this one out later :)
  • Maciek Talaska
    Maciek Talaska over 14 years
    oh, I have posted it on my blog. You may also find a complete source there if you're interested: notonlyzeroesandones.site40.net/2009/10/05/…
  • Maciek Talaska
    Maciek Talaska about 14 years
    and didn't my solution work for you? I have tested in on couple of different machines and it seemed to work fine on all of them.
  • Ed S.
    Ed S. about 14 years
    I didn't try it, I just posted the code I had used to draw a progress bar manually and then added the text bit.
  • Clinton Ward
    Clinton Ward over 11 years
    This is the best solution i've seen.
  • Kosmo零
    Kosmo零 over 10 years
    This is the best solution for sure
  • pradeepradyumna
    pradeepradyumna almost 6 years
    I loved this. This is exactly what I was looking for. Good job @EdS.
  • Fabien Teulieres
    Fabien Teulieres almost 4 years
    Works like a charm! This should be the answer to the question.