VB.NET ProgressBarに動的テキストを重ねて表示する方法【Windows Forms】

Windows Forms の標準 ProgressBar だけでは「何%まで進んだか」を文字で分かりやすく表示できず、ユーザーにとって進捗状況が直感的に伝わりにくくなりがちです。本記事では VB.NET のカスタムコントロール ProgressBarZ を題材に、進捗バーの上に任意のテキストを動的に重ねて描画する実装を、プロパティ設計から描画処理、チラつき対策まで丁寧に解説します。

目次

ProgressBar にテキストを重ねて表示したい典型的なシナリオ

Windows Forms アプリの開発では、標準の ProgressBar をそのまま使うと「バーが伸びているだけ」で、ユーザーに伝わる情報は次のように限定されます。

  • 現在の進捗率がわからない(50%なのか 80%なのか曖昧)
  • 何を処理しているのかが伝わりにくい
  • マルチステップ処理(手順 1/3 など)が表現しづらい

そこでよく行われるのが、進捗バーの上に「50%」「ファイルコピー中…」といった文字列を重ねて描画する方法です。

しかし、単純にカスタムクラスに固定文字列を書いてしまうと、次のような問題が起こります。

  • 文字列がハードコーディングされていて変更できない
  • 外部(フォーム側)から動的にテキストを設定できない
  • 再描画が正しく行われず、テキストが更新されないことがある

この記事では、これらの問題を解決するために、

  • 公開プロパティ DisplayText を追加して外からテキストを設定できるようにする
  • プロパティ変更時に Invalidate() を呼び出して再描画をトリガーする
  • 追加の描画メソッド(ここでは AdditionalPaint)でプロパティ値を描画する

という流れで、実用的な ProgressBar 拡張クラス ProgressBarZ を組み上げていきます。

完成イメージと設計方針

まず、目指す完成イメージと設計方針を整理しておきます。

  • ProgressBar を継承したクラス ProgressBarZ を作成する
  • 標準のバー描画(緑のバー)はそのまま利用し、その上に文字列だけ重ね描きする
  • 文字列は DisplayText プロパティで外部から自由に設定できる
  • 文字列変更時は自動的に再描画され、リアルタイムに更新される
  • フォントや色は、コントロールの Font / ForeColor を利用する

この構成にすることで、フォーム側からは次のような、とてもシンプルな書き方で利用できます。

ProgressBarZ1.Value = 50
ProgressBarZ1.DisplayText = "50 % 完了"

「プロパティを変更するだけで描画が更新される」状態にするのがポイントです。

DisplayText プロパティを追加する

まずは文字列を保持するためのフィールドと、公開プロパティ DisplayText を追加します。

Public Class ProgressBarZ
    Inherits ProgressBar

    Private _displayText As String = String.Empty

    Public Property DisplayText As String
        Get
            Return _displayText
        End Get
        Set(value As String)
            If _displayText <> value Then
                _displayText = value
                ' 値が変わったら再描画を要求
                Me.Invalidate()
            End If
        End Set
    End Property

End Class

ここでのポイントを表にまとめると次の通りです。

要素役割実装上のポイント
_displayText実テキストを保持するプライベートフィールド外部から直接触らせず、プロパティ経由で操作させる
DisplayTextデザイナー・コードから参照される公開プロパティ値が変わったときだけ Invalidate() を呼んで描画コストを抑える
Invalidate()コントロールの再描画を OS に依頼するWM_PAINT が発行され、後述の WndProc/AdditionalPaint が呼ばれる

デザイナーで扱いやすくする属性の追加(任意)

プロパティをデザイナーからも設定しやすくするために、Category や Description 属性を付けておくと便利です。

Imports System.ComponentModel

Public Class ProgressBarZ
    Inherits ProgressBar

    Private _displayText As String = String.Empty

    <Category("Appearance"),
     Description("ProgressBar 上に表示する文字列")>
    Public Property DisplayText As String
        Get
            Return _displayText
        End Get
        Set(value As String)
            If _displayText <> value Then
                _displayText = value
                Me.Invalidate()
            End If
        End Set
    End Property

End Class

これで、フォームデザイナーのプロパティウィンドウにも DisplayText が表示され、デザイン時にテキストを試しながら調整することもできます。

ProgressBar 上に文字列を描画する AdditionalPaint の実装

次に、バーの上に文字列を描画する部分です。ここでは、標準の ProgressBar に描画させた後、WM_PAINT メッセージをフックしてテキストだけを重ね描きするスタイルを取ります。

WM_PAINT をフックする WndProc のオーバーライド

まずは WndProc をオーバーライドし、WM_PAINT メッセージの後に追加描画メソッドを呼び出します。

Protected Overrides Sub WndProc(ByRef m As Message)
    MyBase.WndProc(m)

    Const WM_PAINT As Integer = &H0F

    If m.Msg = WM_PAINT Then
        AdditionalPaint()
    End If
End Sub

MyBase.WndProc(m) を先に呼んでいる点が重要です。これにより、まず標準の ProgressBar が描画され、その上からテキストを描くことができます。

AdditionalPaint で DisplayText を描画する

続いて、実際に文字列を描画する AdditionalPaint メソッドを実装します。

Private Sub AdditionalPaint()
    ' テキストが空なら何もしない
    If String.IsNullOrEmpty(_displayText) Then
        Return
    End If

    Using g As Graphics = Graphics.FromHwnd(Me.Handle)
        Dim rect As New Rectangle(0, 0, Me.Width, Me.Height)

        Using fmt As New StringFormat(StringFormatFlags.NoWrap)
            fmt.Alignment = StringAlignment.Center
            fmt.LineAlignment = StringAlignment.Center

            Using br As New SolidBrush(Me.ForeColor)
                g.DrawString(_displayText, Me.Font, br, rect, fmt)
            End Using
        End Using
    End Using
End Sub

このメソッドでは、

  • Graphics.FromHwnd(Handle) でコントロールのデバイスコンテキストを取得
  • コントロール全体を表す矩形 Rectangle(0, 0, Width, Height) を用意
  • StringFormat を使って水平方向・垂直方向とも中央寄せに設定
  • ForeColor と Font を利用してテキストを描画

という処理を行っています。

プロパティ / メソッド役割ポイント
Graphics.FromHwndコントロールの描画対象を表す Graphics を取得取得した Graphics は必ず Using で破棄する
StringFormat.Alignment水平方向の揃え位置StringAlignment.Center で中央寄せ
StringFormat.LineAlignment垂直方向の揃え位置StringAlignment.Center で上下中央寄せ
SolidBrush(ForeColor)文字色を指定フォーム側から ForeColor を変えれば文字色も変わる

ProgressBarZ クラスの全体コード

ここまでの内容をまとめると、基本形となる ProgressBarZ クラスは次のようになります。

Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms

Public Class ProgressBarZ
    Inherits ProgressBar

    Private _displayText As String = String.Empty

    <Category("Appearance"),
     Description("ProgressBar 上に表示する文字列")>
    Public Property DisplayText As String
        Get
            Return _displayText
        End Get
        Set(value As String)
            If _displayText <> value Then
                _displayText = value
                Me.Invalidate() ' 文字列が変わったら再描画
            End If
        End Set
    End Property

    Protected Overrides Sub WndProc(ByRef m As Message)
        MyBase.WndProc(m)

        Const WM_PAINT As Integer = &H0F

        If m.Msg = WM_PAINT Then
            AdditionalPaint()
        End If
    End Sub

    Private Sub AdditionalPaint()
        If String.IsNullOrEmpty(_displayText) Then
            Return
        End If

        Using g As Graphics = Graphics.FromHwnd(Me.Handle)
            Dim rect As New Rectangle(0, 0, Me.Width, Me.Height)

            Using fmt As New StringFormat(StringFormatFlags.NoWrap)
                fmt.Alignment = StringAlignment.Center
                fmt.LineAlignment = StringAlignment.Center

                Using br As New SolidBrush(Me.ForeColor)
                    g.DrawString(_displayText, Me.Font, br, rect, fmt)
                End Using
            End Using
        End Using
    End Sub

End Class

この状態でフォームに配置し、

ProgressBarZ1.Value = 30
ProgressBarZ1.DisplayText = "30 % 完了"

と設定すれば、バーの中央に「30 % 完了」と表示されるようになります。

DisplayText を動的に更新する例

次に、進捗とテキストをリアルタイムで更新する簡単なサンプルを示します。

フォーム側のサンプルコード

フォームに ProgressBarZ と Timer を配置し、一定間隔で進捗とテキストを更新してみます。

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) _
        Handles MyBase.Load

        ProgressBarZ1.Minimum = 0
        ProgressBarZ1.Maximum = 100
        ProgressBarZ1.Value = 0
        ProgressBarZ1.DisplayText = "処理待機中"

        Timer1.Interval = 100 ' 100ms ごとに更新
        Timer1.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) _
        Handles Timer1.Tick

        If ProgressBarZ1.Value < ProgressBarZ1.Maximum Then
            ProgressBarZ1.Value += 1
            ProgressBarZ1.DisplayText =
                ProgressBarZ1.Value.ToString() & " % 完了"
        Else
            ProgressBarZ1.DisplayText = "完了しました"
            Timer1.Stop()
        End If
    End Sub

End Class

この例では、進捗値とテキストを同じタイミングで更新しているため、「バーの長さ」と「数字の表示」が常に同期した状態で動きます。

バックグラウンドスレッドから更新する場合の注意点

重い処理を Task や BackgroundWorker で実行しつつ、途中経過を DisplayText に表示したくなるケースも多いです。このとき注意しなければならないのがスレッド境界です。

  • UI スレッド以外から DisplayText を直接書き換えるのは NG
  • Invoke または BeginInvoke を使って UI スレッドに処理を渡す

例えば、次のような書き方になります。

Private Sub ReportProgress(value As Integer, message As String)
    If ProgressBarZ1.InvokeRequired Then
        ProgressBarZ1.Invoke(
            Sub()
                ProgressBarZ1.Value = value
                ProgressBarZ1.DisplayText = message
            End Sub)
    Else
        ProgressBarZ1.Value = value
        ProgressBarZ1.DisplayText = message
    End If
End Sub

これにより、バックグラウンド処理中でも安全に ProgressBarZ の表示を更新できます。

テキスト表示をカスタマイズするアイデア

基本の実装ができたら、用途に合わせてテキスト表示をカスタマイズするのもおすすめです。

テキストの見た目を変える(フォント・色・配置)

すでに実装しているように、フォントと色はコントロールの Font / ForeColor を利用しているため、フォーム側から簡単に変更できます。

ProgressBarZ1.Font = New Font("Meiryo", 10, FontStyle.Bold)
ProgressBarZ1.ForeColor = Color.White

配置についても、StringFormat.Alignment と LineAlignment を変えることで、左寄せ・右寄せなどが可能です。

fmt.Alignment = StringAlignment.Near      ' 左寄せ
fmt.LineAlignment = StringAlignment.Far   ' 下寄せ

用途に応じて、例えば「進捗率は右下、小さめの文字で表示」など、UI デザインに合わせた調整ができます。

テキストを一時的に非表示にする

テキストが不要な区間では DisplayText を空文字列にするだけで、描画をスキップできます。

ProgressBarZ1.DisplayText = String.Empty
' または
ProgressBarZ1.DisplayText = Nothing

AdditionalPaint の先頭で空チェックをしているため、この状態では文字列は描画されません。

チラつきを抑える描画設定(任意)

ProgressBar に限らず、カスタム描画を行うと再描画のたびにチラつきが発生することがあります。これを軽減するために、次のような設定を追加することができます。

SetStyle による描画スタイルの設定

ControlStyles のフラグを指定して、ダブルバッファリングなどを有効にします。ただし、ProgressBar を完全に自前描画する場合と組み合わせて使うことが多いので、ここでは「導入例」として紹介します。

Public Sub New()
    MyBase.New()

    Me.SetStyle(ControlStyles.UserPaint, True)
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
    Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
End Sub

UserPaint = True にすると、標準の ProgressBar の描画ではなく、OnPaint を使って完全に自前で描画する形になります。バーそのものの見た目を自由に描きたい場合に有効ですが、既存の標準見た目を活かしたい場合は、この記事前半で解説した WndProc フック方式でも十分です。

CreateParams の ExStyle に WS_EX_TRANSPARENT を与える

透過的に描画することで、再描画時のちらつきを抑えられることがあります。CreateParams をオーバーライドして、拡張スタイルを変更します。

Protected Overrides ReadOnly Property CreateParams As CreateParams
    Get
        Dim cp As CreateParams = MyBase.CreateParams
        Const WS_EX_TRANSPARENT As Integer = &H20
        cp.ExStyle = cp.ExStyle Or WS_EX_TRANSPARENT
        Return cp
    End Get
End Property

環境によっては、意図しない再描画順序になる場合もあるため、「ちらつきがひどい場合の候補」として試しながら調整するとよいでしょう。

Label を重ねる方法との比較

ProgressBar に文字を表示する一般的な別案として、ProgressBar の上に Label を重ねて配置するという方法もあります。それぞれの方法のメリットを簡単に比較しておきます。

方式メリットデメリット
Label を重ねる実装が簡単で、GDI+ コードを書かなくてよい テキストのフォントや色を Label 側で細かく設定しやすいレイアウトがずれやすく、リサイズ時の調整が面倒 見た目の一体感がやや劣る場合がある
ProgressBarZ で描画単一コントロールとして扱えるため、再利用性が高い 描画タイミングと同期しているため、チラつきやずれが少ない 進捗バーのスタイルを拡張したり、アニメーションを追加しやすいGDI+ のコードに慣れていないと少しハードルが高い 環境によってはチラつき対策など調整が必要

「とりあえず動けばいい」という段階では Label 重ね方式も有効ですが、複数画面で使い回したい、デザインを統一したい、メンテナンス性を高めたいといった要件がある場合は、この記事で紹介した ProgressBarZ のようなカスタムコントロール化が圧倒的に有利です。

よくあるつまずきポイントと対処法

テキストが更新されない/昔の文字が残る

主な原因は次のどちらかです。

  • DisplayText の Set で Invalidate() を呼んでいない
  • 変更後の文字列が以前と同じなので、Invalidate() が実行されていない

前者はコードの書き漏れなので修正が必要です。後者の場合、

  • 進捗に応じて必ず異なる文字列になるようにする(例:"50 % 完了")
  • 同じ文字列でも再描画したい場面では、ProgressBarZ1.Invalidate() を明示的に呼ぶ

といった工夫で対応できます。

背景色とのコントラストが弱く、文字が見えづらい

標準の ProgressBar は緑系のバーがデフォルトのため、黒い文字でも視認性が高いケースが多いですが、カスタムテーマや OS の設定によっては見えづらくなります。次のような対策が考えられます。

  • 文字色を白 (Color.White) にしてコントラストを上げる
  • 太字フォントにして視認性を確保する
  • ドロップシャドウ風に、黒と白の 2 回描画を行う

例えば、簡易的な縁取りをする場合は、次のように 2 回描画すると読みやすくなります。

Using g As Graphics = Graphics.FromHwnd(Me.Handle)
    Dim rect As New Rectangle(0, 0, Me.Width, Me.Height)

    Using fmt As New StringFormat(StringFormatFlags.NoWrap)
        fmt.Alignment = StringAlignment.Center
        fmt.LineAlignment = StringAlignment.Center

        ' 影(黒っぽい色)を少しずらして描画
        Using shadow As New SolidBrush(Color.FromArgb(128, Color.Black))
            Dim shadowRect = Rectangle.Inflate(rect, -1, -1)
            g.DrawString(_displayText, Me.Font, shadow, shadowRect, fmt)
        End Using

        ' 本来の文字(白)
        Using br As New SolidBrush(Me.ForeColor)
            g.DrawString(_displayText, Me.Font, br, rect, fmt)
        End Using
    End Using
End Using

このように描画を少し工夫するだけで、視認性の高い ProgressBar に仕上げることができます。

まとめ:ProgressBarZ で「伝わる」進捗表示を実現する

本記事では、Windows Forms(VB.NET)の ProgressBar を拡張したカスタムクラス ProgressBarZ を例に、進捗バー上に任意のテキストを動的に重ねて表示する方法を解説しました。

  • DisplayText プロパティを追加し、文字列を外部から自由に設定できるようにする
  • プロパティの Set で Invalidate() を呼び、変更を確実に再描画へ反映させる
  • WndProc で WM_PAINT をフックし、AdditionalPaint でバー上にテキストを描画する
  • フォント・文字色・配置は Font / ForeColor や StringFormat で柔軟に変更可能
  • 必要に応じて描画スタイルや CreateParams を調整し、チラつきを抑えることもできる

単純なバー表示だけではユーザーに伝わりにくかった進捗状況も、"50 % 完了" や "ファイルのコピー中..." といった明確なメッセージを重ねることで、分かりやすく・安心感のある UI に変えることができます。

既存プロジェクトの ProgressBar を置き換えるだけでも効果は大きいので、ぜひ ProgressBarZ のようなカスタムコントロールを自作して、プロジェクト共通の「使いやすい進捗表示コンポーネント」として育ててみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次