雪国の技術的な夏

プログラミング的な話や特定のツール設定の話など。 ※ただし変態に限る。

ステータスバーのプログレスバーに進捗率の文字列を表示する

[目的]
・ステータスバーの進捗にテキストを印字したい


[仕様]
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));
            }
        }

    }
}


■解説

VB.NET

    ''' <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

表示するテキスト色として用意。
ちょっとまだデザイン仕様確認できていないので問題あり。

VB.NET

    ''' <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も同時生成する必要があるためオーバーライド実装。


VB.NET

        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#...)
で使用されているスタイル変更と同じになる。


VB.NET

    ''' *******************************************************************************************
    ''' <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イベントを発生させテキスト表示を更新させている。
(必要性があったかどうかは忘れた)


VB.NET

        If (Application.RenderWithVisualStyles) Then

C#

            if (Application.RenderWithVisualStyles)

XPスタイルの判断材料、だったはず。
XPマシンではRender?が例外出て描画できない。


VB.NET

    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の作業完了」みたいな感じで出したいと思ったので。
必要なければ消していいさ。


VB.NET

    '''' <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#版追記