前言
程式開發過程中,難免會有的業務邏輯,或者演算法之類產生讓人能夠感知的耗時操作,例如迴圈中對複雜邏輯處理;獲取資料庫百萬乃至千萬級資料;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;
}
/// <summary>
/// Need Stop ?
/// </summary>
public bool IsStop { get; private set; } = false;
/// <summary>
/// TipMessage
/// </summary>
public string TipMessage { get; set; }
/// <summary>
/// TipMessage
/// </summary>
public string TipMessage
{
get
{
return lblTips.Text;
}
set
{
lblTips.Text = value;
}
}
/// <summary>
/// Set ProgressBar value ,which makes ProgressBar smooth
/// </summary>
/// <param name="value"></param>
public void SetProgressValue(int value)
{
if (MainProgressBar.Value == 100) MainProgressBar.Value = 0;
MainProgressBar.Value += value;
}
到現在,這個自定義進度條控制元件的樣式基本完成了.
功能邏輯處理
執行前所需
- 定義
BackgroundWorkerEx<T>
泛型類,並且繼承於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<T>
泛型類中,我們還是使用BackgroundWorker
來處理非同步過程,因此我們定義的引數泛型類需要繼承原來的引數型別,並且在傳輸傳遞中,將原生BackgroundWorker
的Argument
,Result
屬性轉成全域性的泛型T,這樣我們在外部呼叫的時候,拿到的返回結果就是我們傳入到BackgroundWorkerEx<T>
泛型類中的T
型別,而不需要使用as
進行轉換; 注:因為原生沒有停止相關事件,所以自定義非同步停止的事件引數使用的是DoWorkEventArgs<T>
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
用法一致,只是在最後開始了定時器和進度條控制元件而已/// <summary> /// 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<ParaArg> 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