C ා customize the progress bar control according to the BackgroundWorker asynchronous model and ProgressBar control

C ා customize the progress bar control according to the BackgroundWorker asynchronous model and ProgressBar control

preface
In the process of program development, it is inevitable that some business logic or algorithms will generate time-consuming operations that can be perceived by people, such as complex logic processing in the loop; obtaining millions or even tens of millions of data in the database; http requests, etc
The user does not know the internal processing of the program when using the UI operation, resulting in no response of the program due to misoperation, and closing the program waiting affects the experience. Therefore, it is necessary to provide friendly waiting prompt during the waiting process. Next
Let's encapsulate a custom progress bar control together!


Main application technology (C × related)
Backgroundwalker asynchronous model
ProgressBar control
generic paradigm
Timer System.Timers.Timer
Custom control development
Project solutions





BackgroundworkerEx: custom progress bar control project
Test: call the BackgroundworkerEx project (just show how to call it)
Handling control styles

Create a new user control with the name ProgressbarEx
Add Labal control (lblTips) to show the information status of progress bar display
Add a PictureBox control (PicStop) to act as the close button, which is used to get the user's click event and trigger the close / terminate progress bar
Add progress bar control (MainProgressBar)
The processing code is as follows:
Progress bar style is "continuous cycle" and speed is 50
The custom user control is not displayed in the taskbar
Event when the picture control is clicked -------- set the current property IsStop=true to indicate the process termination;
TipMessage property, used to set the information of progress bar
SetProgressValue(int value) sets the Value property of the progress bar so that ProgressBarStyle.Marquee Animation smoothing in styles
MouseDown/MouseUp/MouseMove these three events are used to drag the borderless user control (the code will not be pasted)
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; }


///
/// TipMessage
///
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;

}

Up to now, the custom progress bar control style is basically completed

Functional logic processing
Required before operation
Define BackgroundWorkerEx generic class and inherit from IDisposable
Release resources;


     /// <summary>
    /// Dispose
    /// </summary>
    public void Dispose()
    {
        try
        {
            DoWork = null;
            RunWorkCompleted = null;
            WorkStoped = null;

            _mWorkerThread = null;
            _mWorker.Dispose();
            _mWorker = null;
            _mTimer = null;
        }
        catch (Exception){}
    }

When using T and asynchronous processing, the T type is passed
Because we do this through the. Net BackgroundWorker asynchronous model, we naturally define related events:
Asynchronous start
Asynchronous completion
Plus the asynchronous stop of our custom extension
... report progress events are not required in this progress bar style
First, we define the parameters used for these four events. Because in the BackgroundWorkerEx generic class, we still use BackgroundWorker to handle asynchronous processes. Therefore, the parameter generic class we defined needs to inherit the original parameter type. In the transmission and transmission, we transfer the argument and result properties of the native BackgroundWorker to the global generic T, so when we call externally, The returned result is the T type that we passed in to the BackgroundWorkerEx generic class, instead of using as for conversion. Note: because the native does not stop related events, the event parameter of custom asynchronous stop uses the 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;
    }
}

Then we need to define the event. The parameter uses the generic class defined above

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;

Define global fields
private BackgroundWorker _mWorker = null; asynchronous operation is necessary;
private T _mWorkArg = default(T); the parameter class passed in by the operation and returned to the outside
private Timer _mTimer; timer detects whether the custom progress bar control property IsStop is true, and dynamically modifies the progress bar message
private Thread _mWorkerThread = null; asynchronous operation in this thread, call About() on termination to throw ThreadAbortException exception, which is used to mark the current stop rather than completion status
private int _miWorkerStartDateSecond = 0; asynchronous elapsed time (not required)
private int _miShowProgressCount = 0; number of '.' dynamically displayed (not necessary)
private ProgressbarEx _mfrmProgressForm = null; custom progress bar control instance






    /// <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;

Define global properties
IsBusy return_ Is mWorker busy
ProgressTip custom progress bar control display content

/// <summary>
    /// Express Busy
    /// </summary>
    public bool IsBusy
    {
        get
        {
            if (_mWorker != null)
            {
                return _mWorker.IsBusy;
            }
            return false;
        }
    }

    /// <summary>
    ///Progress bar prompt default: loading data, please wait [{0}] {1}
    /// </summary>
    public string ProgressTip { get; set; } = "Elapsed Time[{0}]{1}";

By now, we have finished processing the necessary fields, properties and styles!!! Next, we will implement the method

Method implementation
Asynchronous work event, consistent with BackgroundWorker,
If no DoWork event is registered at the calling place, return directly
Create the received parameter as a generic parameter class
Open a thread and put asynchronous operation in the thread to operate. Pay attention to setting IsBackground=true of the thread to prevent the main process from accidentally exiting and the thread is still processing
Loop until end of thread
e.Result = Argument.Result ; assign the result to the result, and the result can be obtained in the stop or completion event





    /// <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;
    }

Asynchronous completion / stop
An asynchronous completion event occurs when the thread stops throwing an exception (catch but does not handle) / the thread completes

When finished, close and release the custom progress bar control instance
Global BackgroundWorker instance_ mWorker related events unregister and check the thread status
Feel the thread status, if the thread status is ThreadState.Aborted It means that the thread is stopped, call the stop event, otherwise call the completion event

  /// <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;
      }
  }

Thread start
Check the content of the message reminder, {0} {1} is the same as the number of asynchronous time consuming and ".."
In the timer execution method, check_ mfrmProgressForm.IsStop If it is true, the attribute flag will be stopped; if it is true, an exception will be thrown
A kind of If mfrmProgressForm is not Null, the current content reminder will be modified continuously. It is friendly and can be processed on demand


      /// <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();
        }
    }

Last step: asynchronous start is consistent with BackgroundWorker, only timer and progress bar control are started at the end
///

    /// 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();
    }

Here, the whole progress bar control has been completed!

call
Define a parameter class

/// <summary>
/// Para Class
/// </summary>
public class ParaArg
{
    public DataTable Data { get; set; }
    public string Msg { get; set; }
    public Exception Ex { get; set; }
}

Define the global help class BackgroundWorkerEx workHelper = null;
call

            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;
            };

            //parameter
            ParaArg arg = new ParaArg()
            {
                Msg = "Msg"
            };

            workHelper.AsyncStart(arg);

last
In fact, whether it's a encapsulated process or a call, it can be said that it's totally BackgroundWorker's way, so I haven't explained a lot about BackgroundWorker in detail; as long as I look at this asynchronous model, I can understand it very well! You can also implement the following if you have time, and I prefer communication technology if you have any questions~~~

Another point is that this solution has been put on Github. Welcome to pull it down and use it

GitHub address welcome star/fork
Original address https://www.cnblogs.com/Ligy97/p/12993155.html

Tags: github Database Attribute

Posted on Sun, 31 May 2020 20:13:57 -0700 by centenial