どこでも使える汎用性の高いプログレスバーダイアログを考えてみた

2023年2月7日

こんにちは、ノムノムです。

マルチスレッドのところでプログレスバーを使ったサンプルを作成しましたがもっと汎用性が高く実務に使えるようなプログレスバーダイアログはできないだろうかと思い記事にしてみました。

マルチスレッドで並列処理して快適に
マルチスレッドで並列処理して快適に
こんにちは、ノムノムです。 今回はマルチスレッドをやってみたいと思います。 マルチスレッド マルチスレッドとは…
http://nomux2.net/multithread/

今まで業務で見てきたプログレスバーダイアログは、ダイアログに直接処理を書いてしまうような人が結構いました。

それを見るたびに見難いな、面倒くさいなと思っていました。

今回は、使いやすいプログレスバーを考えてみました。

マルチスレッド用の基底クラスを作ってみた。

基底クラスに関してはクラスの継承で説明しました。

クラスの継承で機能を拡張すればめちゃくちゃ便利!
クラスの継承で機能を拡張すればめちゃくちゃ便利!
こんにちは、ノムノムです。 今回はクラスの継承を行います。 クラスの継承とは、基底クラスの機能を引き継いで新し…
http://nomux2.net/class-inheritance/

汎用性の高いダイアログってことは、プログレスバーダイアログには処理は置かず処理を持ったクラスを渡すことができればいいと考えました。

その結果、以下のような基底クラスを作成しました。

public abstract class BaseThreadClass
{
    public bool Cancel { get; set; }            //キャンセルフラグ
    public bool Completed { get; set; }         //完了フラグ
    public int ProgressMax { get; set; }        //進捗最大値
    public int ProgressValue { get; set; }      //進捗値

    //コンストラクタ
    public BaseThreadClass()
    {
        ProgressMax = 100;
        ProgressValue = 0;
    }

    //マルチスレッドの処理を仮想関数として派生先に強制的にオーバーライドさせる
    public abstract int MultiThreadMethod();

    //非同期処理開始
    public bool RunMultiThread()
    {
        //変数初期化
        Cancel = false;
        Completed = false;

        //非同期処理(マルチスレッド)開始
        Task.Run(new Func<int>(MultiThreadMethod));

        return true;
    }
}

プログレスバーに必要な情報(3~6行目)とマルチスレッドの仮想関数(16行目)、そしてマルチスレッドを実行する処理(26行目)を書きました。

基底クラスを継承したクラス

今回はわかりやすくAスレッドクラスとします。

public class AThread : BaseThreadClass
{
    public override int MultiThreadMethod()
    {
        ProgressMax = 100;
        for (int i = 0; i < ProgressMax; i++)
        {
            if (Cancel == true)
            {
                break;
            }

            ProgressValue = i;

            System.Threading.Thread.Sleep(150);
        }

        //処理完了
        Completed = true;

        return 0;
    }
}

先ほど紹介した基底クラス(BaseThreadClassクラス)を継承しています。(1行目)

そして仮想関数であるMultiThreadMethod関数の処理をオーバーライドで記載しなければいけません。(3行目)

これだけですね。 基底クラス(BaseThreadClassクラス)を 使っていくつでも処理を増産できるし、処理はクラスごとに書かれているので可読性も保てているのではないでしょうか?

プログレスバーダイアログの実装

次にプログレスバーの設定です。これはあまり変わりありません。

プロパティ名設定値
ControlBoxFalse
MaximizeBoxFalse
MinimizeBoxFalse
StartPositionCenterScreen
TopMostTrue

それではソースの方を見てみます。

public partial class dlgProgress2 : Form
{
    BaseThreadClass _base = null;

    public dlgProgress2()
    {
        InitializeComponent();
    }

    private void dlgProgress_Load(object sender, EventArgs e)
    {
        this.progressBar1.Value = 0;

        //マルチスレッドのクラスがない場合は、何もしないで閉じる
        if (_base == null)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
            return;
        }

        //タイマー開始
        this.timer1.Enabled = true;

        //マルチスレッドスタート
        _base.RunMultiThread();
    }

    /// <summary>
    /// キャンセル処理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnCancel_Click(object sender, EventArgs e)
    {
        _base.Cancel = true;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        //進捗状況を設定
        this.progressBar1.Maximum = _base.ProgressMax;
        this.progressBar1.Value = _base.ProgressValue;

        //終了判定
        if (_base.Completed)
        {
            if (_base.Cancel)
            {
                this.DialogResult = DialogResult.Cancel;
                this.Close();
            } else
            {
                this.DialogResult = DialogResult.OK;
                this.Close();
            }
        }
    }

    /// <summary>
    /// BaseThreadClassを継承したクラスを取得
    /// </summary>
    /// <param name="thread"></param>
    public void SetThreadClass(BaseThreadClass thread)
    {
        _base = thread;
    }
}

プログレスバーに非同期処理を持たせることで、呼び出し元のフォームへの負担を減らしています。
タイマーイベントで進捗状況のプログレスバーの設定と完了した時の処理を記載しています。

呼出し側の処理

あとはフォームから呼び出してあげるだけです。

AThread a = new AThread();

using (dlgProgress2 dlg = new dlgProgress2())
{
    //マルチスレッド用のクラスを渡す
    dlg.SetThreadClass(a);

    //プログレスバーの表示と処理開始
    if (dlg.ShowDialog(this) == DialogResult.OK)
    {
        MessageBox.Show("完了しました。");
    } else
    {
        MessageBox.Show("キャンセルしました。");
    }
}

マルチスレッド用のクラスを渡してShowDialog関数を呼び出してあげればいいですね。

まとめ

基底クラス(BaseThreadClassクラス) を継承するればいくつでも並列処理が行えます。

クラスの継承とマルチスレッドの合わせ技でした。

このようにプログラミングでの解決方法は一つではありません。
十人十色いろんな方法があります。こういうところは国語っぽいですね。

論理的思考なんていう言い方してますが、結局自分で考え自分の中での最適解をだして解決していくしかないですね。

ここまで読んでいただいてありがとうございました。