前言
程序开发过程中,难免会有的业务逻辑,或者算法之类产生让人能够感知的耗时操作,例如循环中对复杂逻辑处理;获取数据库百万乃至千万级数据;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