• C# 根据BackgroundWorker异步模型和ProgressBar控件,自定义进度条控件


    前言

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

  • 相关阅读:
    点击Notification之后收起通知栏
    Visual Studio常用的快捷键
    数据库语法二之外键
    数据引擎 创建表完整语法,字段类型,约束条件
    数据库 tcp协程实现并发 回调函数
    GIL以及协程
    进程,互斥锁,生产者消费者,线程
    udp协议,进程(同步,异步)
    单例模式,网络编程之tcp协议以及粘包问题
    网络编程
  • 原文地址:https://www.cnblogs.com/Ligy97/p/12993155.html
Copyright © 2020-2023  润新知