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