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)效果

  

 

posted @ 2019-12-11 11:18  吴土炮Jared  阅读(1156)  评论(3编辑  收藏  举报