• Winform耗时代码处理---类似正在加载中,请稍后..效果


    (1)背景

      在桌面程序开发中,经常需要执行耗时较长的业务代码。为了用户体验友好,需要一个加载动画,实现异步处理耗时代码时显示加载动画。

    (2)代码实现

      2.1 FrmLoading 前端代码

     1 namespace Loading
     2 {
     3     partial class FrmLoading
     4     {
     5         /// <summary>
     6         /// Required designer variable.
     7         /// </summary>
     8         private System.ComponentModel.IContainer components = null;
     9 
    10         /// <summary>
    11         /// Clean up any resources being used.
    12         /// </summary>
    13         /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    14         protected override void Dispose(bool disposing)
    15         {
    16             if (disposing && (components != null))
    17             {
    18                 components.Dispose();
    19             }
    20             base.Dispose(disposing);
    21         }
    22 
    23         #region Windows Form Designer generated code
    24 
    25         /// <summary>
    26         /// Required method for Designer support - do not modify
    27         /// the contents of this method with the code editor.
    28         /// </summary>
    29         private void InitializeComponent()
    30         {
    31             this.LblMessage = new System.Windows.Forms.Label();
    32             this.PnlImage = new System.Windows.Forms.Panel();
    33             this.SuspendLayout();
    34             // 
    35             // LblMessage
    36             // 
    37             this.LblMessage.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
    38             this.LblMessage.BackColor = System.Drawing.Color.Transparent;
    39             this.LblMessage.ForeColor = System.Drawing.Color.White;
    40             this.LblMessage.Location = new System.Drawing.Point(36, 224);
    41             this.LblMessage.Name = "LblMessage";
    42             this.LblMessage.Size = new System.Drawing.Size(328, 64);
    43             this.LblMessage.TabIndex = 0;
    44             this.LblMessage.Text = "正在处理中,请稍候……";
    45             this.LblMessage.TextAlign = System.Drawing.ContentAlignment.TopCenter;
    46             // 
    47             // PnlImage
    48             // 
    49             this.PnlImage.Anchor = System.Windows.Forms.AnchorStyles.None;
    50             this.PnlImage.BackColor = System.Drawing.Color.Transparent;
    51             this.PnlImage.Location = new System.Drawing.Point(100, 12);
    52             this.PnlImage.Name = "PnlImage";
    53             this.PnlImage.Size = new System.Drawing.Size(200, 200);
    54             this.PnlImage.TabIndex = 1;
    55             this.PnlImage.Paint += new System.Windows.Forms.PaintEventHandler(this.PnlImage_Paint);
    56             this.PnlImage.Resize += new System.EventHandler(this.PnlImage_Resize);
    57             // 
    58             // FrmLoading
    59             // 
    60             this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 27F);
    61             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    62             this.BackColor = System.Drawing.Color.Black;
    63             this.ClientSize = new System.Drawing.Size(400, 300);
    64             this.Controls.Add(this.LblMessage);
    65             this.Controls.Add(this.PnlImage);
    66             this.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
    67             this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
    68             this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
    69             this.Name = "FrmLoading";
    70             this.Opacity = 0.5D;
    71             this.ShowInTaskbar = false;
    72             this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
    73             this.Text = "FrmLoading";
    74             this.Load += new System.EventHandler(this.FrmLoading_Load);
    75             this.Shown += new System.EventHandler(this.FrmLoading_Shown);
    76             this.ResumeLayout(false);
    77 
    78         }
    79 
    80         #endregion
    81 
    82         private System.Windows.Forms.Label LblMessage;
    83         private System.Windows.Forms.Panel PnlImage;
    84     }
    85 }
    View Code

      2.2 FrmLoading后端代码

      1 using System;
      2 using System.ComponentModel;
      3 using System.Drawing;
      4 using System.Drawing.Drawing2D;
      5 using System.Linq;
      6 using System.Threading;
      7 using System.Threading.Tasks;
      8 using System.Windows.Forms;
      9 using ThreadingTimer = System.Threading.Timer;
     10 using UITimer = System.Windows.Forms.Timer;
     11 
     12 namespace Loading
     13 {
     14     public partial class FrmLoading : Form
     15     {
     16         /// <summary>
     17         /// 构造器
     18         /// </summary>
     19         public FrmLoading()
     20         {
     21             InitializeComponent();
     22             SetStyle(
     23               ControlStyles.AllPaintingInWmPaint |
     24               ControlStyles.UserPaint |
     25               ControlStyles.OptimizedDoubleBuffer,
     26               true);
     27             //初始化绘图timer
     28             _tmrGraphics = new UITimer { Interval = 1 };
     29             //Invalidate()强制重绘,绘图操作在OnPaint中实现
     30             _tmrGraphics.Tick += (sender, e) => PnlImage.Invalidate(false);
     31             _dotSize = PnlImage.Width / 10f;
     32             //初始化"点"
     33             _dots = new LoadingDot[5];
     34             Color = Color.Orange;
     35         }
     36 
     37         /// <summary>
     38         /// 构造器
     39         /// </summary>
     40         /// <param name="message"></param>
     41         public FrmLoading(string message)
     42         {
     43             InitializeComponent();
     44             //双缓冲,禁擦背景
     45             SetStyle(
     46                 ControlStyles.AllPaintingInWmPaint |
     47                 ControlStyles.UserPaint |
     48                 ControlStyles.OptimizedDoubleBuffer,
     49                 true);
     50             //初始化绘图timer
     51             _tmrGraphics = new UITimer { Interval = 1 };
     52             //Invalidate()强制重绘,绘图操作在OnPaint中实现
     53             _tmrGraphics.Tick += (sender, e) => PnlImage.Invalidate(false);
     54             _dotSize = PnlImage.Width / 10f;
     55             //初始化"点"
     56             _dots = new LoadingDot[5];
     57             Color = Color.Orange;
     58             Message = message;
     59         }
     60 
     61         private void FrmLoading_Load(object sender, EventArgs e)
     62         {
     63             LblMessage.ForeColor = Color;
     64             if (Owner != null)
     65             {
     66                 StartPosition = FormStartPosition.Manual;
     67                 Location = new Point(Owner.Left, Owner.Top);
     68                 Width = Owner.Width;
     69                 Height = Owner.Height;
     70             }
     71             else
     72             {
     73                 var screenRect = Screen.PrimaryScreen.WorkingArea;
     74                 Location = new Point((screenRect.Width - Width) / 2, (screenRect.Height - Height) / 2);
     75             }
     76             Start();
     77         }
     78 
     79         private void FrmLoading_Shown(object sender, EventArgs e)
     80         {
     81             if (_workAction != null)
     82             {
     83                 _workThread = new Thread(ExecWorkAction);
     84                 _workThread.IsBackground = true;
     85                 _workThread.Start();
     86             }
     87         }
     88 
     89         #region 属性  
     90 
     91         [Description("消息")]
     92         public string Message
     93         {
     94             get { return LblMessage.Text; }
     95             set { LblMessage.Text = value; }
     96         }
     97 
     98         [Browsable(false), Description("圆心")]
     99         public PointF CircleCenter => new PointF(PnlImage.Width / 2f, PnlImage.Height / 2f);
    100 
    101         [Browsable(false), Description("半径")]
    102         public float CircleRadius => PnlImage.Width / 2f - _dotSize;
    103 
    104         [Browsable(true), Category("Appearance"), Description("设置"点"的前景色")]
    105         public Color Color { get; set; }
    106 
    107         #endregion 属性  
    108 
    109         #region 字段  
    110 
    111         [Description("工作是否完成")]
    112         public bool IsWorkCompleted;
    113 
    114         [Description("工作动作")]
    115         private ParameterizedThreadStart _workAction;
    116 
    117         [Description("工作动作参数")]
    118         private object _workActionArg;
    119 
    120         [Description("工作线程")]
    121         private Thread _workThread;
    122 
    123         [Description("工作异常")]
    124         public Exception WorkException { get; private set; }
    125 
    126         [Description("点数组")]
    127         private readonly LoadingDot[] _dots;
    128 
    129         [Description("UITimer")]
    130         private readonly UITimer _tmrGraphics;
    131 
    132         [Description("ThreadingTimer")]
    133         private ThreadingTimer _tmrAction;
    134 
    135         [Description("点大小")]
    136         private float _dotSize;
    137 
    138         [Description("是否活动")]
    139         private bool _isActived;
    140 
    141         [Description("是否绘制:用于状态重置时挂起与恢复绘图")]
    142         private bool _isDrawing = true;
    143 
    144         [Description("Timer计数:用于延迟启动每个点 ")]
    145         private int _timerCount;
    146 
    147         #endregion 字段  
    148 
    149         #region 常量  
    150 
    151         [Description("动作间隔(Timer)")]
    152         private const int ActionInterval = 30;
    153 
    154         [Description("计数基数:用于计算每个点启动延迟:index * timerCountRadix")]
    155         private const int TimerCountRadix = 45;
    156 
    157         #endregion 常量  
    158 
    159         #region 方法  
    160 
    161         /// <summary>
    162         /// 设置工作动作
    163         /// </summary>
    164         /// <param name="workAction"></param>
    165         /// <param name="arg"></param>
    166         public void SetWorkAction(ParameterizedThreadStart workAction, object arg)
    167         {
    168             _workAction = workAction;
    169             _workActionArg = arg;
    170         }
    171 
    172         /// <summary>
    173         /// 执行工作动作
    174         /// </summary>
    175         private void ExecWorkAction()
    176         {
    177             try
    178             {
    179                 var workTask = new Task(arg =>
    180                 {
    181                     _workAction(arg);
    182                 }, _workActionArg);
    183                 workTask.Start();
    184                 Task.WaitAll(workTask);
    185             }
    186             catch (Exception exception)
    187             {
    188                 WorkException = exception;
    189             }
    190             finally
    191             {
    192                 IsWorkCompleted = true;
    193             }
    194         }
    195 
    196         /// <summary>
    197         /// 检查是否重置
    198         /// </summary>
    199         /// <returns></returns>
    200         private bool CheckToReset()
    201         {
    202             return _dots.Count(d => d.Opacity > 0) == 0;
    203         }
    204 
    205         /// <summary>
    206         /// 初始化点元素
    207         /// </summary>
    208         private void CreateLoadingDots()
    209         {
    210             for (var i = 0; i < _dots.Length; ++i)
    211                 _dots[i] = new LoadingDot(CircleCenter, CircleRadius);
    212         }
    213 
    214         /// <summary>  
    215         /// 开始  
    216         /// </summary>  
    217         public void Start()
    218         {
    219             CreateLoadingDots();
    220             _timerCount = 0;
    221             foreach (var dot in _dots)
    222             {
    223                 dot.Reset();
    224             }
    225             _tmrGraphics.Start();
    226             //初始化动作timer  
    227             _tmrAction = new ThreadingTimer(
    228                 state =>
    229                 {
    230                     //动画动作  
    231                     for (var i = 0; i < _dots.Length; i++)
    232                     {
    233                         if (_timerCount++ > i * TimerCountRadix)
    234                         {
    235                             _dots[i].LoadingDotAction();
    236                         }
    237                     }
    238                     //是否重置  
    239                     if (CheckToReset())
    240                     {
    241                         //重置前暂停绘图  
    242                         _isDrawing = false;
    243                         _timerCount = 0;
    244                         foreach (var dot in _dots)
    245                         {
    246                             dot.Reset();
    247                         }
    248                         //恢复绘图  
    249                         _isDrawing = true;
    250                     }
    251                     _tmrAction.Change(ActionInterval, Timeout.Infinite);
    252                 },
    253                 null, ActionInterval, Timeout.Infinite);
    254             _isActived = true;
    255         }
    256 
    257         /// <summary>  
    258         /// 停止  
    259         /// </summary>  
    260         public void Stop()
    261         {
    262             _tmrGraphics.Stop();
    263             _tmrAction.Dispose();
    264             _isActived = false;
    265         }
    266 
    267         #endregion 方法  
    268 
    269         #region 重写  
    270 
    271         protected override void OnPaint(PaintEventArgs e)
    272         {
    273             if (IsWorkCompleted)
    274             {
    275                 Stop();
    276                 Close();
    277             }
    278         }
    279 
    280         private void PnlImage_Paint(object sender, PaintEventArgs e)
    281         {
    282             if (_isActived && _isDrawing)
    283             {
    284                 //抗锯齿  
    285                 e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
    286                 using (var bitmap = new Bitmap(200, 200))
    287                 {
    288                     //缓冲绘制  
    289                     using (var bufferGraphics = Graphics.FromImage(bitmap))
    290                     {
    291                         //抗锯齿  
    292                         bufferGraphics.SmoothingMode = SmoothingMode.HighQuality;
    293                         foreach (var dot in _dots)
    294                         {
    295                             var rectangleF = new RectangleF(
    296                                 new PointF(dot.Location.X - _dotSize / 2, dot.Location.Y - _dotSize / 2),
    297                                 new SizeF(_dotSize, _dotSize));
    298                             bufferGraphics.FillEllipse(new SolidBrush(Color.FromArgb(dot.Opacity, Color)),
    299                                 rectangleF);
    300                         }
    301                     }
    302                     //贴图  
    303                     e.Graphics.DrawImage(bitmap, new PointF(0, 0));
    304                 } //bmp disposed  
    305             }
    306             base.OnPaint(e);
    307         }
    308 
    309         private void PnlImage_Resize(object sender, EventArgs e)
    310         {
    311             PnlImage.Height = PnlImage.Width;
    312             _dotSize = PnlImage.Width / 12f;
    313             OnResize(e);
    314         }
    315 
    316         #endregion 重写  
    317     }
    318 }
    View Code

      2.3 LoadingDot代码

      1 using System;
      2 using System.ComponentModel;
      3 using System.Drawing;
      4 
      5 namespace Loading
      6 {
      7     /// <summary>  
      8     /// 表示一个"点"  
      9     /// </summary>  
     10     internal sealed class LoadingDot
     11     {
     12         #region 字段/属性  
     13 
     14         [Description("圆心")]
     15         private readonly PointF _circleCenter;
     16         [Description("半径")]
     17         private readonly float _circleRadius;
     18 
     19         /// <summary>  
     20         /// 当前帧绘图坐标,在每次DoAction()时重新计算  
     21         /// </summary>  
     22         public PointF Location;
     23 
     24         [Description("点相对于圆心的角度,用于计算点的绘图坐标")]
     25         private int _angle;
     26         [Description("透明度")]
     27         private int _opacity;
     28         [Description("动画进度")]
     29         private int _progress;
     30         [Description("速度")]
     31         private int _speed;
     32 
     33         [Description("透明度")]
     34         public int Opacity => _opacity < MinOpacity ? MinOpacity : (_opacity > MaxOpacity ? MaxOpacity : _opacity);
     35 
     36         #endregion
     37 
     38         #region 常量  
     39 
     40         [Description("最小速度")]
     41         private const int MinSpeed = 2;
     42         [Description("最大速度")]
     43         private const int MaxSpeed = 11;
     44 
     45         [Description("出现区的相对角度")]
     46         private const int AppearAngle = 90;
     47         [Description("减速区的相对角度")]
     48         private const int SlowAngle = 225;
     49         [Description("加速区的相对角度")]
     50         private const int QuickAngle = 315;
     51 
     52         [Description("最小角度")]
     53         private const int MinAngle = 0;
     54         [Description("最大角度")]
     55         private const int MaxAngle = 360;
     56 
     57         [Description("淡出速度")]
     58         private const int AlphaSub = 25;
     59 
     60         [Description("最小透明度")]
     61         private const int MinOpacity = 0;
     62         [Description("最大透明度")]
     63         private const int MaxOpacity = 255;
     64 
     65         #endregion 常量  
     66 
     67         #region 构造器  
     68 
     69         public LoadingDot(PointF circleCenter, float circleRadius)
     70         {
     71             Reset();
     72             _circleCenter = circleCenter;
     73             _circleRadius = circleRadius;
     74         }
     75 
     76         #endregion 构造器  
     77 
     78         #region 方法  
     79 
     80         /// <summary>  
     81         /// 重新计算当前帧绘图坐标
     82         /// </summary>  
     83         private void ReCalcLocation()
     84         {
     85             Location = GetDotLocationByAngle(_circleCenter, _circleRadius, _angle);
     86         }
     87 
     88         /// <summary>  
     89         /// 点动作
     90         /// </summary>  
     91         public void LoadingDotAction()
     92         {
     93             switch (_progress)
     94             {
     95                 case 0:
     96                     {
     97                         _opacity = MaxOpacity;
     98                         AddSpeed();
     99                         if (_angle + _speed >= SlowAngle && _angle + _speed < QuickAngle)
    100                         {
    101                             _progress = 1;
    102                             _angle = SlowAngle - _speed;
    103                         }
    104                     }
    105                     break;
    106                 case 1:
    107                     {
    108                         SubSpeed();
    109                         if (_angle + _speed >= QuickAngle || _angle + _speed < SlowAngle)
    110                         {
    111                             _progress = 2;
    112                             _angle = QuickAngle - _speed;
    113                         }
    114                     }
    115                     break;
    116                 case 2:
    117                     {
    118                         AddSpeed();
    119                         if (_angle + _speed >= SlowAngle && _angle + _speed < QuickAngle)
    120                         {
    121                             _progress = 3;
    122                             _angle = SlowAngle - _speed;
    123                         }
    124                     }
    125                     break;
    126                 case 3:
    127                     {
    128                         SubSpeed();
    129                         if (_angle + _speed >= QuickAngle && _angle + _speed < MaxAngle)
    130                         {
    131                             _progress = 4;
    132                             _angle = QuickAngle - _speed;
    133                         }
    134                     }
    135                     break;
    136                 case 4:
    137                     {
    138                         SubSpeed();
    139                         if (_angle + _speed >= MinAngle && _angle + _speed < AppearAngle)
    140                         {
    141                             _progress = 5;
    142                             _angle = MinAngle;
    143                         }
    144                     }
    145                     break;
    146                 case 5:
    147                     {
    148                         AddSpeed();
    149                         FadeOut();
    150                     }
    151                     break;
    152             }
    153 
    154             //移动  
    155             _angle = _angle >= (MaxAngle - _speed) ? MinAngle : _angle + _speed;
    156             //重新计算坐标  
    157             ReCalcLocation();
    158         }
    159 
    160         /// <summary>
    161         /// 淡出
    162         /// </summary>
    163         private void FadeOut()
    164         {
    165             if ((_opacity -= AlphaSub) <= 0)
    166                 _angle = AppearAngle;
    167         }
    168 
    169 
    170         /// <summary>
    171         /// 重置状态
    172         /// </summary>
    173         public void Reset()
    174         {
    175             _angle = AppearAngle;
    176             _speed = MinSpeed;
    177             _progress = 0;
    178             _opacity = 1;
    179         }
    180 
    181         /// <summary>
    182         /// 加速
    183         /// </summary>
    184         private void AddSpeed()
    185         {
    186             if (++_speed >= MaxSpeed) _speed = MaxSpeed;
    187         }
    188 
    189         /// <summary>
    190         /// 减速
    191         /// </summary>
    192         private void SubSpeed()
    193         {
    194             if (--_speed <= MinSpeed) _speed = MinSpeed;
    195         }
    196 
    197         #endregion 方法  
    198 
    199         /// <summary>  
    200         /// 根据半径、角度求圆上坐标
    201         /// </summary>  
    202         /// <param name="center">圆心</param>  
    203         /// <param name="radius">半径</param>  
    204         /// <param name="angle">角度</param>  
    205         /// <returns>坐标</returns>  
    206         public static PointF GetDotLocationByAngle(PointF center, float radius, int angle)
    207         {
    208             var x = (float)(center.X + radius * Math.Cos(angle * Math.PI / 180));
    209             var y = (float)(center.Y + radius * Math.Sin(angle * Math.PI / 180));
    210 
    211             return new PointF(x, y);
    212         }
    213     }
    214 }
    View Code

      2.4 LoadingHelper代码  

     1 using System.Dynamic;
     2 using System.Threading;
     3 using System.Windows.Forms;
     4 
     5 namespace Loading
     6 {
     7     public class LoadingHelper
     8     {
     9         /// <summary>
    10         /// 开始加载
    11         /// </summary>
    12         /// <param name="message">消息</param>
    13         /// <param name="ownerForm">父窗体</param>
    14         /// <param name="work">待执行工作</param>
    15         /// <param name="workArg">工作参数</param>
    16         public static void ShowLoading(string message, Form ownerForm, ParameterizedThreadStart work, object workArg = null)
    17         {
    18             var loadingForm = new FrmLoading(message);
    19             dynamic expandoObject = new ExpandoObject();
    20             expandoObject.Form = loadingForm;
    21             expandoObject.WorkArg = workArg;
    22             loadingForm.SetWorkAction(work, expandoObject);
    23             loadingForm.ShowDialog(ownerForm);
    24             if (loadingForm.WorkException != null)
    25             {
    26                 throw loadingForm.WorkException;
    27             }
    28         }
    29     }
    30 }
    View Code

    (3)调用代码  

    1 LoadingHelper.ShowLoading("正在加载中,请稍后...", this, o =>
    2             {
    3                 //这里写处理耗时的代码,代码处理完成则自动关闭该窗口
    4                 Thread.Sleep(30000);
    5             });

    (4)效果

      

  • 相关阅读:
    二分查找思路以及可能出现情况对应解决办法
    多线程知识点大纲
    服务器consul启动方法
    大白话带你认识 ZooKeeper !重要概念一网打尽!
    「Netty实战 02」手把手教你实现自己的第一个 Netty 应用!新手也能搞懂!
    从 BIO、NIO 聊到 Netty,最后还要实现个 RPC 框架!
    什么是P问题、NP问题和NPC问题
    期刊汇总
    Typora 使用
    TCA 复习
  • 原文地址:https://www.cnblogs.com/turnip/p/12021415.html
Copyright © 2020-2023  润新知