2022年5月

最近在学习使用C#做GUI。其中想在进度条控件上同时显示点文本信息,类似下面的效果:

以下是各种折腾:

尝试1:直接赋值text

ProgressBar控件的属性列表里面是看不到Text属性的,但程序里仍然可以设置:

progressBar1.Text = "test";

编译运行之,啥也没有显示...

尝试2:自定义Paint事件

查阅各种资料得知,控件可以添加自定义的Paint事件:

progressBar1.Paint += new PaintEventHandler(my_paint);
......
private void my_paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.DrawString(......);
}

编译运行之,还是啥也没有显示...

调试发现,my_paint根本就没被调用。这是为什么呢?这是按照微软的例子写的啊!
(https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.control.paint?view=windowsdesktop-6.0)

再次查找各种资料,在dotnet源码里面发现,ProgressBar控件其实就是对win32的进度条的包装。
所有操作都是通过发消息给进度条窗口来实现的,其本身根本就没有OnPaint事件,自然也不会调用你自己注册的Paint了。

尝试3:重载ProgressBar类

再次查找各种资料。这次主要是看别人如何实现的。基本上都是重载ProgressBar类,然后再重载WndProc,在OnPaint事件里面去画Text。比如这里的一个实现:
https://doogalbellend.blogspot.com/2010/10/winforms-progressbar-with-text.html

这样确实实现了预期的效果,但也有不尽人意的地方。比如不能用设计工具来放置进度条了。或者你先放置好,然后再改代码,改为你重载的新进度条类。

尝试4:使用NativeWindow

偶尔发现有用NativeWindow实现的。这几乎是最优化的实现方案了。NativeWindow是对win32窗口的一个封装,并且它可以使用别人的窗口Handle:

public class ProgresBarExt : NativeWindow
{
    private ProgressBar c;
    // 创建时,传入一个已有的ProgressBar实例,并使用它的窗口Handle。
    public ProgresBarExt(ProgressBar progressBar)
    {
        c = progressBar;
        this.AssignHandle(c.Handle);
    }

    private const int WmPaint = 15;
    protected override void WndProc(ref Message m)
    {
        // 先画本体
        base.WndProc(ref m);
        if ((m.Msg == WmPaint))
        {
            // 再画text
            using (var g = Graphics.FromHwnd(Handle))
            {
                string str = c.Value.ToString();
                int x = c.Width / 2 - (int)g.MeasureString(str, SystemFonts.DefaultFont).Width / 2;
                int y = c.Height/2 - SystemFonts.DefaultFont.Height / 2;
                g.DrawString(str, SystemFonts.DefaultFont, Brushes.Black, x, y);
            }
        }
    }

}

如何使用:

private ProgresBarExt mProgresBar1;
......
public Form1()
{
    InitializeComponent();

    mProgresBar1 = new ProgresBarExt(progressBar1);
}

以后直接对progressBar1.Text赋值就可以显示你的text了。