如何在C# .NET 3.5中改变进度条的颜色?

109

我希望在我的进度条上做两件事情。

  1. 将绿色改为红色。
  2. 移除方块并将其变成单一颜色。

如何实现这两个目标的任何信息都将不胜感激!

谢谢。

22个回答

138

好的,我花了一些时间阅读所有的答案和链接。以下是我从中得到的内容:

示例结果

被接受的答案禁用了视觉样式,它确实允许你将颜色设置为任何你想要的,但结果看起来很简单:

enter image description here

使用以下方法,您可以获得类似于这样的内容:

enter image description here

如何操作

首先,如果您还没有,请包含以下内容:using System.Runtime.InteropServices;

其次,您可以创建此新类,或将其代码放入现有的static非泛型类中:

public static class ModifyProgressBarColor
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr w, IntPtr l);
    public static void SetState(this ProgressBar pBar, int state)
    {
        SendMessage(pBar.Handle, 1040, (IntPtr)state, IntPtr.Zero);
    }
}

现在,要使用它,只需调用:

progressBar1.SetState(2);

请注意SetState中的第二个参数,1 = 正常(绿色);2 = 错误(红色);3 = 警告(黄色)

19
您创建了一个扩展方法,因此调用应该是progressBar1.SetState(2); 除此之外,回答非常好! - Edgar Hernandez
5
需要注意的是,此功能仅适用于Vista及以上版本。您可以通过搜索 PBM_SETSTATE 查看完整文档。 - Djof
+1。谢谢!给其他人的提示:如果在运行时出现此错误,请将其粘贴到命名空间的末尾而不是开头:System.Resources.MissingManifestResourceException。 - Andrew Bucklin
1
这很棒,但它会部分破坏我的进度条,它永远不会填满100%.... - Mecanik
1
public const uint PBM_SETSTATE = 0x0410; // 1040 - Andrei Krasutski
显示剩余7条评论

92

由于之前的答案似乎在 Visual Styles 中不起作用。你可能需要创建自己的类或扩展进度条:

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

    protected override void OnPaint(PaintEventArgs e)
    {
        Rectangle rec = e.ClipRectangle;

        rec.Width = (int)(rec.Width * ((double)Value / Maximum)) - 4;
        if(ProgressBarRenderer.IsSupported)
           ProgressBarRenderer.DrawHorizontalBar(e.Graphics, e.ClipRectangle);
        rec.Height = rec.Height - 4;
        e.Graphics.FillRectangle(Brushes.Red, 2, 2, rec.Width, rec.Height);
    }
}

编辑:更新代码,使进度条使用视觉样式作为背景


2
谢谢这个,这是我可以在我的解决方案中使用的东西。 - Irwin
太酷了,谢谢。这真的帮助了我目前正在处理的一些事情,尽管我改变了它,每次重绘整个控件而不仅仅是由e.ClipRectangle指定的区域。否则,在另一个窗口或屏幕边缘使部分控件无效后,它就不能正确地重绘。 - Matt Blaine
@Matt Blaine:你能否将你的修改作为答案的编辑发布一下?我相信会有足够多的人对你的完整解决方案感兴趣。 - Marek
@Marek 刚刚看到了你的评论。我没有足够的声望来编辑答案,所以我发了自己的。谢谢。https://dev59.com/THRA5IYBdhLWcg3w8SeJ#2498036 - Matt Blaine
3
我知道这个很老了,也知道这只是一个小问题(因为最小值几乎总是零);但为了更加准确,宽度比例应该是**(((double)Value - (double)Minimum) / ((double)Maximum - (double)Minimum))**。 :) - Jesse Chisholm
显示剩余3条评论

41
这是一份无闪烁的最常用代码版本,你可以在问题的回答中找到。所有功劳归于那些出色回答的发帖者,感谢 Dusty、Chris、Matt 和 Josh!
像“Fueled”在评论中的要求一样,我也需要一个更加专业的版本。这份代码保持了之前代码的样式,但增加了离屏图像渲染和图形缓冲(并正确地处理了图形对象)。
结果:所有优点,没有闪烁。 :)
public class NewProgressBar : ProgressBar
{
    public NewProgressBar()
    {
        this.SetStyle(ControlStyles.UserPaint, true);
    }

    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
        // None... Helps control the flicker.
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        const int inset = 2; // A single inset value to control teh sizing of the inner rect.

        using (Image offscreenImage = new Bitmap(this.Width, this.Height))
        {
            using (Graphics offscreen = Graphics.FromImage(offscreenImage))
            {
                Rectangle rect = new Rectangle(0, 0, this.Width, this.Height);

                if (ProgressBarRenderer.IsSupported)
                    ProgressBarRenderer.DrawHorizontalBar(offscreen, rect);

                rect.Inflate(new Size(-inset, -inset)); // Deflate inner rect.
                rect.Width = (int)(rect.Width * ((double)this.Value / this.Maximum));
                if (rect.Width == 0) rect.Width = 1; // Can't draw rec with width of 0.

                LinearGradientBrush brush = new LinearGradientBrush(rect, this.BackColor, this.ForeColor, LinearGradientMode.Vertical);
                offscreen.FillRectangle(brush, inset, inset, rect.Width, rect.Height);

                e.Graphics.DrawImage(offscreenImage, 0, 0);
            }
        }
    }
}

这是最接近微软原始实现的版本。 - Yakup Ünyılmaz
1
使用using块和调用Dispose实际上有点冗余。不过回答很好。 - Kevin Stricker
1
我知道这很老;我也知道这是个小问题(因为最小值几乎总是零);但是宽度比例因子应该是**(((double)Value - (double)Minimum) / ((double)Maximum - (double)Minimum))**,这样才能完全正确。 :) - Jesse Chisholm
当我使用TextRenderer.DrawText()将文本绘制到离屏图像时,最终在屏幕上的结果看起来像素化,并不像使用完全相同字体的标签。直接将所有内容绘制到e.Graphics而不使用离屏图像可以得到正确的结果...有什么想法吗? - CuriousGeorge
1
是的,我确实尝试了未经缩放的blitting。最终,我切换到了WPF,因此我所需的一切都已内置。 - CuriousGeorge
显示剩余4条评论

31
在设计器中,您只需要将ForeColor属性设置为您想要的任何颜色即可。对于红色,有一个预定义的颜色。
要在代码(C#)中执行此操作,请执行以下操作:
pgs.ForeColor = Color.Red;

编辑:对了,还要将样式设置为continuous。在代码中,像这样:

pgs.Style = System.Windows.Forms.ProgressBarStyle.Continuous;

另一个编辑:你还需要从你的Program.cs(或类似文件)中删除一行代码Application.EnableVisualStyles()。如果你不能这样做,因为你希望应用程序的其余部分具有视觉样式,那么我建议你自己绘制控件,或者转向使用WPF,因为在WPF中处理这种情况很容易。你可以在codeplex上找到关于自绘进度条的教程。


3
前景色为红色;背景色为蓝色;样式为连续。没有改变,它仍然像我从未触碰过它一样。 - Ivan Prodanov
3
好的,我看到问题了。为了使属性与此配合使用,您需要禁用视觉样式。它可能在使用您选择的任何主题进行绘画。在 Program.cs 文件中有一行代码:Application.EnableVisualStyles(),请删除该行,就可以正常工作了。否则,您需要自己绘制控件。另一个选择是(如果可能的话)使用 WPF,因为它可以让您轻松完成这种操作。 - dustyburwell
请给我更多关于“如何自己绘制它”的信息。 - Ivan Prodanov
1
这是一个关于自绘进度条的教程。该过程对于评论来说太复杂了。http://www.codeproject.com/KB/cpp/VistaProgressBar.aspx - dustyburwell
1
很遗憾,Application.EnableVisualStyles()是这个解决方案的关键部分。不幸的是,这会破坏所有其他样式。 - mr.user1065741

20

使用Matt Blaine和Chris Persichetti的答案,我创建了一个进度条,它看起来更好一些,同时允许无限颜色选择(基本上我改变了Matt解决方案中的一行代码):

ProgressBarEx:

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace QuantumConcepts.Common.Forms.UI.Controls
{
    public class ProgressBarEx : ProgressBar
    {
        public ProgressBarEx()
        {
            this.SetStyle(ControlStyles.UserPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            LinearGradientBrush brush = null;
            Rectangle rec = new Rectangle(0, 0, this.Width, this.Height);
            double scaleFactor = (((double)Value - (double)Minimum) / ((double)Maximum - (double)Minimum));

            if (ProgressBarRenderer.IsSupported)
                ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rec);

            rec.Width = (int)((rec.Width * scaleFactor) - 4);
            rec.Height -= 4;
            brush = new LinearGradientBrush(rec, this.ForeColor, this.BackColor, LinearGradientMode.Vertical);
            e.Graphics.FillRectangle(brush, 2, 2, rec.Width, rec.Height);
        }
    }
}

用法:

progressBar.ForeColor = Color.FromArgb(255, 0, 0);
progressBar.BackColor = Color.FromArgb(150, 0, 0);

结果

你可以使用任何你喜欢的渐变!

下载

https://skydrive.live.com/?cid=0EDE5D21BDC5F270&id=EDE5D21BDC5F270%21160&sc=documents#


2
我知道这很老;我也知道这是个小问题(因为最小值几乎总是零);但是宽度比例因子应该是 (((double)Value - (double)Minimum) / ((double)Maximum - (double)Minimum)) 才能完全正确。 :) - Jesse Chisholm
根据您的建议更新了答案。 - Josh M.
1
我知道这很老,但是--一旦它达到4的值,这段代码将会出错,因为LinearGradientBrush将rec宽度读取为0。最简单的解决方法是在代码中不要进行4px的“填充”,而是将其放置在带有填充的面板或其他容器中(如果您想要边框),并将rec.Width = (int)((rec.Width * scaleFactor) - 4)更改为rec.Width = (int)(rec.Width * scaleFactor) + 1 - MiDri

16

对dustyburwell的回答进行修改(我没有足够的声望来自己编辑)。像他的回答一样,它适用于启用了“视觉样式”的情况。您可以在任何表单的设计视图中设置进度条的ForeColor属性。

using System;
using System.Windows.Forms;
using System.Drawing;

public class ProgressBarEx : ProgressBar
{
    private SolidBrush brush = null;

    public ProgressBarEx()
    {
        this.SetStyle(ControlStyles.UserPaint, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (brush == null || brush.Color != this.ForeColor)
            brush = new SolidBrush(this.ForeColor);

        Rectangle rec = new Rectangle(0, 0, this.Width, this.Height);
        if (ProgressBarRenderer.IsSupported)
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rec);
        rec.Width = (int)(rec.Width * ((double)Value / Maximum)) - 4;
        rec.Height = rec.Height - 4;
        e.Graphics.FillRectangle(brush, 2, 2, rec.Width, rec.Height);
    }
}

这对于改变前景色来说效果很好,但它会频繁闪烁,而默认的ProgressBar控件不会。有什么解决方法吗? - Fueled
5
我已经找到了解决闪烁问题的方法:在用户定义的进度条的 ControlStyles 中添加双缓冲,像这样:SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, ...)。 - Fueled
我知道这很老;我也意识到这只是一个小细节(因为最小值几乎总是零);但是如果要追求完美,宽度比例因子应该为 **(((double)Value - (double)Minimum) / ((double)Maximum - (double)Minimum))**。 :) - Jesse Chisholm

6

我刚把这个放到一个静态类中。

  const int WM_USER = 0x400;
  const int PBM_SETSTATE = WM_USER + 16;
  const int PBM_GETSTATE = WM_USER + 17;

  [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
  static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

  public enum ProgressBarStateEnum : int
  {
   Normal = 1,
   Error = 2,
   Paused = 3,
  }

  public static ProgressBarStateEnum GetState(this ProgressBar pBar)
  {
   return (ProgressBarStateEnum)(int)SendMessage(pBar.Handle, PBM_GETSTATE, IntPtr.Zero, IntPtr.Zero);
  }

  public static void SetState(this ProgressBar pBar, ProgressBarStateEnum state)
  {
   SendMessage(pBar.Handle, PBM_SETSTATE, (IntPtr)state, IntPtr.Zero);
  }

1
+1:我认为大多数人所说的“红色”是指显示错误状态。 - quantum

3
通常进度条要么是主题化的,要么遵循用户的颜色偏好。因此,如果要更改颜色,您需要关闭视觉样式并设置 ForeColor 或自己绘制控件。
至于连续样式而不是块状样式,您可以设置 Style 属性:
pBar.Style = ProgressBarStyle.Continuous;

2

更改颜色和值(即时更改)

在顶部添加using System.Runtime.InteropServices;

使用ColorBar.SetState(progressBar1, ColorBar.Color.Yellow, myValue);进行调用

我注意到如果您更改进度条的值(大小),则如果它处于除默认绿色外的其他颜色,则不会更改。我采用了user1032613的代码并添加了一个值选项。

public static class ColorBar
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr w, IntPtr l);
    public enum Color { None, Green, Red, Yellow }

    public static void SetState(this ProgressBar pBar, Color newColor, int newValue)
    {
        if (pBar.Value == pBar.Minimum)  // If it has not been painted yet, paint the whole thing using defualt color...
        {                                // Max move is instant and this keeps the initial move from going out slowly 
            pBar.Value = pBar.Maximum;   // in wrong color on first painting
            SendMessage(pBar.Handle, 1040, (IntPtr)(int)Color.Green, IntPtr.Zero);
        }
        pBar.Value = newValue;
        SendMessage(pBar.Handle, 1040, (IntPtr)(int)Color.Green, IntPtr.Zero);     // run it out to the correct spot in default
        SendMessage(pBar.Handle, 1040, (IntPtr)(int)newColor, IntPtr.Zero);        // now turn it the correct color
    }

}

2
我们不得不将 pBar.Value = pBar.Maximum;SendMessage(pBar.Handle, 1040, (IntPtr)(int)Color.Green, IntPtr.Zero); 翻转到条件体内部,以便进度条显示正确的新颜色。 - DomTheDeveloper

2
编辑 听起来您正在使用具有绿色块状进度条的XP主题。尝试将UI样式切换到Windows Classic并再次测试,但您可能需要实现自己的OnPaint事件以在所有UI样式上获得所需效果。
或者,正如其他人指出的那样,禁用您的应用程序的VisualStyles。 原文 据我所知,进度条的渲染与您选择的Windows主题风格(win2K、xp、vista)是同步进行的。
您可以通过设置属性来更改颜色。
ProgressBar.ForeColor

我不确定你还能做更多的事情了...
进行一些谷歌搜索
这里有一篇来自微软知识库的文章,介绍如何创建一个“平滑”的进度条。

http://support.microsoft.com/kb/323116


@Eion,前景色=红色; 背景色=蓝色; 样式=连续。什么也没有改变,它保持着像我从未触碰过它一样的状态。 - Ivan Prodanov

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