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

ProgressBarをマーキースタイル(ブロックが移動するアニメーション)で表示する

ProgressBarの特殊なスタイルとして、マーキースタイルのプログレスバー(indeterminate progress bar)があります。マーキースタイルのプログレスバーでは、バーブロックが絶え間なく、連続して左から右に移動し続けます。

マーキースタイルのProgressBar

マーキースタイルは、現在処理が進行中であることをユーザーに知らせる目的で使用します。ただし、処理がどの程度進行したかを示すことができるのであれば通常のプログレスバー(determinate progress bar)を使うべきで、マーキースタイルは進行状況を判断できない場合にのみ使用します。

ここでは、ProgressBarをマーキースタイルで表示する方法を紹介します。

ProgressBar.StyleプロパティをMarqueeにする方法

.NET Framework 2.0からは、ProgressBar.StyleプロパティをProgressBarStyle.Marqueeにすることでマーキースタイルにできます。

ProgressBarをマーキースタイルにするとプログレスバーの値は意味がなくなりますので、ProgressBar.Value、Minimum、Maximumなどの設定は無視されます。

また、アプリケーションのvisualスタイルが無効の時はマーキースタイルにならず、ProgressBar.StyleプロパティがBlocksの時と同じになります。visualスタイルを無効にする方法は、「コントロールの外観をビジュアルスタイル(XPスタイル)にする」をご覧ください。

さらに、ProgressBar.MarqueeAnimationSpeedプロパティでブロックの移動速度を変更することができます。デフォルトは100で、値を大きくすると遅くなり、小さくすると速くなります。0にすると、止まります。

VB.NET
コードを隠すコードを選択
'ProgressBar1をマーキースタイルにする
ProgressBar1.Style = ProgressBarStyle.Marquee
'ブロックの移動速度をデフォルトの倍にする
ProgressBar1.MarqueeAnimationSpeed = 50
C#
コードを隠すコードを選択
//ProgressBar1をマーキースタイルにする
ProgressBar1.Style = ProgressBarStyle.Marquee;
//ブロックの移動速度をデフォルトの倍にする
ProgressBar1.MarqueeAnimationSpeed = 50;

自分で描画する方法

プログレスバーを自分で描画(オーナードロー)する方法は「ProgressBarの色を変える」で紹介しましたが、マーキースタイルのプログレスバーもこれと同じようにして自分で描画することもできます。

以下にその例を示します。ここではvisualスタイルで描画するために.NET Framework 2.0以降でしか使用できないメソッドなどを使用していますが、visualスタイルで描画する部分を削除すれば.NET Framework 1.1以前でも行けるかと思います(未確認です)。

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

''' <summary>
''' マーキースタイルのプログレスバーコントロールを表します。
''' </summary>
Public Class MarqueeProgressBar
    Inherits ProgressBar

    'プロパティの初期値
    Private Const defaultMarqueeWidth As Integer = 127
    Private Const defaultMarqueeAnimationSpeed As Integer = 100
    Private Const defaultMarqueeAnimationStep As Integer = 6
    Private Const defaultForeColorString As String = "Highlight"
    Private Const defaultBackColorString As String = "ControlLight"
    Private Const defaultFrameColorString As String = "ActiveBorder"
    Private Const defaultUseVisualStyle As Boolean = True

    'ブロックの移動に使用するタイマー
    Private marqueeTimer As Timer = Nothing
    'ブロックの現在の位置
    Private marqueeLeft As Integer = 0

    Private _marqueeWidth As Integer
    ''' <summary>
    ''' ブロックの幅を取得または設定します。
    ''' </summary>
    <DefaultValue(defaultMarqueeWidth)> _
    Public Property MarqueeWidth() As Integer
        Get
            Return Me._marqueeWidth
        End Get
        Set(value As Integer)
            Me._marqueeWidth = value
            '描画し直す
            Me.Invalidate()
        End Set
    End Property

    Private _marqueeAnimationSpeed As Integer
    ''' <summary>
    ''' ブロックが移動する間隔をミリ秒単位で取得または設定します。
    ''' </summary>
    <DefaultValue(defaultMarqueeAnimationSpeed)> _
    Public Shadows Property MarqueeAnimationSpeed() As Integer
        Get
            Return Me._marqueeAnimationSpeed
        End Get
        Set(value As Integer)
            If value < 1 Then
                Throw New ArgumentOutOfRangeException( _
                    "1以上の整数を指定してください")
            End If
            Me._marqueeAnimationSpeed = value
            If Not Me.marqueeTimer Is Nothing Then
                Me.marqueeTimer.Interval = Me._marqueeAnimationSpeed
            End If
        End Set
    End Property

    Private _marqueeAnimationStep As Integer
    ''' <summary>
    ''' ブロックが一度に移動する距離をピクセル単位で取得または設定します。
    ''' </summary>
    <DefaultValue(defaultMarqueeAnimationStep)> _
    Public Property MarqueeAnimationStep() As Integer
        Get
            Return Me._marqueeAnimationStep
        End Get
        Set(value As Integer)
            If value < 1 Then
                Throw New ArgumentOutOfRangeException( _
                    "1以上の整数を指定してください")
            End If
            Me._marqueeAnimationStep = value
        End Set
    End Property

    Private foreColorBrush As Brush = Nothing
    ''' <summary>
    ''' プログレスバーのブロックの色を取得または設定します。
    ''' </summary>
    <DefaultValue(GetType(Color), defaultForeColorString)> _
    Public Overrides Property ForeColor() As Color
        Get
            Return MyBase.ForeColor
        End Get
        Set(value As Color)
            MyBase.ForeColor = value
            'Brushを作成する
            If Not Me.foreColorBrush Is Nothing Then
                Me.foreColorBrush.Dispose()
            End If
            Me.foreColorBrush = New SolidBrush(MyBase.ForeColor)
            '描画し直す
            Me.Invalidate()
        End Set
    End Property

    Private backColorBrush As Brush = Nothing
    ''' <summary>
    ''' プログレスバーの背景色を取得または設定します。
    ''' </summary>
    <DefaultValue(GetType(Color), defaultBackColorString)> _
    Public Overrides Property BackColor() As Color
        Get
            Return MyBase.BackColor
        End Get
        Set(value As Color)
            MyBase.BackColor = value
            'Brushを作成する
            If Not Me.backColorBrush Is Nothing Then
                Me.backColorBrush.Dispose()
            End If
            Me.backColorBrush = New SolidBrush(MyBase.BackColor)
            '描画し直す
            Me.Invalidate()
        End Set
    End Property

    Private _frameColor As Color
    Private frameColorPen As Pen = Nothing
    ''' <summary>
    ''' プログレスバーの枠の色を取得または設定します。
    ''' </summary>
    <DefaultValue(GetType(Color), defaultFrameColorString)> _
    Public Property FrameColor() As Color
        Get
            Return Me._frameColor
        End Get
        Set(value As Color)
            If value.IsEmpty Then
                Return
            End If
            Me._frameColor = value
            'Penを作成する
            If Not Me.frameColorPen Is Nothing Then
                Me.frameColorPen.Dispose()
            End If
            Me.frameColorPen = New Pen(Me.FrameColor)
            '描画し直す
            Me.Invalidate()
        End Set
    End Property

    Private _useVisualStyle As Boolean
    ''' <summary>
    ''' visual スタイルがサポートされている場合に、
    ''' visual スタイルを使用してプログレスバーを描画するかどうかを
    ''' 取得または設定します。
    ''' </summary>
    ''' <remarks>
    ''' このプロパティがTrueで、visualスタイルが有効である場合は、
    ''' ForeColor、BackColor、FrameColorプロパティの値は無視されます。
    ''' </remarks>
    <DefaultValue(defaultUseVisualStyle)> _
    Public Property UseVisualStyle() As Boolean
        Get
            Return Me._useVisualStyle
        End Get
        Set(value As Boolean)
            Me._useVisualStyle = value
        End Set
    End Property

    ''' <summary>
    ''' MarqueeProgressBarクラスの新しいインスタンスを初期化します。
    ''' </summary>
    Public Sub New()
        'Paintイベントが発生するようにする
        'ダブルバッファリングを有効にする
        MyBase.SetStyle(ControlStyles.UserPaint Or _
                        ControlStyles.AllPaintingInWmPaint Or _
                        ControlStyles.OptimizedDoubleBuffer, True)

        'プロパティを初期化する
        Me.MarqueeAnimationSpeed = defaultMarqueeAnimationSpeed
        Me.MarqueeAnimationStep = defaultMarqueeAnimationStep
        Me.MarqueeWidth = defaultMarqueeWidth
        Me.ForeColor = Color.FromName(defaultForeColorString)
        Me.BackColor = Color.FromName(defaultBackColorString)
        Me.FrameColor = Color.FromName(defaultFrameColorString)
        Me.UseVisualStyle = defaultUseVisualStyle

        'ブロックの移動を開始する
        Me.StartMarquee()
    End Sub

    ''' <summary>
    ''' ブロックの移動を開始します。
    ''' </summary>
    Public Sub StartMarquee()
        '初期化する
        Me.SetStartMarqueeLeft()
        'タイマーを初期化する
        If Me.marqueeTimer Is Nothing Then
            Me.marqueeTimer = New Timer()
            AddHandler Me.marqueeTimer.Tick, AddressOf marqueeTimer_Tick
        End If
        Me.marqueeTimer.Interval = Me.MarqueeAnimationSpeed
        'タイマーを開始する
        Me.marqueeTimer.Start()
    End Sub

    ''' <summary>
    ''' ブロックの移動を停止します。
    ''' </summary>
    Public Sub StopMarquee()
        If Not Me.marqueeTimer Is Nothing Then
            Me.marqueeTimer.[Stop]()
        End If
    End Sub

    ''' <summary>
    ''' ブロックの初期位置を設定します。
    ''' </summary>
    Private Sub SetStartMarqueeLeft()
        Me.marqueeLeft = -Me.MarqueeWidth
    End Sub

    ''' <summary>
    ''' ブロックの位置を次の位置に設定します。
    ''' </summary>
    Private Sub SetNextMarqueeLeft()
        Me.marqueeLeft += Me.MarqueeAnimationStep
        '最後まで移動した時ははじめの位置に戻す
        If Me.ClientRectangle.Width <= Me.marqueeLeft Then
            Me.SetStartMarqueeLeft()
        End If
    End Sub

    'marqueeTimer.Tickイベントハンドラメソッド
    Private Sub marqueeTimer_Tick(sender As Object, e As EventArgs)
        'ブロックの位置を次に移動する
        Me.SetNextMarqueeLeft()
        '描画する
        Me.Invalidate()
    End Sub

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        If Me.UseVisualStyle AndAlso _
            Application.RenderWithVisualStyles Then
            'visualスタイルで描画する
            '背景を描画する
            ProgressBarRenderer.DrawHorizontalBar( _
                e.Graphics, Me.ClientRectangle)

            'ブロックを描画する
            '枠を塗りつぶさないようにする
            e.Graphics.SetClip(New Rectangle(1, 1, _
                Me.ClientRectangle.Width - 2, _
                Me.ClientRectangle.Height - 2))
            Dim chunksRect As New Rectangle(Me.marqueeLeft, 0, _
                Me.MarqueeWidth, Me.ClientSize.Height)
            ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, chunksRect)
        Else
            'visualスタイルを使わずに描画する
            '背景を描画する
            e.Graphics.FillRectangle(Me.backColorBrush, Me.ClientRectangle)

            'ブロックを描画する
            Dim chunksRect As New Rectangle(Me.marqueeLeft, 0, _
                Me.MarqueeWidth, Me.ClientSize.Height)
            e.Graphics.FillRectangle(Me.foreColorBrush, chunksRect)

            '枠を描画する
            Dim frameRect As New Rectangle(0, 0, _
                Me.ClientSize.Width - 1, Me.ClientSize.Height - 1)
            e.Graphics.DrawRectangle(Me.frameColorPen, frameRect)
        End If
    End Sub

    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposing Then
            '後片付け
            If Not Me.marqueeTimer Is Nothing Then
                RemoveHandler Me.marqueeTimer.Tick, AddressOf marqueeTimer_Tick
                Me.marqueeTimer.Dispose()
                Me.marqueeTimer = Nothing
            End If
            If Not Me.foreColorBrush Is Nothing Then
                Me.foreColorBrush.Dispose()
                Me.foreColorBrush = Nothing
            End If
            If Not Me.backColorBrush Is Nothing Then
                Me.backColorBrush.Dispose()
                Me.backColorBrush = Nothing
            End If
            If Not Me.frameColorPen Is Nothing Then
                Me.frameColorPen.Dispose()
                Me.frameColorPen = Nothing
            End If
        End If

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

/// <summary>
/// マーキースタイルのプログレスバーコントロールを表します。
/// </summary>
public class MarqueeProgressBar : ProgressBar
{
    //プロパティの初期値
    private const int defaultMarqueeWidth = 127;
    private const int defaultMarqueeAnimationSpeed = 100;
    private const int defaultMarqueeAnimationStep = 6;
    private const string defaultForeColorString = "Highlight";
    private const string defaultBackColorString = "ControlLight";
    private const string defaultFrameColorString = "ActiveBorder";
    private const bool defaultUseVisualStyle = true;
    
    //ブロックの移動に使用するタイマー
    private Timer marqueeTimer = null;
    //ブロックの現在の位置
    private int marqueeLeft = 0;

    private int _marqueeWidth;
    /// <summary>
    /// ブロックの幅を取得または設定します。
    /// </summary>
    [DefaultValue(defaultMarqueeWidth)]
    public int MarqueeWidth
    {
        get
        {
            return this._marqueeWidth;
        }
        set
        {
            this._marqueeWidth = value;
            //描画し直す
            this.Invalidate();
        }
    }

    private int _marqueeAnimationSpeed;
    /// <summary>
    /// ブロックが移動する間隔をミリ秒単位で取得または設定します。
    /// </summary>
    [DefaultValue(defaultMarqueeAnimationSpeed)]
    public new int MarqueeAnimationSpeed
    {
        get
        {
            return this._marqueeAnimationSpeed;
        }
        set
        {
            if (value < 1)
            {
                throw new ArgumentOutOfRangeException(
                    "1以上の整数を指定してください");
            }
            this._marqueeAnimationSpeed = value;
            if (this.marqueeTimer != null)
            {
                this.marqueeTimer.Interval = this._marqueeAnimationSpeed;
            }
        }
    }

    private int _marqueeAnimationStep;
    /// <summary>
    /// ブロックが一度に移動する距離をピクセル単位で取得または設定します。
    /// </summary>
    [DefaultValue(defaultMarqueeAnimationStep)]
    public int MarqueeAnimationStep
    {
        get
        {
            return this._marqueeAnimationStep;
        }
        set
        {
            if (value < 1)
            {
                throw new ArgumentOutOfRangeException(
                    "1以上の整数を指定してください");
            }
            this._marqueeAnimationStep = value;
        }
    }

    private Brush foreColorBrush = null;
    /// <summary>
    /// プログレスバーのブロックの色を取得または設定します。
    /// </summary>
    [DefaultValue(typeof(Color), defaultForeColorString)]
    public override Color ForeColor
    {
        get
        {
            return base.ForeColor;
        }
        set
        {
            base.ForeColor = value;
            //Brushを作成する
            if (this.foreColorBrush != null)
            {
                this.foreColorBrush.Dispose();
            }
            this.foreColorBrush = new SolidBrush(base.ForeColor);
            //描画し直す
            this.Invalidate();
        }
    }

    private Brush backColorBrush = null;
    /// <summary>
    /// プログレスバーの背景色を取得または設定します。
    /// </summary>
    [DefaultValue(typeof(Color), defaultBackColorString)]
    public override Color BackColor
    {
        get
        {
            return base.BackColor;
        }
        set
        {
            base.BackColor = value;
            //Brushを作成する
            if (this.backColorBrush != null)
            {
                this.backColorBrush.Dispose();
            }
            this.backColorBrush = new SolidBrush(base.BackColor);
            //描画し直す
            this.Invalidate();
        }
    }

    private Color _frameColor;
    private Pen frameColorPen = null;
    /// <summary>
    /// プログレスバーの枠の色を取得または設定します。
    /// </summary>
    [DefaultValue(typeof(Color), defaultFrameColorString)]
    public Color FrameColor
    {
        get
        {
            return this._frameColor;
        }
        set
        {
            if (value.IsEmpty)
            {
                return;
            }
            this._frameColor = value;
            //Penを作成する
            if (this.frameColorPen != null)
            {
                this.frameColorPen.Dispose();
            }
            this.frameColorPen = new Pen(this.FrameColor);
            //描画し直す
            this.Invalidate();
        }
    }

    private bool _useVisualStyle;
    /// <summary>
    /// visual スタイルがサポートされている場合に、
    /// visual スタイルを使用してプログレスバーを描画するかどうかを
    /// 取得または設定します。
    /// </summary>
    /// <remarks>
    /// このプロパティがTrueで、visualスタイルが有効である場合は、
    /// ForeColor、BackColor、FrameColorプロパティの値は無視されます。
    /// </remarks>
    [DefaultValue(defaultUseVisualStyle)]
    public bool UseVisualStyle
    {
        get { return this._useVisualStyle; }
        set { this._useVisualStyle = value; }
    }
    
    /// <summary>
    /// MarqueeProgressBarクラスの新しいインスタンスを初期化します。
    /// </summary>
    public MarqueeProgressBar()
    {
        //Paintイベントが発生するようにする
        //ダブルバッファリングを有効にする
        base.SetStyle(ControlStyles.UserPaint |
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.OptimizedDoubleBuffer, true);

        //プロパティを初期化する
        this.MarqueeAnimationSpeed = defaultMarqueeAnimationSpeed;
        this.MarqueeAnimationStep = defaultMarqueeAnimationStep;
        this.MarqueeWidth = defaultMarqueeWidth;
        this.ForeColor = Color.FromName(defaultForeColorString);
        this.BackColor = Color.FromName(defaultBackColorString);
        this.FrameColor = Color.FromName(defaultFrameColorString);
        this.UseVisualStyle = defaultUseVisualStyle;

        //ブロックの移動を開始する
        this.StartMarquee();
    }

    /// <summary>
    /// ブロックの移動を開始します。
    /// </summary>
    public void StartMarquee()
    {
        //初期化する
        this.SetStartMarqueeLeft();
        //タイマーを初期化する
        if (this.marqueeTimer == null)
        {
            this.marqueeTimer = new Timer();
            this.marqueeTimer.Tick += marqueeTimer_Tick;
        }
        this.marqueeTimer.Interval = this.MarqueeAnimationSpeed;
        //タイマーを開始する
        this.marqueeTimer.Start();
    }

    /// <summary>
    /// ブロックの移動を停止します。
    /// </summary>
    public void StopMarquee()
    {
        if (this.marqueeTimer != null)
        {
            this.marqueeTimer.Stop();
        }
    }

    /// <summary>
    /// ブロックの初期位置を設定します。
    /// </summary>
    private void SetStartMarqueeLeft()
    {
        this.marqueeLeft = -this.MarqueeWidth;
    }

    /// <summary>
    /// ブロックの位置を次の位置に設定します。
    /// </summary>
    private void SetNextMarqueeLeft()
    {
        this.marqueeLeft += this.MarqueeAnimationStep;
        //最後まで移動した時ははじめの位置に戻す
        if (this.ClientRectangle.Width <= this.marqueeLeft)
        {
            this.SetStartMarqueeLeft();
        }
    }

    //marqueeTimer.Tickイベントハンドラメソッド
    private void marqueeTimer_Tick(object sender, EventArgs e)
    {
        //ブロックの位置を次に移動する
        this.SetNextMarqueeLeft();
        //描画する
        this.Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (this.UseVisualStyle && Application.RenderWithVisualStyles)
        {
            //visualスタイルで描画する
            //背景を描画する
            ProgressBarRenderer.DrawHorizontalBar(
                e.Graphics, this.ClientRectangle);

            //ブロックを描画する
            //枠を塗りつぶさないようにする
            e.Graphics.SetClip(new Rectangle(1, 1,
                this.ClientRectangle.Width - 2,
                this.ClientRectangle.Height - 2));
            Rectangle chunksRect = new Rectangle(this.marqueeLeft, 0,
                this.MarqueeWidth, this.ClientSize.Height);
            ProgressBarRenderer.DrawHorizontalChunks(
                e.Graphics, chunksRect);
        }
        else
        {
            //visualスタイルを使わずに描画する
            //背景を描画する
            e.Graphics.FillRectangle(
                this.backColorBrush, this.ClientRectangle);

            //ブロックを描画する
            Rectangle chunksRect = new Rectangle(this.marqueeLeft, 0,
                this.MarqueeWidth, this.ClientSize.Height);
            e.Graphics.FillRectangle(this.foreColorBrush, chunksRect);

            //枠を描画する
            Rectangle frameRect = new Rectangle(0, 0,
                this.ClientSize.Width - 1, this.ClientSize.Height - 1);
            e.Graphics.DrawRectangle(this.frameColorPen, frameRect);
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            //後片付け
            if (this.marqueeTimer != null)
            {
                this.marqueeTimer.Tick -= marqueeTimer_Tick;
                this.marqueeTimer.Dispose();
                this.marqueeTimer = null;
            }
            if (this.foreColorBrush != null)
            {
                this.foreColorBrush.Dispose();
                this.foreColorBrush = null;
            }
            if (this.backColorBrush != null)
            {
                this.backColorBrush.Dispose();
                this.backColorBrush = null;
            }
            if (this.frameColorPen != null)
            {
                this.frameColorPen.Dispose();
                this.frameColorPen = null;
            }
        }

        base.Dispose(disposing);
    }
}

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

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。