天天看點

C# 根據BackgroundWorker異步模型和ProgressBar控件,自定義進度條控件

C# 根據BackgroundWorker異步模型和ProgressBar控件,自定義進度條控件

前言

程式開發過程中,難免會有的業務邏輯,或者算法之類産生讓人能夠感覺的耗時操作,例如循環中對複雜邏輯處理;擷取資料庫百萬乃至千萬級資料;http請求的時候等......

使用者在使用UI操作并不知道程式的内部處理,進而誤操作導緻程式無響應,關閉程式等待影響體驗的情況,是以,在等待過程中提供友好的等待提示是有必要的,接下來

我們一起封裝一個自定義進度條控件!

主要使用技術(C#相關)

BackgroundWoker異步模型

ProgressBar控件

泛型

定時器 System.Timers.Timer

自定義控件開發

項目解決方案

BackgroundworkerEx : 自定義進度條控件工程

Test : 調用BackgroundworkerEx的工程(隻是展示如何調用)

處理控件樣式

建立一個ProgressbarEx名稱的 使用者控件

添加Labal控件(lblTips),用于展示進度條顯示的資訊狀态

添加一個PictureBox控件(PicStop),充當關閉按鈕,用于擷取使用者點選事件,觸發關閉/終止進度條

添加進度條ProgressBar控件(MainProgressBar)

處理代碼如下:

進度條樣式為"不斷循環",并且速度為50

該自定義使用者控件不展示在工作列中

圖檔控件被點選事件------>設定目前屬性IsStop=true,訓示過程終止;

TipMessage屬性,用于設定進度條的資訊

SetProgressValue(int value) 設定進度條的Value屬性,使得在ProgressBarStyle.Marquee樣式中動畫平滑

MouseDown/MouseUp/MouseMove這三個事件是用于拖動無邊框的使用者控件(代碼就不貼了)

public ProgressbarEx()

{

InitializeComponent();

MainProgressBar.Style = ProgressBarStyle.Marquee;
MainProgressBar.MarqueeAnimationSpeed = 50;

this.ShowInTaskbar = false;

PicStop.Click += (s, eve) =>
{
IsStop = true;
};

this.MouseDown += CusProgressForm_MouseDown;
this.MouseUp += CusProgressForm_MouseUp;
this.MouseMove += CusProgressForm_MouseMove;           

}

///

/// Need Stop ?

public bool IsStop { get; private set; } = false;

/// TipMessage

public string TipMessage { get; set; }

public string TipMessage

get
{
return lblTips.Text;
}
set
{

lblTips.Text = value;
}           

/// Set ProgressBar value ,which makes ProgressBar smooth

public void SetProgressValue(int value)

if (MainProgressBar.Value == 100) MainProgressBar.Value = 0;

MainProgressBar.Value += value;
           

到現在,這個自定義進度條控件的樣式基本完成了.

功能邏輯處理

運作前所需

定義BackgroundWorkerEx泛型類,并且繼承于 IDisposable

釋放資源;

/// <summary>
    /// Dispose
    /// </summary>
    public void Dispose()
    {
        try
        {
            DoWork = null;
            RunWorkCompleted = null;
            WorkStoped = null;

            _mWorkerThread = null;
            _mWorker.Dispose();
            _mWorker = null;
            _mTimer = null;
        }
        catch (Exception){}
    }           

T用與異步處理的時候,傳遞T類型

因為我們是通過.Net 的 BackgroundWorker異步模型來做的,是以我們理所當然定義相關的事件:

異步開始

異步完成

加上我們自定義擴充的異步停止

......報告進度事件在此進度條樣式中并不需要

我們先定義這四個事件所用到的參數,因為在BackgroundWorkerEx泛型類中,我們還是使用BackgroundWorker來處理異步過程,是以我們定義的參數泛型類需要繼承原來的參數類型,并且在傳輸傳遞中,将原生BackgroundWorker的Argument,Result屬性轉成全局的泛型T,這樣我們在外部調用的時候,拿到的傳回結果就是我們傳入到BackgroundWorkerEx泛型類中的T類型,而不需要使用as進行轉換; 注:因為原生沒有停止相關事件,是以自定義異步停止的事件參數使用的是DoWorkEventArgs

public class DoWorkEventArgs<T> : DoWorkEventArgs
{
    public new T Argument { get; set; }
    public new T Result { get; set; }
    public DoWorkEventArgs(object argument) : base(argument)
    {
        Argument = (T)argument;
    }
}
           
public class RunWorkerCompletedEventArgs<T> : RunWorkerCompletedEventArgs
{
    public new T Result { get; set; }
    public RunWorkerCompletedEventArgs(object result, Exception error, bool cancelled) : base(result, error, cancelled)
    {
        Result = (T)result;
    }
}           

接着我們需要去定義事件,參數使用以上定義的泛型類

public delegate void DoWorkEventHandler(DoWorkEventArgs<T> Argument);
    /// <summary>
    /// StartAsync
    /// </summary>
    public event DoWorkEventHandler DoWork;

    public delegate void StopEventHandler(DoWorkEventArgs<T> Argument);
    /// <summary>
    /// StopAsync
    /// </summary>
    public event StopEventHandler WorkStoped;

    public delegate void RunWorkCompletedEventHandler(RunWorkerCompletedEventArgs<T> Argument);
    /// <summary>
    /// FinishAsync
    /// </summary>
    public event RunWorkCompletedEventHandler RunWorkCompleted;           

定義全局的字段

private BackgroundWorker _mWorker = null;異步操作必要;

private T _mWorkArg = default(T);操作傳遞進來的參數類并且傳回到外部

private Timer _mTimer; 定時器檢測自定義進度條控件屬性IsStop是否為true,并且動态修改進度條消息

private Thread _mWorkerThread = null;異步操作在該線程中,終止時調用About()抛出ThreadAbortException異常,用于标記目前是停止而不是完成狀态

private int _miWorkerStartDateSecond = 0; 異步消耗時間(非必要)

private int _miShowProgressCount = 0; 動态顯示"."的個數(非必要)

private ProgressbarEx _mfrmProgressForm = null; 自定義進度條控件執行個體

/// <summary>
    /// .Net  BackgroundWorker
    /// </summary>
    private BackgroundWorker _mWorker = null;

    /// <summary>
    /// Whole Para
    /// </summary>
    private T _mWorkArg = default(T);

    /// <summary>
    /// Timer
    /// </summary>
    private Timer _mTimer = null;

    /// <summary>
    /// WorkingThread
    /// </summary>
    private Thread _mWorkerThread = null;

    /// <summary>
    /// Async time sec
    /// </summary>
    private int _miWorkerStartDateSecond = 0;

    /// <summary>
    /// Async time dot
    /// </summary>
    private int _miShowProgressCount = 0;

    /// <summary>
    /// ProgressbarEx
    /// </summary
    private ProgressbarEx _mfrmProgressForm = null;
           

定義全局屬性

IsBusy 傳回_mWorker的工作忙碌是否

ProgressTip 自定義進度條控件顯示内容

/// <summary>
    /// Express Busy
    /// </summary>
    public bool IsBusy
    {
        get
        {
            if (_mWorker != null)
            {
                return _mWorker.IsBusy;
            }
            return false;
        }
    }

    /// <summary>
    /// 進度條提示 預設: 正在加載資料,請稍後[{0}]{1}
    /// </summary>
    public string ProgressTip { get; set; } = "Elapsed Time[{0}]{1}";           

到現在,我們已經将必要的字段,屬性,樣式都處理完成!!!  接下來我們就要實作方法

方法實作

異步工作事件,用法與BackgroundWorker一緻,

如果調用處沒有注冊DoWork事件,則直接傳回

将接受到的參數建立成泛型參數類

開線程,将異步操作放在該線程中操作,注意設定線程的IsBackground=true,防止主程序意外退出,線程還在處理

循環直到線程結束

e.Result = Argument.Result;将結果賦予Result,在停止或者完成事件中可以擷取到結果

/// <summary>
    /// Working
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Worker_DoWork(object sender, DoWorkEventArgs e)
    {
        if (DoWork == null)
        {
            e.Cancel = true;
            return;
        }

        DoWorkEventArgs<T> Argument = new DoWorkEventArgs<T>(e.Argument);

        try
        {
            if (_mWorkerThread != null && _mWorkerThread.IsAlive)
            {
                _mWorkerThread.Abort();
            }
        }
        catch (Exception)
        {
            Thread.Sleep(50);
        }

        _mWorkerThread = new Thread(a =>
        {
            try
            {
                DoWork?.Invoke(a as DoWorkEventArgs<T>);
            }
            catch (Exception)
            {

            }
        });

        _mWorkerThread.IsBackground = true;
        _mWorkerThread.Start(Argument);

        //Maybe cpu do not start thread
        Thread.Sleep(20);

        //Wait.....
        while (_mWorkerThread.IsAlive)
        {
            Thread.Sleep(50);
        }
        e.Result = Argument.Result;
    }           

異步完成/停止

當線程停止抛出異常(catch但是不處理)/線程完成時會進入異步完成事件

完成後,将自定義進度條控件執行個體關閉,釋放

将全局的BackgroundWorker執行個體_mWorker相關事件取消注冊,并且檢查線程情況

感覺線程情況,如果線程狀态為ThreadState.Aborted意味着線程被停止了,調用停止事件,否則調用完成事件

/// <summary>
  /// Completed
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void Worker_RunWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
      try
      {
          if (_mfrmProgressForm != null)
          {
            _mfrmProgressForm.Close();
              _mfrmProgressForm.Dispose();
            _mfrmProgressForm = null;
          }

            if (_mWorker != null)
            {
                _mWorker.DoWork -= Worker_DoWork;
                _mWorker.RunWorkerCompleted -= Worker_RunWorkCompleted;

                try
                {
                    if (_mWorkerThread != null && _mWorkerThread.IsAlive) _mWorkerThread.Abort();
                }
                catch (Exception) { }
            }

          //In timer, When stop progress will make thread throw AbortException
          if (_mWorkerThread != null && _mWorkerThread.ThreadState == ThreadState.Aborted)
        {
              WorkStoped?.Invoke(new DoWorkEventArgs<T>(_mWorkArg));
          }
          else
          {
              RunWorkCompleted?.Invoke(new RunWorkerCompletedEventArgs<T>(e.Result, e.Error, e.Cancelled));
          }
      }
      catch (Exception ex)
      {
          throw ex;
      }
  }           

線程開始

檢查消息提醒内容 , {0}{1}同于顯示異步耗時和".."的個數

在定時器執行方法中,檢查_mfrmProgressForm.IsStop是否為true,這個屬性标志是否被停止;true則抛出異常

_mfrmProgressForm不為Null則不斷修改目前的内容提醒,友好化,實際可以按需處理

/// <summary>
    /// Timer Start 
    /// </summary>
    private void StartTimer()
    {
        //Check user ProgressTip
        if (!ProgressTip.Contains("{0}"))
        {
            ProgressTip += "...Elapsed Time{0}{1}";
        }

        if (_mTimer != null) return;

        //On one sec 
        _mTimer = new Timer(1000);
        _mTimer.Elapsed += (s, e) =>
        {
            //progress and it's stop flag (picture stop)||  this stop flag
            if (_mfrmProgressForm != null && _mfrmProgressForm.IsStop)
            {
                if (_mWorker != null)
                {
                    try
                    {
                        if (_mWorkerThread != null && _mWorkerThread.IsAlive)
                        {
                            if (_mTimer != null && _mTimer.Enabled)
                            {
                                _mTimer.Stop();
                                _mTimer = null;
                            }
                            _mWorkerThread.Abort();
                        }
                    }
                    catch (Exception) { }
                }
            }

            if (_mfrmProgressForm != null)
            {
                //Callback 
                _mfrmProgressForm.Invoke(new Action<DateTime>(elapsedtime =>
                {
                    DateTime sTime = elapsedtime;

                    //worked time
                    _miWorkerStartDateSecond++;
                    if (_mfrmProgressForm != null)
                    {
                        _mfrmProgressForm.SetProgressValue(_miWorkerStartDateSecond);
                    }

                    //.....count
                    _miShowProgressCount++;

                    if (_miShowProgressCount > 6)
                    {
                        _miShowProgressCount = 1;
                    }

                    string[] strs = new string[_miShowProgressCount];

                    string ProgressStr = string.Join(".", strs);

                    string ProgressText = string.Format(ProgressTip, _miWorkerStartDateSecond, ProgressStr);

                    if (_mfrmProgressForm != null)
                    {
                        _mfrmProgressForm.TipMessage = ProgressText;
                    }
                }), e.SignalTime);
            }
        };

        if (!_mTimer.Enabled)
        {
            _mTimer.Start();
        }
    }           

最後一步:異步開始 與BackgroundWorker用法一緻,隻是在最後開始了定時器和進度條控件而已

///

/// Start AsyncWorl
    /// </summary>
    /// <param name="Para"></param>
    public void AsyncStart(T Para)
    {
        //if workeven is  null ,express user do not regist event
        if (DoWork == null)
        {
            return;
        }

        _miWorkerStartDateSecond = 0;
        _miShowProgressCount = 0;

        //init
        if (_mWorker != null && _mWorker.IsBusy)
        {
            _mWorker.CancelAsync();
            _mWorker = null;
        }

        _mWorker = new BackgroundWorker();

        //create progressbar
        _mfrmProgressForm = new ProgressbarEx();

        //add event
        _mWorker.DoWork += Worker_DoWork;
        _mWorker.RunWorkerCompleted += Worker_RunWorkCompleted;

        _mWorker.WorkerReportsProgress = true;
        _mWorker.WorkerSupportsCancellation = true;

        //Set Whole Para
        _mWorkArg = Para;

        _mWorker.RunWorkerAsync(Para);
        //Start timer
        StartTimer();

        _mfrmProgressForm.StartPosition = FormStartPosition.CenterParent;
        _mfrmProgressForm.ShowDialog();
    }           

到這裡,整個的進度條控件已經完成了!

調用

定義一個參數類

/// <summary>
/// Para Class
/// </summary>
public class ParaArg
{
    public DataTable Data { get; set; }
    public string Msg { get; set; }
    public Exception Ex { get; set; }
}           

定義全局的幫助類BackgroundWorkerEx workHelper = null;

if (workHelper != null || (workHelper != null && workHelper.IsBusy))
            {
                workHelper.Dispose();
                workHelper = null;
            }
            if (workHelper == null)
            {
                workHelper = new BackgroundWorkerEx<ParaArg>();
            }

            workHelper.DoWork += (eve) =>
            {
                ParaArg args = eve.Argument;

                try
                { 
                    //ToDo  like Thread.Sleep(20000);
                    Thread.Sleep(10000);
                    args.Msg = "...this is bussiness code result";
                    throw new Exception("");
                }
                catch (Exception ex)
                {
                    args.Ex = ex;
                }
                finally
                {
                    eve.Result = args;
                }

            };
            workHelper.RunWorkCompleted += (eve) =>
            {
                if (eve.Error != null)
                {
                    //get .net backgroundworker exception;
                    //handle this exception;
                    //return ?
                }

                //get your para result
                ParaArg x = eve.Result;
             
                if (x.Ex != null)
                {
                    //get your bussiness exception;
                    //handle this exception;
                    //return ?
                }

                //finially get your need;
                //MayBe to do some UI hanlde and bussiness logical
                string sReusltMsg = x.Msg;
            };

            workHelper.WorkStoped += (eve) =>
            { 
                //if stoped ! it means no error;
                //just get what you want; 
                ParaArg x = eve.Result as ParaArg;

                btnBegin.Enabled = true;
            };

            //參數
            ParaArg arg = new ParaArg()
            {
                Msg = "Msg"
            };

            workHelper.AsyncStart(arg);
           

最後

其實不管是封裝的過程,還是調用,可以說完全就是BackgroundWorker的方式,是以很多BackgroundWorker相關的地方我都沒有很詳細的去說明;隻要看看這個異步模型,就能夠很好了解!大家有空也可以實作以下,有問題也可以詳細,我比較喜歡交流技術~~~

還有一點就是這個解決方案已經放上Github上了,歡迎大家拉下來用

GitHub位址 歡迎star/fork

原文位址

https://www.cnblogs.com/Ligy97/p/12993155.html