DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

ProgressBarの色を変える

ProgressBarコントロールのバーの色は通常緑色で、ForeColorとBackColorプロパティの値は無視されます。ここでは、ProgressBarコントロールの色を変更する方法を幾つか紹介します。

アプリケーションのvisualスタイルを無効にする

アプリケーションのvisualスタイルを無効にすると、ForeColorとBackColorプロパティで指定した色でProgressBarコントロールが表示されるようになります。アプリケーションのvisualスタイルを無効にする方法は「コントロールの外観をビジュアルスタイル(XPスタイル)にする」で説明していますので、そちらを参考にして無効にしてください。通常は、エントリポイントメソッドのMainメソッドで呼び出している「Application.EnableVisualStyles」を削除することでvisualスタイルが無効になるでしょう。

補足:visualスタイルを無効にするとProgressBar.StyleプロパティをProgressBarStyle.Marqueeにしてもマーキースタイルになりません。
補足:「Application.EnableVisualStyles」がどこにあるかどうしても分からない場合は、「Application.EnableVisualStyles」という文字列を検索して探してください。

下の画像は、visualスタイルを無効にして、ForeColorプロパティをColor.Blue、BackColorプロパティをColor.Yellowにした時のProgressBarです。このように、visualスタイルを無効にすると見た目は悪くなります。

visualスタイルを無効にしたProgressBar

補足:バーを点線ではなく連続した線にしたい場合は、ProgressBar.StyleプロパティをProgressBarStyle.Continuousにします。

PBM_SETSTATEメッセージを使用して、ErrorやPausedの状態にする

WindowsメッセージPBM_SETSTATEを使うことで、プログレスバーの状態をエラーや一時停止(ポーズ)にすることができます。ProgressBarの状態をエラーにするとバーは赤っぽい色になり、一時停止にすると黄色っぽい色になります。

下の図で、上のプログレスバーがエラーで、下が一時停止の状態です。

状態をエラー、一時停止にしたプログレスバー

補足:「Progress Bars」にあるガイドラインによると、赤と黄色のプログレスバーは進行中の処理が中断していることを示すために使うようです。ユーザーによって回復が可能な中断の場合はプログレスバーを赤くします。ユーザーによって一時停止されていたり、何らかの理由(ネットワーク状態が悪いなど)で進行が妨げられている場合はプログレスバーを黄色くします。処理が中断し、回復できない場合は、プログレスバーは緑のままで、エラーメッセージを表示します。
補足:この方法でもマーキースタイルにはなりません。また、アニメ効果もありません。

この方法は、Windows Vista以降で有効です。

以下のコードでは、ProgressBar1の状態をエラーに、ProgressBar2の状態を一時停止にしています。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms
'Imports System.Runtime.InteropServices

<DllImport("user32.dll", CharSet:=CharSet.Unicode)> _
Private Shared Function SendMessage(hWnd As HandleRef, _
    Msg As UInt32, wParam As UInt32, lParam As IntPtr) As IntPtr
End Function

Private Const WM_USER As UInt32 = &H400
Private Const PBM_SETSTATE As UInt32 = WM_USER + 16
Private Const PBST_NORMAL As UInt32 = &H1
Private Const PBST_ERROR As UInt32 = &H2
Private Const PBST_PAUSED As UInt32 = &H3

Public Sub ChangeProgressState()
    'ProgressBar1の状態をエラーにする
    If ProgressBar1.IsHandleCreated Then
        SendMessage(New HandleRef(ProgressBar1, ProgressBar1.Handle), _
                    PBM_SETSTATE, PBST_ERROR, IntPtr.Zero)
    End If
    'ProgressBar2の状態を一時停止にする
    If ProgressBar2.IsHandleCreated Then
        SendMessage(New HandleRef(ProgressBar2, ProgressBar2.Handle), _
                    PBM_SETSTATE, PBST_PAUSED, IntPtr.Zero)
    End If
End Sub
C#
コードを隠すコードを選択
//using System;
//using System.Windows.Forms;
//using System.Runtime.InteropServices;

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(HandleRef hWnd,
    uint Msg, uint wParam, IntPtr lParam);

private const uint WM_USER = 0x400;
private const uint PBM_SETSTATE = WM_USER + 16;
private const uint PBST_NORMAL = 0x0001;
private const uint PBST_ERROR = 0x0002;
private const uint PBST_PAUSED = 0x0003;

public void ChangeProgressState()
{
    //ProgressBar1の状態をエラーにする
    if (ProgressBar1.IsHandleCreated)
    {
        SendMessage(new HandleRef(ProgressBar1, ProgressBar1.Handle),
            PBM_SETSTATE, PBST_ERROR, IntPtr.Zero);
    }
    //ProgressBar2の状態を一時停止にする
    if (ProgressBar2.IsHandleCreated)
    {
        SendMessage(new HandleRef(ProgressBar2, ProgressBar2.Handle),
            PBM_SETSTATE, PBST_PAUSED, IntPtr.Zero);
    }
}

Valueプロパティの値が正しく反映されるようにする

私が試した限りでは(Windows 8)、このように状態を変更したプログレスバーを使用した時、ProgressBar.Valueプロパティの値が正しく表示に反映されませんでした。例えばProgressBarのValueプロパティを0から1にしても表示は0のままで、Valueプロパティを2にすると表示が1になりました。このようにValueプロパティに新たな値を設定すると、前の値でProgressBarが表示されるようです。

この対処法を探した所、「Windows Forms Aero」というコントロールのライブラリで提供されているProgressBarでは、ProgressBarを描画する時にProgressBarの状態をNormalに戻してから、ErrorやPausedにしているようでした。

この方法を参考にして、ProgressBar.Valueプロパティの値を変更した後にProgressBarの状態を一旦Normalにしてから戻してみると、正常に表示されるようになりました。しかしProgressBarの状態をNormalにした時、一瞬バーが緑になります。

以下にこの方法を使った例を示します。

VB.NET
コードを隠すコードを選択
'ProgressBar1の値を1つずつ増やす
If ProgressBar1.Value = ProgressBar1.Maximum Then
    ProgressBar1.Value = ProgressBar1.Minimum
Else
    ProgressBar1.Value += 1
End If

'ProgressBarをNormalにしてからPausedに戻す
SendMessage(New HandleRef(ProgressBar1, ProgressBar1.Handle), _
            PBM_SETSTATE, PBST_NORMAL, IntPtr.Zero)
SendMessage(New HandleRef(ProgressBar1, ProgressBar1.Handle), _
            PBM_SETSTATE, PBST_PAUSED, IntPtr.Zero)
C#
コードを隠すコードを選択
//ProgressBar1の値を1つずつ増やす
if (ProgressBar1.Value == ProgressBar1.Maximum)
{
    ProgressBar1.Value = ProgressBar1.Minimum;
}
else
{
    ProgressBar1.Value++;
}

//ProgressBarをNormalにしてからPausedに戻す
SendMessage(new HandleRef(ProgressBar1, ProgressBar1.Handle),
    PBM_SETSTATE, PBST_NORMAL, IntPtr.Zero);
SendMessage(new HandleRef(ProgressBar1, ProgressBar1.Handle),
    PBM_SETSTATE, PBST_PAUSED, IntPtr.Zero);

試行錯誤で、これとは別の方法も見つけました。それは、ProgressBar.Maximumプロパティを変更してすぐに戻すという方法です。これならば一瞬色が変わるということもありません。

VB.NET
コードを隠すコードを選択
'ProgressBar1の値を1つずつ増やす
If ProgressBar1.Value = ProgressBar1.Maximum Then
    ProgressBar1.Value = ProgressBar1.Minimum
Else
    ProgressBar1.Value += 1
End If

'Maximumを1増やして、戻す
ProgressBar1.Maximum += 1
ProgressBar1.Maximum -= 1
C#
コードを隠すコードを選択
//ProgressBar1の値を1つずつ増やす
if (ProgressBar1.Value == ProgressBar1.Maximum)
{
    ProgressBar1.Value = ProgressBar1.Minimum;
}
else
{
    ProgressBar1.Value++;
}

//Maximumを1増やして、戻す
ProgressBar1.Maximum++;
ProgressBar1.Maximum--;

自分で描画する

上記の方法で満足できない場合は、自分で描画(オーナードロー)することになります。

ProgressBarコントロールではPaintイベントが発生しません。Paintイベントが発生するようにするには、Control.SetStyleメソッドでControlStyles.UserPaintをTrueにします。

ProgressBarクラスのOnPaintメソッドをオーバーライドして、オーナードローしているプログレスバーの例を以下に示します。この例ではコードを簡単にするために、背景もバーも四角でベタ塗りしているだけです。

このプログレスバーの使い方は、通常のProgressBarとほぼ同じです。詳しくは、こちらをご覧ください。

このプログレスバーのBackColorをSilver、ForeColorをDodgerBlueにすると、以下の図のように表示されます。

オーナードローしたProgressBar

VB.NET
コードを隠すコードを選択
Imports System.Drawing
Imports System.Windows.Forms

''' <summary>
''' ForeColorとBackColorプロパティによって色を変えることができる
''' プログレスバーコントロールを表します。
''' </summary>
Public Class ColorProgressBar
    Inherits ProgressBar
    Public Sub New()
        'Paintイベントが発生するようにする
        'ダブルバッファリングを有効にする
        MyBase.SetStyle(ControlStyles.UserPaint Or _
                        ControlStyles.AllPaintingInWmPaint Or _
                        ControlStyles.OptimizedDoubleBuffer, True)
    End Sub

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        Dim backBrush As Brush = New SolidBrush(Me.BackColor)
        Dim foreBrush As Brush = New SolidBrush(Me.ForeColor)

        '背景を描画する
        e.Graphics.FillRectangle(backBrush, Me.ClientRectangle)

        'バーの幅を計算する
        Dim chunksWidth As Integer = CInt( _
            CDbl(Me.ClientSize.Width) * _
            CDbl(Me.Value - Me.Minimum) / _
            CDbl(Me.Maximum - Me.Minimum))
        Dim chunksRect As New Rectangle(0, 0, chunksWidth, Me.ClientSize.Height)
        'バーを描画する
        e.Graphics.FillRectangle(foreBrush, chunksRect)

        backBrush.Dispose()
        foreBrush.Dispose()
    End Sub
End Class
C#
コードを隠すコードを選択
using System;
using System.Drawing;
using System.Windows.Forms;

/// <summary>
/// ForeColorとBackColorプロパティによって色を変えることができる
/// プログレスバーコントロールを表します。
/// </summary>
public class ColorProgressBar : ProgressBar
{
        public ColorProgressBar()
        {
            //Paintイベントが発生するようにする
            //ダブルバッファリングを有効にする
            base.SetStyle(ControlStyles.UserPaint |
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            Brush backBrush = new SolidBrush(this.BackColor);
            Brush foreBrush = new SolidBrush(this.ForeColor);

            //背景を描画する
            e.Graphics.FillRectangle(backBrush, this.ClientRectangle);

            //バーの幅を計算する
            int chunksWidth = (int)(
                (double)this.ClientSize.Width *
                (double)(this.Value - this.Minimum) /
                (double)(this.Maximum - this.Minimum));
            Rectangle chunksRect = new Rectangle(0, 0,
                chunksWidth, this.ClientSize.Height);
            //バーを描画する
            e.Graphics.FillRectangle(foreBrush, chunksRect);

            backBrush.Dispose();
            foreBrush.Dispose();
        }
}
補足:上記の例では、Styleプロパティなどは無視しています。下の例も同様です。なおマーキースタイルのプログレスバーを自分で描画する方法は、「ProgressBarをマーキースタイル(ブロックが移動するアニメーション)で表示する」で説明しています。

Partial状態のProgressBarを描画する

これは蛇足になるかもしれませんので、興味のある方のみお読みください。

先程プログレスバーの状態には、Normalの他に、ErrorやPausedがあることを紹介しましたが、実はこの他にPartialという状態があります。Partialのプログレスバーは青っぽい色になります。Windows Vista以降のエクスプローラで「コンピュータ」を表示すると、有効なドライブの一覧が表示されますが、この時空き領域を示すグラフがPartialのプログレスバーです。このようにパーセンテージを示すメーターとしてプログレスバーを使用する時にPartialにします。

しかしPBM_SETSTATEによる方法ではプログレスバーをPartialにできません。Partialのプログレスバーを表示するには、自分で描画するしかないようです。

プログレスバーはProgressBarRendererクラスを使って描画できますが、これではNormalのプログレスバーしか描画できません。その他の状態のプログレスバーを描画するには、VisualStyleRendererクラスを使用します。VisualStyleRendererクラスを使ってコントロールを描画する方法について詳しくは、「Visualスタイルでコントロールを描画する」をご覧ください。

ProgressBarRendererクラスを使用してオーナードローしたプログレスバーコントロールの例を以下に示します。このコントロールでは、BarStateプロパティでプログレスバーの状態を変更できます。BarStateプロパティをPartialにした時の外観は以下の図のようになります。

ProgressBarRendererクラスを使ってオーナードローしたProgressBar

VB.NET
コードを隠すコードを選択
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.VisualStyles

''' <summary>
''' StateProgressBarで状態を指定する時に使用します。
''' </summary>
Public Enum ProgressBarState
    Normal
    [Error]
    Paused
    [Partial]
End Enum

''' <summary>
''' 状態を変更できるプログレスバーコントロールを表します。
''' </summary>
Public Class StateProgressBar
    Inherits ProgressBar
    Private Const progressClassName As String = "PROGRESS"

    Private Enum PROGRESSPARTS
        PP_BAR = 1
        PP_BARVERT = 2
        PP_CHUNK = 3
        PP_CHUNKVERT = 4
        PP_FILL = 5
        PP_FILLVERT = 6
        PP_PULSEOVERLAY = 7
        PP_MOVEOVERLAY = 8
        PP_PULSEOVERLAYVERT = 9
        PP_MOVEOVERLAYVERT = 10
        PP_TRANSPARENTBAR = 11
        PP_TRANSPARENTBARVERT = 12
    End Enum

    Private Enum TRANSPARENTBARSTATES
        PBBS_NORMAL = 1
        PBBS_PARTIAL = 2
    End Enum

    Private Enum TRANSPARENTBARVERTSTATES
        PBBVS_NORMAL = 1
        PBBVS_PARTIAL = 2
    End Enum

    Private Enum FILLSTATES
        PBFS_NORMAL = 1
        PBFS_ERROR = 2
        PBFS_PAUSED = 3
        PBFS_PARTIAL = 4
    End Enum

    Private Enum FILLVERTSTATES
        PBFVS_NORMAL = 1
        PBFVS_ERROR = 2
        PBFVS_PAUSED = 3
        PBFVS_PARTIAL = 4
    End Enum

    Private vsRenderer As VisualStyleRenderer = Nothing

    Private _barState As ProgressBarState = ProgressBarState.Normal
    ''' <summary>
    ''' プログレスバーの状態を取得または設定します。
    ''' </summary>
    Public Property BarState() As ProgressBarState
        Get
            Return Me._barState
        End Get
        Set(value As ProgressBarState)
            Me._barState = value
        End Set
    End Property

    Public Sub New()
        'Paintイベントが発生するようにする
        'ダブルバッファリングを有効にする
        MyBase.SetStyle(ControlStyles.UserPaint Or _
                        ControlStyles.AllPaintingInWmPaint Or _
                        ControlStyles.OptimizedDoubleBuffer, True)

        'visualスタイルで表示するか調べる
        If Application.RenderWithVisualStyles AndAlso _
            VisualStyleRenderer.IsElementDefined( _
                VisualStyleElement.ProgressBar.Bar.Normal) Then
            Me.vsRenderer = New VisualStyleRenderer( _
                VisualStyleElement.ProgressBar.Bar.Normal)
        End If
    End Sub

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        'visualスタイルで描画するか
        If Me.vsRenderer Is Nothing Then
            '(VisualStyleRendererを使わないで描画するコードは省略)
            Return
        End If

        '背景とバーのPartとStateの値を決定する
        Dim backPart As Integer = CInt(PROGRESSPARTS.PP_TRANSPARENTBAR)
        Dim backState As Integer = CInt(TRANSPARENTBARSTATES.PBBS_NORMAL)
        Dim forePart As Integer = CInt(PROGRESSPARTS.PP_FILL)
        Dim foreState As Integer = CInt(FILLSTATES.PBFS_NORMAL)
        Select Case Me.BarState
            Case ProgressBarState.Normal
                Exit Select
            Case ProgressBarState.[Error]
                foreState = CInt(FILLSTATES.PBFS_ERROR)
                Exit Select
            Case ProgressBarState.Paused
                foreState = CInt(FILLSTATES.PBFS_PAUSED)
                Exit Select
            Case ProgressBarState.[Partial]
                backState = CInt(TRANSPARENTBARSTATES.PBBS_PARTIAL)
                foreState = CInt(FILLSTATES.PBFS_PARTIAL)
                Exit Select
        End Select

        '背景を描画する
        vsRenderer.SetParameters(progressClassName, backPart, backState)
        vsRenderer.DrawBackground(e.Graphics, Me.ClientRectangle)

        'バーの幅を計算する
        Dim chunksWidth As Integer = CInt( _
            CDbl(Me.ClientSize.Width) * _
            CDbl(Me.Value - Me.Minimum) / _
            CDbl(Me.Maximum - Me.Minimum))
        Dim chunksRect As New Rectangle( _
            0, 0, chunksWidth, Me.ClientSize.Height)
        'バーを描画する
        vsRenderer.SetParameters(progressClassName, forePart, foreState)
        vsRenderer.DrawBackground(e.Graphics, chunksRect)
    End Sub
End Class
C#
コードを隠すコードを選択
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

/// <summary>
/// StateProgressBarで状態を指定する時に使用します。
/// </summary>
public enum ProgressBarState
{
    Normal,
    Error,
    Paused,
    Partial,
}

/// <summary>
/// 状態を変更できるプログレスバーコントロールを表します。
/// </summary>
public class StateProgressBar : ProgressBar
{
    private const string progressClassName = "PROGRESS";

    private enum PROGRESSPARTS
    {
        PP_BAR = 1,
        PP_BARVERT = 2,
        PP_CHUNK = 3,
        PP_CHUNKVERT = 4,
        PP_FILL = 5,
        PP_FILLVERT = 6,
        PP_PULSEOVERLAY = 7,
        PP_MOVEOVERLAY = 8,
        PP_PULSEOVERLAYVERT = 9,
        PP_MOVEOVERLAYVERT = 10,
        PP_TRANSPARENTBAR = 11,
        PP_TRANSPARENTBARVERT = 12,
    }

    private enum TRANSPARENTBARSTATES
    {
        PBBS_NORMAL = 1,
        PBBS_PARTIAL = 2,
    }

    private enum TRANSPARENTBARVERTSTATES
    {
        PBBVS_NORMAL = 1,
        PBBVS_PARTIAL = 2,
    }

    private enum FILLSTATES
    {
        PBFS_NORMAL = 1,
        PBFS_ERROR = 2,
        PBFS_PAUSED = 3,
        PBFS_PARTIAL = 4,
    }

    private enum FILLVERTSTATES
    {
        PBFVS_NORMAL = 1,
        PBFVS_ERROR = 2,
        PBFVS_PAUSED = 3,
        PBFVS_PARTIAL = 4,
    }

    private VisualStyleRenderer vsRenderer = null;

    private ProgressBarState _barState = ProgressBarState.Normal;
    /// <summary>
    /// プログレスバーの状態を取得または設定します。
    /// </summary>
    public ProgressBarState BarState
    {
        get { return this._barState; }
        set { this._barState = value; }
    }

    public StateProgressBar()
    {
        //Paintイベントが発生するようにする
        //ダブルバッファリングを有効にする
        base.SetStyle(ControlStyles.UserPaint |
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.OptimizedDoubleBuffer, true);

        //visualスタイルで表示するか調べる
        if (Application.RenderWithVisualStyles &&
            VisualStyleRenderer.IsElementDefined(
                VisualStyleElement.ProgressBar.Bar.Normal))
        {
            this.vsRenderer =
                new VisualStyleRenderer(VisualStyleElement.ProgressBar.Bar.Normal);
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        //visualスタイルで描画するか
        if (this.vsRenderer == null)
        {
            //(VisualStyleRendererを使わないで描画するコードは省略)
            return;
        }

        //背景とバーのPartとStateの値を決定する
        int backPart = (int)PROGRESSPARTS.PP_TRANSPARENTBAR;
        int backState = (int)TRANSPARENTBARSTATES.PBBS_NORMAL;
        int forePart = (int)PROGRESSPARTS.PP_FILL;
        int foreState = (int)FILLSTATES.PBFS_NORMAL;
        switch (this.BarState)
        {
            case ProgressBarState.Normal:
                break;
            case ProgressBarState.Error:
                foreState = (int)FILLSTATES.PBFS_ERROR;
                break;
            case ProgressBarState.Paused:
                foreState = (int)FILLSTATES.PBFS_PAUSED;
                break;
            case ProgressBarState.Partial:
                backState = (int)TRANSPARENTBARSTATES.PBBS_PARTIAL;
                foreState = (int)FILLSTATES.PBFS_PARTIAL;
                break;
        }

        //背景を描画する
        vsRenderer.SetParameters(progressClassName, backPart, backState);
        vsRenderer.DrawBackground(e.Graphics, this.ClientRectangle);

        //バーの幅を計算する
        int chunksWidth = (int)(
            (double)this.ClientSize.Width *
            (double)(this.Value - this.Minimum) /
            (double)(this.Maximum - this.Minimum));
        Rectangle chunksRect = new Rectangle(0, 0,
            chunksWidth, this.ClientSize.Height);
        //バーを描画する
        vsRenderer.SetParameters(progressClassName, forePart, foreState);
        vsRenderer.DrawBackground(e.Graphics, chunksRect);
    }
}
補足:上の例では使用していませんが、PP_PULSEOVERLAYとPP_MOVEOVERLAYによってプログレスバーが光っているような効果を描画することができます。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。