前言

程序开发过程中,难免会有的业务逻辑,或者算法之类产生让人能够感知的耗时操作,例如循环中对复杂逻辑处理;获取数据库百万乃至千万级数据;http请求的时候等......

用户在使用UI操作并不知道程序的内部处理,从而误操作导致程序无响应,关闭程序等待影响体验的情况,因此,在等待过程中提供友好的等待提示是有必要的,接下来

我们一起封装一个自定义进度条控件!

主要使用技术(C#相关)

  • BackgroundWoker异步模型
  • ProgressBar控件
  • 泛型
  • 定时器 System.Timers.Timer

自定义控件开发

项目解决方案

  • BackgroundworkerEx : 自定义进度条控件工程
  • Test : 调用BackgroundworkerEx的工程(只是展示如何调用)

处理控件样式

  • 新建一个ProgressbarEx名称的 用户控件

  • 添加Labal控件(lblTips),用于展示进度条显示的信息状态

  • 添加一个PictureBox控件(PicStop),充当关闭按钮,用于获取用户点击事件,触发关闭/终止进度条

  • 添加进度条ProgressBar控件(MainProgressBar)

  • 处理代码如下:

  1. 进度条样式为"不断循环",并且速度为50
  2. 该自定义用户控件不展示在任务栏中
  3. 图片控件被点击事件------>设置当前属性IsStop=true,指示过程终止;
  4. TipMessage属性,用于设置进度条的信息
  5. SetProgressValue(int value) 设置进度条的Value属性,使得在ProgressBarStyle.Marquee样式中动画平滑
  6. 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

    1. 释放资源;
 		/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
try
{
DoWork = null;
RunWorkCompleted = null;
WorkStoped = null; _mWorkerThread = null;
_mWorker.Dispose();
_mWorker = null;
_mTimer = null;
}
catch (Exception){}
}
  1. T用与异步处理的时候,传递T类型
  • 因为我们是通过.Net 的 BackgroundWorker异步模型来做的,所以我们理所当然定义相关的事件:

    1. 异步开始
    2. 异步完成
    3. 加上我们自定义扩展的异步停止
    4. ......报告进度事件在此进度条样式中并不需要

我们先定义这四个事件所用到的参数,因为在BackgroundWorkerEx<T>泛型类中,我们还是使用BackgroundWorker来处理异步过程,因此我们定义的参数泛型类需要继承原来的参数类型,并且在传输传递中,将原生BackgroundWorkerArgument,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;
  • 定义全局的字段

    1. private BackgroundWorker _mWorker = null;异步操作必要;
    2. private T _mWorkArg = default(T);操作传递进来的参数类并且返回到外部
    3. private Timer _mTimer; 定时器检测自定义进度条控件属性IsStop是否为true,并且动态修改进度条消息
    4. private Thread _mWorkerThread = null;异步操作在该线程中,终止时调用About()抛出ThreadAbortException异常,用于标记当前是停止而不是完成状态
    5. private int _miWorkerStartDateSecond = 0; 异步消耗时间(非必要)
    6. private int _miShowProgressCount = 0; 动态显示"."的个数(非必要)
    7. 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;
  • 定义全局属性
  1. IsBusy 返回_mWorker的工作忙碌是否
  2. 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一致,

    1. 如果调用处没有注册DoWork事件,则直接返回

    2. 将接受到的参数创建成泛型参数类

    3. 开线程,将异步操作放在该线程中操作,注意设置线程的IsBackground=true,防止主进程意外退出,线程还在处理

    4. 循环直到线程结束

    5. 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但是不处理)/线程完成时会进入异步完成事件

    1. 完成后,将自定义进度条控件实例关闭,释放
  1. 将全局的BackgroundWorker实例_mWorker相关事件取消注册,并且检查线程情况
  2. 感觉线程情况,如果线程状态为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;
}
}
  • 线程开始

    1. 检查消息提醒内容 , {0}{1}同于显示异步耗时和".."的个数
    2. 在定时器执行方法中,检查_mfrmProgressForm.IsStop是否为true,这个属性标志是否被停止;true则抛出异常
    3. _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上了,欢迎大家拉下来用

C# 根据BackgroundWoker异步模型和ProgressBar控件,自定义进度条控件的更多相关文章

  1. BitBlt()函数实现带数字百分比进度条控件、静态文本(STATIC)控件实现的位图进度条、自定义进度条控件实现七彩虹颜色带数字百分比

    Windows API BitBlt()函数实现带数字百分比进度条控件. 有两个例子:一用定时器实现,二用多线程实现. 带有详细注解. 此例是本人原创,绝对是网上稀缺资源(本源码用Windows AP ...

  2. [转载]ExtJs4 笔记(8) Ext.slider 滚轴控件、 Ext.ProgressBar 进度条控件、 Ext.Editor 编辑控件

    作者:李盼(Lipan)出处:[Lipan] (http://www.cnblogs.com/lipan/)版权声明:本文的版权归作者与博客园共有.转载时须注明本文的详细链接,否则作者将保留追究其法律 ...

  3. ExtJs4 笔记(8) Ext.slider 滚轴控件、 Ext.ProgressBar 进度条控件、 Ext.Editor 编辑控件

    本篇要登场的有三个控件,分别是滚轴控件.进度条控件和编辑控件. 一.滚轴控件 Ext.slider 1.滚轴控件的定义 下面我们定义三个具有代表意义滚轴控件,分别展示滚轴横向.纵向,以及单值.多值选择 ...

  4. 2015.3.11 VS异步控件及进度条结合应用

    1.在Form中添加 指针控件:BackgroundWorker-bgwork:进度条控件progressBar1 以及开始.取消按钮 2.开始按钮启动异步线程 private void button ...

  5. Photoshop和WPF双剑配合,打造炫酷个性的进度条控件

    现在如果想打造一款专业的App,UI的设计和操作的简便性相当重要.UI设计可以借助Photoshop或者AI等设计工具,之前了解到WPF设计工具Expression Blend可以直接导入PSD文件或 ...

  6. Android自己定义控件:进度条的四种实现方式

    前三种实现方式代码出自: http://stormzhang.com/openandroid/2013/11/15/android-custom-loading/ (源代码下载)http://down ...

  7. [K/3Cloud]进度条控件编程接口

    进度条控件编程接口 1.启动进度查询 this.GetControl<ProgressBar>().Start(2)  //每2秒查询一次进度 2.汇报进度 在插件中重载 OnQueryP ...

  8. DevExpress的进度条控件ProgressBarControl的使用-以ZedGraph添加曲线进度为例

    场景 Winform控件-DevExpress18下载安装注册以及在VS中使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1 ...

  9. 用 CALayer 定制下载进度条控件

    // // RPProgressView.h // CALayer定制下载进度条控件 // // Created by RinpeChen on 16/1/2. // Copyright © 2016 ...

随机推荐

  1. Spring Data Jpa 入门学习

    本文主要讲解 springData Jpa 入门相关知识, 了解JPA规范与Jpa的实现,搭建springboot+dpringdata jpa环境实现基础增删改操作,适合新手学习,老鸟绕道~ 1. ...

  2. centos7 源码安装goaccess

    1. 使用yum安装在不同服务器上可能失败, 推荐使用源码安装goaccess # 安装依赖 yum install -y ncurses-devel GeoIP-devel.x86_64 tokyo ...

  3. java制作甘特图

    今日来做一下甘特图.网上搜到了这个源码,但是导的jar包,并没有给我.swiftganttdemo但是名为swiftgantt制作:所以灵机一动在网上搜到了swiftangantt组件:在组件中找到了 ...

  4. STM32 CubeIDE快速创建工程(图文详解)

    使用STM32CubeIDE快速创建STM32的HAL库工程. 文章目录 1 STM32CubeIDE Home 2 生成工程 3 程序下载 1 STM32CubeIDE Home 进入到官网的下载界 ...

  5. c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind

    一.符号修饰与函数签名 1.符号修饰 编译器将c++源代码编译成目标文件时,用函数签名的信息对函数名进行改编,形成修饰名.GCC的C++符号修饰方法如下: 1)所有符号都以_z开头 2)名字空间的名字 ...

  6. IO 模型知多少 | 代码篇

    引言 之前的一篇介绍IO 模型的文章IO 模型知多少 | 理论篇 比较偏理论,很多同学反应不是很好理解.这一篇咱们换一个角度,从代码角度来分析一下. socket 编程基础 开始之前,我们先来梳理一下 ...

  7. [hdu5323]复杂度计算,dfs

    题意:求最小的线段树的右端点(根节点表示区间[0,n]),使得给定的区间[L,R]是线段树的某个节点. 数据范围:L,R<=1e9,L/(R-L+1)<=2015 思路:首先从答案出发来判 ...

  8. 手机app传统邀请码安装与免邀请码安装区别,如何选择呢?

    App 邀请机制是每个产品几乎必做的功能点,它一般以两种形式存在:一是作为常置功能用于推荐,二是作为裂变活动用于邀请. 无论以哪种形式出现,都可以归为社交分享的一种表现方式.相较于营销推广,邀请好友机 ...

  9. C# 数据操作系列 - 5. EF Core 入门

    0.前言 上一章简单介绍了一下ORM框架,并手写了一个类似ORM的工具类.这一章将介绍一个在C#世界里大名鼎鼎的ORM框架--Entity Framework的Core版. Entity Framew ...

  10. PK,FK, UK,DF, CK

    PK 主键 constraint primary key FK 主外键关系 constraint foreign references UK 唯一约束 constraint unique key DF ...