C#: 重写 ProgressBar 的 OnPaint 方法无效?

7
我想创建一个能够在其上绘制文字的进度条很容易。然而,我不太确定这里发生了什么......
我添加了以下两个覆盖:
    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);
    }

然而,我没有看到任何文本,并且该方法似乎根本没有被调用。这是怎么回事?


更新:感谢两位答案,我已经使用SetStyle(ControlStyles.UserPaint,true)实际调用了OnPaint,并通过发送new Rectangle(0,0,Width,Height)而不是Bounds 将文本绘制在正确的位置。

我现在有文本了,但是ProgressBar消失了...而且重点是要将文本放在ProgressBar上方。有什么方法可以解决这个问题吗?

5个回答

14
你可以重写WndProc并捕获WmPaint消息。
下面的示例在进度条中心绘制了Text属性。
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;
        }
    }
}

这绝对是最佳解决方案。 - Kosmo零
运行得非常好!“这个”应该是问题的答案。 - Fabien Teulieres

7

我需要自己完成这个任务,因为我找不到任何例子,所以我想发布一个简化的解决方案。如果使用ProgressBarRenderer类,它实际上非常简单:

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 ); 
        }
    }
}

你的问题解决了吗?我的解决方案在几台不同的机器上测试过,似乎都能正常工作。 - Maciek Talaska
我没有尝试过它,我只是发布了我用来手动绘制进度条的代码,然后添加了文本部分。 - Ed S.
这是我见过的最佳解决方案。 - Clinton Ward
我很喜欢这个。这正是我在寻找的。干得好,@EdS。 - pradeepradyumna

2
你的问题在于将Bounds作为矩形参数传入。Bounds包含控件的高度和宽度,这是你想要的,但它还包含控件相对于父窗体的顶部和左侧属性,因此你的“Hello”被偏移了控件在其父窗体上的偏移量。
请用new Rectangle(0, 0, this.Width, this.Height)替换Bounds,然后你应该能看到你的“Hello”。

1

看起来,如果你调用了'SetStyle(ControlStyles.UserPaint, true)',为ProgressBar实现的标准OnPaint方法将无法被调用(使用base.OnPaint(e)根本不起作用)。最奇怪的是,即使你实际上创建了一个UserControl,并尝试在进度条上绘制一些文本...似乎也不起作用...当然你可以在其上方放置一个Label...但我想这实际上并不是你想要实现的。

好吧,看起来我已经成功解决了这个问题。虽然有点复杂。首先,你需要创建一个透明的Label控件。代码如下:

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;
        }
    }
}

第二件事是创建UserControl,在其上放置一个ProgressBar(Dock = Fill) - 这将是我们将使用的控件,而不是标准的ProgressBar。 代码:
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; 
        }
    }
}

ProgressBar 的奇怪之处在于它会“覆盖”放置在其上的控件,因此需要将 ProgressBar 发送到后面,并将标签控件置于前面。目前我还没有找到更优雅的解决方案。
这个方法可行,标签被显示在了 ProgressBar 上,标签控件的背景是透明的,所以我认为它看起来就像你想要的那样 :)
如果您愿意,我可以分享我的示例代码...
顺便说一下,我提到的 ProgressBar 控件的这种奇怪行为导致无法使用 Graphics 对象在从 ProgressBar 派生的控件上绘制任何内容。文本(或使用 Graphics 对象绘制的任何内容)实际上正在被绘制,但是...在 ProgressBar 控件的后面(如果您仔细观察,您可能会看到这些用户绘制的东西在 ProgressBar 需要重新绘制自己时闪烁)。

好的,这实际上是我想要实现的。问题在于当我把一个标签放在它上面时,进度条就被隐藏了。我尝试创建一个透明的标签,但那也不起作用,因为一旦进度条开始改变,标签就会消失。 - Svish
如果您创建了一个UserControl,请尝试首先放置一个ProgressBar(也许是'SendItBack'),然后将Label控件放置到ProgressBar上。应该可以工作。问题是如何使Label的背景透明 - 我正在为此寻找解决方案... - Maciek Talaska
嗯,这很有趣...以后得试试这个 :) - Svish
哦,我已经在我的博客上发布了它。如果您感兴趣,您也可以在那里找到完整的源代码:http://notonlyzeroesandones.site40.net/2009/10/05/transparent-windowsforms-controls-drawing-on-top-of-progressbar/ - Maciek Talaska

0
这里有另一个解决方案,以及其他人的建议。我对进度条控件进行了子类化以使其工作。我从各个地方混合和匹配代码来实现这一点。绘制事件可能会更加简洁,但这取决于你自己去做 ;)
   public class LabeledProgressBar: ProgressBar
   {
      private string labelText;

      public string LabelText
      {
         get { return labelText; }
         set { labelText = value; }
      }

      public LabeledProgressBar() : base()
      { 
         this.SetStyle(ControlStyles.UserPaint, true);
         this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);

         this.Paint += OnLabelPaint;
      }

      public void OnLabelPaint(object sender, PaintEventArgs e)
      {
         using(Graphics gr = this.CreateGraphics())
        {
         string str = LabelText + string.Format(": {0}%", this.Value);
         LinearGradientBrush brBG = new LinearGradientBrush(e.ClipRectangle, 
             Color.GreenYellow, Color.Green, LinearGradientMode.Horizontal);
         e.Graphics.FillRectangle(brBG, e.ClipRectangle.X, e.ClipRectangle.Y, 
             e.ClipRectangle.Width * this.Value / this.Maximum, e.ClipRectangle.Height);
         e.Graphics.DrawString(str, SystemFonts.DefaultFont,Brushes.Black,  
            new PointF(this.Width / 2 - (gr.MeasureString(str, SystemFonts.DefaultFont).Width / 2.0F),
            this.Height / 2 - (gr.MeasureString(str, SystemFonts.DefaultFont).Height / 2.0F)));
         }
       }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接