ステータスバーのプログレスバーに進捗率の文字列を表示する
[目的]
・ステータスバーの進捗にテキストを印字したい
[仕様]
・C#.NETもしくはVB.NET
・Flamework *.*以上
・XPスタイル対応
・ToolStripProgressBar Class (System.Windows.Forms) | Microsoft Docsを使用する
[ソース&解説]
・(VB版)PercentToolStripProgressBar.vb
Imports System.ComponentModel Imports System.Reflection Imports System.Windows.Forms.Design ''' ******************************************************************************************* ''' <summary> ''' プログレスバー。 ''' バーに文字列を表示させる版。 ''' </summary> ''' <remarks> ''' ''' 参考:https://dobon.net/vb/dotnet/control/pbshowtext.html ''' ''' </remarks> ''' ******************************************************************************************* <ToolStripItemDesignerAvailability( ToolStripItemDesignerAvailability.StatusStrip Or ToolStripItemDesignerAvailability.ToolStrip)> Public Class PercentToolStripProgressBar Inherits ToolStripProgressBar ''' <summary>進捗テキスト色</summary> Private MyTextBrush As Brush ''' <summary>進捗テキスト色</summary> Private MyTextColor As Color ''' <summary>進捗テキスト色</summary> <DefaultValue(GetType(SystemColors), "ControlText"), 'TODO:[20181019]デザイン側でうまくセットされていない?リセット時にEmptyになる。 Description("進捗テキスト色。")> Public Property TextColor As Color Get Return MyTextColor End Get Set(value As Color) MyTextColor = value If Not (Application.RenderWithVisualStyles) Then MyTextBrush = New SolidBrush(value) End If End Set End Property ''' <summary>進捗ゲージ色</summary> Private MyForeBrush As Brush ''' <summary>進捗ゲージ色</summary> Public Overrides Property ForeColor As Color Get Return MyBase.ForeColor End Get Set(value As Color) MyBase.ForeColor = value If Not (Application.RenderWithVisualStyles) Then MyForeBrush = New SolidBrush(value) End If End Set End Property ''' <summary>進捗背景</summary> Private MyBackBrush As Brush ''' <summary>進捗背景</summary> Public Overrides Property BackColor As Color Get Return MyBase.BackColor End Get Set(value As Color) MyBase.BackColor = value If Not (Application.RenderWithVisualStyles) Then MyBackBrush = New SolidBrush(value) End If End Set End Property ''' ******************************************************************************************* ''' <summary>プログレスバーの現在位置を取得または設定します。</summary> ''' <remarks>セット時にテキストも同時更新</summary> ''' ******************************************************************************************* Public Shadows Property Value As Integer Get Return MyBase.Value End Get Set(ByVal value As Integer) ' 範囲外は上限下限をセット If (value < Minimum) Then value = Minimum End If If (Maximum < value) Then value = Maximum End If MyBase.Value = value Text = GetDispStr(GetParcent()) End Set End Property ''' <summary>描画用メッセージ</summary> Private StatusMessage As String = "" ''' <summary>最大作業数</summary> Public Property MaxProc As Integer = 1 ''' ******************************************************************************************* ''' <summary> ''' コンストラクタ ''' </summary> ''' ******************************************************************************************* Public Sub New() ' プログレスバーに文字列表示させるためのスタイルセット ProgressBar.GetType().InvokeMember( "SetStyle", BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, ProgressBar, New Object() { ControlStyles.UserPaint Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer, True } ) TextColor = SystemColors.ControlText If (Application.RenderWithVisualStyles) Then 'RENDERER_BAR = New VisualStyles.VisualStyleRenderer(VisualStyles.VisualStyleElement.ProgressBar.Bar.Normal) 'RENDERER_CHUNK = New VisualStyles.VisualStyleRenderer(VisualStyles.VisualStyleElement.ProgressBar.Chunk.Normal) Else MyForeBrush = New SolidBrush(MyBase.ForeColor) MyBackBrush = New SolidBrush(MyBase.BackColor) End If Font = New Font("MS ゴシック", Font.Size) 'NOTE.パーセント表記を空白で埋める視覚的に見やすくするため End Sub ''' ******************************************************************************************* ''' <summary>パーセンテージ的に値を割り当てる</summary> ''' <param name="par">進捗パーセント</param> ''' ******************************************************************************************* Public Sub SetPar(par As Double) Debug.Print("[" & DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") & "]" & StatusMessage & "(" & par.ToString("000.00") & "%)") '' 範囲外は上限下限をセット 'If (par < 0.0) Then ' Value = Minimum ' Return 'End If 'If (100.0 < par) Then ' Value = Maximum ' Return 'End If ' 合わせて計算 Value = CInt((Maximum - Minimum) * par / 100) End Sub ''' ******************************************************************************************* ''' <summary>パーセンテージ的に値を割り当てる</summary> ''' <param name="par">進捗パーセント</param> ''' <param name="sMsg">表示メッセージ</param> ''' ******************************************************************************************* Public Sub SetPar(par As Double, sMsg As String) StatusMessage = sMsg SetPar(par) End Sub ''' ******************************************************************************************* ''' <summary>パーセンテージ的に値を割り当てる</summary> ''' <param name="par">進捗パーセント</param> ''' <param name="NowProcNum">現在の作業番号</param> ''' <param name="sMsg">表示メッセージ</param> ''' ******************************************************************************************* Public Sub SetPar(par As Double, NowProcNum As Integer, sMsg As String) SetPar(par, String.Format("[{0}/{1}]{2}", NowProcNum, MaxProc, sMsg)) End Sub ''' ******************************************************************************************* ''' <summary> ''' プログレスバーを初期化する ''' </summary> ''' ******************************************************************************************* Public Sub ClearPar() SetPar(0.0, "") End Sub ''' ******************************************************************************************* ''' <summary>パーセントの数値を取得する</summary> ''' <returns>数値 ※1=100%</returns> ''' ******************************************************************************************* Protected Function GetParcent() As Double Return CDbl(Value - Minimum) / (Maximum - Minimum) End Function ''' ******************************************************************************************* ''' <summary>バー表示用の進捗度</summary> ''' <param name="dPar">パーセント</param> ''' <returns>表示文字列</returns> ''' ******************************************************************************************* Protected Function GetDispStr(dPar As Double) As String Dim sDisp As String sDisp = String.Format("{0:0.00}%", (dPar * 100.0)).PadLeft(7) ' 左空白埋めの3桁2桁 If (String.IsNullOrEmpty(StatusMessage)) Then Return sDisp Else Return sDisp & "(" & StatusMessage & ")" End If End Function ''' ******************************************************************************************* ''' <summary> ''' テキスト変更イベント。 ''' 表示パーセンテージの文字列も更新する。 ''' </summary> ''' <param name="e"></param> ''' ******************************************************************************************* Protected Overrides Sub OnTextChanged(e As EventArgs) MyBase.OnTextChanged(e) ' ペイントイベントで文言更新 ProgressBar.Refresh() End Sub ''' ******************************************************************************************* ''' <summary>ペイント時に進捗テキストを表示</summary> ''' <param name="e"></param> ''' ******************************************************************************************* Protected Overrides Sub OnPaint(e As PaintEventArgs) MyBase.OnPaint(e) Dim percent As Double = GetParcent() ' 0.00~1.00 Dim ClientRectangle As Rectangle = ProgressBar.ClientRectangle ' プログレスバー領域 Dim chunksRect As Rectangle ' 進捗ゲージ領域 '進捗ゲージ計算 With ClientRectangle.Size Dim chunksWidth As Integer = CInt(CDbl(.Width - 2) * percent) ' 進捗ゲージ幅 chunksRect = New Rectangle(1, 1, chunksWidth, .Height - 2) End With If (Application.RenderWithVisualStyles) Then Const tff As TextFormatFlags = (TextFormatFlags.Left Or TextFormatFlags.VerticalCenter Or TextFormatFlags.SingleLine) With e '背景/バー/テキスト を描画する ProgressBarRenderer.DrawHorizontalBar(.Graphics, ClientRectangle) ProgressBarRenderer.DrawHorizontalChunks(.Graphics, chunksRect) TextRenderer.DrawText(.Graphics, Text, Font, ClientRectangle, MyTextColor, tff) End With Else Dim ClientRectangleF As RectangleF With ClientRectangle ClientRectangleF = New RectangleF(.X + 2, .Y + (.Height - Font.Height) / 2, .Width, .Height) End With ' XPスタイル互換 With e.Graphics .FillRectangle(MyBackBrush, ClientRectangle) .FillRectangle(MyForeBrush, chunksRect) .DrawString(Text, Font, MyTextBrush, ClientRectangleF, New StringFormat(StringFormatFlags.NoWrap)) End With End If End Sub End Class
・(C#版)PercentToolStripProgressBar.cs
namespace PercentToolStripProgressBar { using System; using System.ComponentModel; using System.Drawing; using System.Reflection; using System.Windows.Forms; using System.Windows.Forms.Design; /// <summary> /// プログレスバー。 /// バーに文字列を表示させる版。 /// </summary> [ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.StatusStrip | ToolStripItemDesignerAvailability.ToolStrip)] public class PercentToolStripProgressBar : ToolStripProgressBar//ProgressBar { /// <summary>進捗テキスト色</summary> private Brush MyTextBrush; /// <summary>進捗テキスト色</summary> private Color MyTextColor; /// <summary>進捗テキスト色</summary> [DefaultValue(typeof(SystemColors), "ControlText"), //TODO:[20181019]デザイン側でうまくセットされていない?リセット時にEmptyになる。 Description("進捗テキスト色。")] public Color TextColor { get { return MyTextColor; } set { MyTextColor = value; if (!Application.RenderWithVisualStyles) { MyTextBrush = new SolidBrush(value); } } } /// <summary>進捗ゲージ色</summary> private Brush MyForeBrush; /// <summary>進捗ゲージ色</summary> public override Color ForeColor { get { return base.ForeColor; } set { base.ForeColor = value; if (!Application.RenderWithVisualStyles) { MyForeBrush = new SolidBrush(value); } } } /// <summary>進捗背景</summary> private Brush MyBackBrush; /// <summary>進捗背景</summary> public override Color BackColor { get { return base.BackColor; } set { base.BackColor = value; if (!Application.RenderWithVisualStyles) { MyBackBrush = new SolidBrush(value); } } } /// <summary>プログレスバーの現在位置を取得または設定します。</summary> /// <remarks>セット時にテキストも同時更新</remarks> public new int Value { get { return base.Value; } set { // 範囲外は上限下限をセット if (value < Minimum) { value = Minimum; } else if (Maximum < value) { value = Maximum; } base.Value = value; Text = GetDispStr(GetParcent()); } } /// <summary>描画用メッセージ</summary> private string StatusMessage = ""; /// <summary>最大作業数</summary> public int MaxProc { private get; set; } = 1; /// <summary> /// コンストラクタ /// </summary> public PercentToolStripProgressBar() { ProgressBar.GetType().InvokeMember( "SetStyle", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, ProgressBar, new object[] { ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true } ); TextColor = SystemColors.ControlText; if (Application.RenderWithVisualStyles) { //RENDERER_BAR = New VisualStyles.VisualStyleRenderer(VisualStyles.VisualStyleElement.ProgressBar.Bar.Normal) //RENDERER_CHUNK = New VisualStyles.VisualStyleRenderer(VisualStyles.VisualStyleElement.ProgressBar.Chunk.Normal) } else { MyForeBrush = new SolidBrush(base.ForeColor); MyBackBrush = new SolidBrush(base.BackColor); } Font = new Font("MS ゴシック", Font.Size); //NOTE.パーセント表記を空白で埋める視覚的に見やすくするため } /// <summary> /// パーセンテージ的に値を割り当てる /// </summary> /// <param name="par">進捗パーセント</param> public void SetPar(double par) { 共通.Common.DebugPrint(StatusMessage + "(" + par.ToString("00.00") + "%)"); //// 範囲外は上限下限をセット //if (par <= 0.0) //{ // Value = Minimum; // return; //} //if (100.0 <= par) //{ // Value = Maximum; // return; //} // 合わせて計算 Value = (int)((Maximum - Minimum) * par / 100); return; } /// <summary> /// パーセンテージ的に値を割り当てる /// </summary> /// <param name="par">進捗パーセント</param> /// <param name="sMsg">表示メッセージ</param> public void SetPar(double par, string sMsg) { StatusMessage = sMsg; SetPar(par); } /// <summary> /// パーセンテージ的に値を割り当てる /// </summary> /// <param name="par">進捗パーセント</param> /// <param name="NowProcNum">現在の作業番号</param> /// <param name="sMsg">表示メッセージ</param> public void SetPar(double par, int NowProcNum, string sMsg) { SetPar(par, string.Format("[{0}/{1}]{2}", NowProcNum, MaxProc, sMsg)); } /// <summary> /// プログレスバーを初期化する /// </summary> public void ClearPar() { SetPar(0.0, ""); } /// <summary>パーセントの数値を取得する</summary> /// <returns>数値 ※1.00=100%</returns> private double GetParcent() { return ((double)(Value - Minimum)) / (Maximum - Minimum); } /// <summary>バー表示用の進捗度</summary> /// <param name="dPar">パーセント</param> /// <returns>表示文字列</returns> private string GetDispStr(double dPar) { string sDisp; sDisp = string.Format(" {0:0.00}%", (dPar * 100.0)).PadLeft(7); // 左空白埋めの3桁2桁 if (string.IsNullOrEmpty(StatusMessage)) { return sDisp; } else { return sDisp + "(" + StatusMessage + ")"; } } /// <summary> /// テキスト変更イベント。 /// 表示パーセンテージの文字列も更新する。 /// </summary> /// <param name="e"></param> protected override void OnTextChanged(EventArgs e) { base.OnTextChanged(e); // ペイントイベントで文言更新 ProgressBar.Refresh(); } /// <summary> /// 描画イベント。 /// ペイント時に進捗テキストを表示。 /// </summary> /// <param name="e"></param> protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); double parcent = GetParcent(); // 0.00~1.00 Rectangle ClientRectangle = ProgressBar.ClientRectangle; // プログレスバー領域 Rectangle chunksRect; // 進捗ゲージ領域 int chunksWidth; // 進捗ゲージ幅 chunksWidth = (int)((double)(ClientRectangle.Size.Width - 2) * parcent); chunksRect = new Rectangle(1, 1, chunksWidth, ClientRectangle.Size.Height - 2); if (Application.RenderWithVisualStyles) { const TextFormatFlags tff = (TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); // 背景/バー/テキスト を描画する ProgressBarRenderer.DrawHorizontalBar(e.Graphics, ClientRectangle); ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, chunksRect); TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle, MyTextColor, tff); } else { // XPスタイル互換 RectangleF ClientRectangleF = new RectangleF(ClientRectangle.X + 2, ClientRectangle.Y + (ClientRectangle.Height - Font.Height) / 2, ClientRectangle.Width, ClientRectangle.Height); e.Graphics.FillRectangle(MyBackBrush, ClientRectangle); e.Graphics.FillRectangle(MyForeBrush, chunksRect); e.Graphics.DrawString(Text, Font, MyTextBrush, ClientRectangleF, new StringFormat(StringFormatFlags.NoWrap)); } } } }
''' <summary>進捗テキスト色</summary> Private MyTextBrush As Brush ''' <summary>進捗テキスト色</summary> Private MyTextColor As Color ''' <summary>進捗テキスト色</summary> <DefaultValue(GetType(SystemColors), "ControlText"), Description("進捗テキスト色。")> Public Property TextColor As Color
・C#
/// <summary>進捗テキスト色</summary> private Brush MyTextBrush; /// <summary>進捗テキスト色</summary> private Color MyTextColor; /// <summary>進捗テキスト色</summary> [DefaultValue(typeof(SystemColors), "ControlText"), Description("進捗テキスト色。")] public Color TextColor
表示するテキスト色として用意。
ちょっとまだデザイン仕様確認できていないので問題あり。
''' <summary>進捗ゲージ色</summary> Private MyForeBrush As Brush ''' <summary>進捗ゲージ色</summary> Public Overrides Property ForeColor As Color ''' <summary>進捗背景</summary> Private MyBackBrush As Brush ''' <summary>進捗背景</summary> Public Overrides Property BackColor As Color
・C#
/// <summary>進捗ゲージ色</summary> private Brush MyForeBrush; /// <summary>進捗ゲージ色</summary> public override Color ForeColor /// <summary>進捗背景</summary> private Brush MyBackBrush; /// <summary>進捗背景</summary> public override Color BackColor
進捗ゲージ色/進捗背景。
処理の都合(XP対策)でセットの際にBrushも同時生成する必要があるためオーバーライド実装。
ProgressBar.GetType().InvokeMember( "SetStyle", BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.InvokeMethod, Nothing, ProgressBar, New Object() { ControlStyles.UserPaint Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer, True } )
・C#
ProgressBar.GetType().InvokeMember( "SetStyle", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, ProgressBar, new object[] { ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true } );
この実装の肝。
COMコンポーネント(ActiveX)の呼び出しにも使用されると思うが、
privateでもメソッドなどを呼び出すことができる。
後述の備考でも書いてるが、
ProgressBarにパーセンテージなどの文字列を表示する - .NET Tips (VB.NET,C#...)
で使用されているスタイル変更と同じになる。
''' ******************************************************************************************* ''' <summary>プログレスバーの現在位置を取得または設定します。</summary> ''' <remarks>セット時にテキストも同時更新</summary> ''' ******************************************************************************************* Public Shadows Property Value As Integer Get Return MyBase.Value End Get Set(ByVal value As Integer) ' 範囲外は上限下限をセット If (value < Minimum) Then value = Minimum End If If (Maximum < value) Then value = Maximum End If MyBase.Value = value Text = GetDispStr(GetParcent()) End Set End Property ''' ******************************************************************************************* ''' <summary> ''' テキスト変更イベント。 ''' 表示パーセンテージの文字列も更新する。 ''' </summary> ''' <param name="e"></param> ''' ******************************************************************************************* Protected Overrides Sub OnTextChanged(e As EventArgs) MyBase.OnTextChanged(e) ' ペイントイベントで文言更新 ProgressBar.Refresh() End Sub
・C#
/// <summary>プログレスバーの現在位置を取得または設定します。</summary> /// <remarks>セット時にテキストも同時更新</remarks> public new int Value { get { return base.Value; } set { // 範囲外は上限下限をセット if (value < Minimum) { value = Minimum; } else if (Maximum < value) { value = Maximum; } base.Value = value; Text = GetDispStr(GetParcent()); } } /// <summary> /// テキスト変更イベント。 /// 表示パーセンテージの文字列も更新する。 /// </summary> /// <param name="e"></param> protected override void OnTextChanged(EventArgs e) { base.OnTextChanged(e); // ペイントイベントで文言更新 ProgressBar.Refresh(); }
進捗のテキスト表示にはTextを使用している。
進捗値が変更されると同時にTextも同時更新している。
またTextChanged()イベントで故意にRefresh()を呼び出すことで
OnPaintイベントを発生させテキスト表示を更新させている。
(必要性があったかどうかは忘れた)
If (Application.RenderWithVisualStyles) Then
・C#
if (Application.RenderWithVisualStyles)
XPスタイルの判断材料、だったはず。
XPマシンではRender?が例外出て描画できない。
Public Sub SetPar(par As Double) Public Sub SetPar(par As Double, sMsg As String) Public Sub SetPar(par As Double, NowProcNum As Integer, sMsg As String)
・C#
public void SetPar(double par) public void SetPar(double par, string sMsg) public void SetPar(double par, int NowProcNum, string sMsg)
簡単に内容出したかったので…
「10.00% 1/10の作業完了」みたいな感じで出したいと思ったので。
必要なければ消していいさ。
'''' <summary>プログレスバー:visualスタイル用</summary> 'Private ReadOnly RENDERER_BAR As VisualStyles.VisualStyleRenderer '''' <summary>プログレスバーのゲージ:visualスタイル用</summary> 'Private ReadOnly RENDERER_CHUNK As VisualStyles.VisualStyleRenderer 'RENDERER_BAR = New VisualStyles.VisualStyleRenderer(VisualStyles.VisualStyleElement.ProgressBar.Bar.Normal) 'RENDERER_CHUNK = New VisualStyles.VisualStyleRenderer(VisualStyles.VisualStyleElement.ProgressBar.Chunk.Normal)
別の描画方法の名残。
ちょっともったいなかったのでソースだけ残している。
[備考]
・ProgressBar版
→ProgressBarにパーセンテージなどの文字列を表示する - .NET Tips (VB.NET,C#...)
・どこぞにも書いてるように、文字列を表示させるのは非推奨らしいので注意。
・ソースが中途半端だけど許してにゃんv
・ソースの保証はできません。
・ソースに関する苦情は一切受け付けません
・記事としての見た目はまた日を改めて…
[履歴]
・(2019/03/19)潜在バグあったので後日修正予定
・(2019/04/15)上記バグ修正版の反映、C#版追記