winform耗时代码处理,仿win10加载动画Loading

在桌面程序编程中,我们经常需要执行耗时较长的代码。为了良好的用户体验,仿照win10加载动画,实现了Loading时异步处理耗时代码。借鉴了网上两个Demo,整理后实现了较好效果,先来看效果图。



先实现了异步开启执行工作任务,然后展示加载动画,待任务执行完毕,关闭动画。

1.画点

using System;
using System.ComponentModel;
using System.Drawing;

namespace Loading
{
    /// <summary>  
    /// 表示一个"点"  
    /// </summary>  
    internal sealed class LoadingDot
    {
        #region 字段/属性  

        [Description("圆心")] private readonly PointF _circleCenter;
        [Description("半径")] private readonly float _circleRadius;

        /// <summary>  
        /// 当前帧绘图坐标,在每次DoAction()时重新计算  
        /// </summary>  
        public PointF Location;

        [Description("点相对于圆心的角度,用于计算点的绘图坐标")] private int _angle;
        [Description("透明度")] private int _opacity;
        [Description("动画进度")] private int _progress;
        [Description("速度")] private int _speed;

        [Description("透明度")]
        public int Opacity => _opacity < MinOpacity ? MinOpacity : (_opacity > MaxOpacity ? MaxOpacity : _opacity);

        #endregion

        #region 常量  

        [Description("最小速度")] private const int MinSpeed = 2;
        [Description("最大速度")] private const int MaxSpeed = 11;

        [Description("出现区的相对角度")] private const int AppearAngle = 90;
        [Description("减速区的相对角度")] private const int SlowAngle = 225;
        [Description("加速区的相对角度")] private const int QuickAngle = 315;

        [Description("最小角度")] private const int MinAngle = 0;
        [Description("最大角度")] private const int MaxAngle = 360;

        [Description("淡出速度")] private const int AlphaSub = 25;

        [Description("最小透明度")] private const int MinOpacity = 0;
        [Description("最大透明度")] private const int MaxOpacity = 255;

        #endregion 常量  

        #region 构造器  

        public LoadingDot(PointF circleCenter, float circleRadius)
        {
            Reset();
            _circleCenter = circleCenter;
            _circleRadius = circleRadius;
        }

        #endregion 构造器  

        #region 方法  

        /// <summary>  
        /// 重新计算当前帧绘图坐标
        /// </summary>  
        private void ReCalcLocation()
        {
            Location = GetDotLocationByAngle(_circleCenter, _circleRadius, _angle);
        }

        /// <summary>  
        /// 点动作
        /// </summary>  
        public void LoadingDotAction()
        {
            switch (_progress)
            {
                case 0:
                {
                    _opacity = MaxOpacity;
                    AddSpeed();
                    if (_angle + _speed >= SlowAngle && _angle + _speed < QuickAngle)
                    {
                        _progress = 1;
                        _angle = SlowAngle - _speed;
                    }
                }
                    break;
                case 1:
                {
                    SubSpeed();
                    if (_angle + _speed >= QuickAngle || _angle + _speed < SlowAngle)
                    {
                        _progress = 2;
                        _angle = QuickAngle - _speed;
                    }
                }
                    break;
                case 2:
                {
                    AddSpeed();
                    if (_angle + _speed >= SlowAngle && _angle + _speed < QuickAngle)
                    {
                        _progress = 3;
                        _angle = SlowAngle - _speed;
                    }
                }
                    break;
                case 3:
                {
                    SubSpeed();
                    if (_angle + _speed >= QuickAngle && _angle + _speed < MaxAngle)
                    {
                        _progress = 4;
                        _angle = QuickAngle - _speed;
                    }
                }
                    break;
                case 4:
                {
                    SubSpeed();
                    if (_angle + _speed >= MinAngle && _angle + _speed < AppearAngle)
                    {
                        _progress = 5;
                        _angle = MinAngle;
                    }
                }
                    break;
                case 5:
                {
                    AddSpeed();
                    FadeOut();
                }
                    break;
            }

            //移动  
            _angle = _angle >= (MaxAngle - _speed) ? MinAngle : _angle + _speed;
            //重新计算坐标  
            ReCalcLocation();
        }

        /// <summary>
        /// 淡出
        /// </summary>
        private void FadeOut()
        {
            if ((_opacity -= AlphaSub) <= 0)
                _angle = AppearAngle;
        }


        /// <summary>
        /// 重置状态
        /// </summary>
        public void Reset()
        {
            _angle = AppearAngle;
            _speed = MinSpeed;
            _progress = 0;
            _opacity = 1;
        }

        /// <summary>
        /// 加速
        /// </summary>
        private void AddSpeed()
        {
            if (++_speed >= MaxSpeed) _speed = MaxSpeed;
        }

        /// <summary>
        /// 减速
        /// </summary>
        private void SubSpeed()
        {
            if (--_speed <= MinSpeed) _speed = MinSpeed;
        }

        #endregion 方法  

        /// <summary>  
        /// 根据半径、角度求圆上坐标
        /// </summary>  
        /// <param name="center">圆心</param>  
        /// <param name="radius">半径</param>  
        /// <param name="angle">角度</param>  
        /// <returns>坐标</returns>  
        public static PointF GetDotLocationByAngle(PointF center, float radius, int angle)
        {
            var x = (float) (center.X + radius*Math.Cos(angle*Math.PI/180));
            var y = (float) (center.Y + radius*Math.Sin(angle*Math.PI/180));

            return new PointF(x, y);
        }
    }
}
2.创建Loading窗体,包括遮罩层,加载动画,文字提示。
namespace Loading
{
    partial class FrmLoading
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.LblMessage = new System.Windows.Forms.Label();
            this.PnlImage = new System.Windows.Forms.Panel();
            this.SuspendLayout();
            // 
            // LblMessage
            // 
            this.LblMessage.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
            this.LblMessage.BackColor = System.Drawing.Color.Transparent;
            this.LblMessage.ForeColor = System.Drawing.Color.White;
            this.LblMessage.Location = new System.Drawing.Point(36, 224);
            this.LblMessage.Name = "LblMessage";
            this.LblMessage.Size = new System.Drawing.Size(328, 64);
            this.LblMessage.TabIndex = 0;
            this.LblMessage.Text = "正在处理中,请稍候……";
            this.LblMessage.TextAlign = System.Drawing.ContentAlignment.TopCenter;
            // 
            // PnlImage
            // 
            this.PnlImage.Anchor = System.Windows.Forms.AnchorStyles.None;
            this.PnlImage.BackColor = System.Drawing.Color.Transparent;
            this.PnlImage.Location = new System.Drawing.Point(100, 12);
            this.PnlImage.Name = "PnlImage";
            this.PnlImage.Size = new System.Drawing.Size(200, 200);
            this.PnlImage.TabIndex = 1;
            this.PnlImage.Paint += new System.Windows.Forms.PaintEventHandler(this.PnlImage_Paint);
            this.PnlImage.Resize += new System.EventHandler(this.PnlImage_Resize);
            // 
            // FrmLoading
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 27F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.Color.Black;
            this.ClientSize = new System.Drawing.Size(400, 300);
            this.Controls.Add(this.LblMessage);
            this.Controls.Add(this.PnlImage);
            this.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
            this.Name = "FrmLoading";
            this.Opacity = 0.5D;
            this.ShowInTaskbar = false;
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
            this.Text = "FrmLoading";
            this.Load += new System.EventHandler(this.FrmLoading_Load);
            this.Shown += new System.EventHandler(this.FrmLoading_Shown);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Label LblMessage;
        private System.Windows.Forms.Panel PnlImage;
    }
}
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using ThreadingTimer = System.Threading.Timer;
using UITimer = System.Windows.Forms.Timer;

namespace Loading
{
    public partial class FrmLoading : Form
    {
        /// <summary>
        /// 构造器
        /// </summary>
        public FrmLoading()
        {
            InitializeComponent();
            SetStyle(
              ControlStyles.AllPaintingInWmPaint |
              ControlStyles.UserPaint |
              ControlStyles.OptimizedDoubleBuffer,
              true);
            //初始化绘图timer
            _tmrGraphics = new UITimer { Interval = 1 };
            //Invalidate()强制重绘,绘图操作在OnPaint中实现
            _tmrGraphics.Tick += (sender, e) => PnlImage.Invalidate(false);
            _dotSize = PnlImage.Width / 10f;
            //初始化"点"
            _dots = new LoadingDot[5];
            Color = Color.Orange;
        }

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="message"></param>
        public FrmLoading(string message)
        {
            InitializeComponent();
            //双缓冲,禁擦背景
            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.UserPaint |
                ControlStyles.OptimizedDoubleBuffer,
                true);
            //初始化绘图timer
            _tmrGraphics = new UITimer {Interval = 1};
            //Invalidate()强制重绘,绘图操作在OnPaint中实现
            _tmrGraphics.Tick += (sender, e) => PnlImage.Invalidate(false);
            _dotSize = PnlImage.Width/10f;
            //初始化"点"
            _dots = new LoadingDot[5];
            Color = Color.Orange;
            Message = message;
        }

        private void FrmLoading_Load(object sender, EventArgs e)
        {
            LblMessage.ForeColor = Color;
            if (Owner != null)
            {
                StartPosition = FormStartPosition.Manual;
                Location = new Point(Owner.Left, Owner.Top);
                Width = Owner.Width;
                Height = Owner.Height;
            }
            else
            {
                var screenRect = Screen.PrimaryScreen.WorkingArea;
                Location = new Point((screenRect.Width - Width) / 2, (screenRect.Height - Height) / 2);
            }
            Start();
        }

        private void FrmLoading_Shown(object sender, EventArgs e)
        {
            if (_workAction != null)
            {
                _workThread = new Thread(ExecWorkAction);
                _workThread.IsBackground = true;
                _workThread.Start();
            }
        }

        #region 属性  

        [Description("消息")]
        public string Message
        {
            get { return LblMessage.Text; }
            set { LblMessage.Text = value; }
        }

        [Browsable(false), Description("圆心")]
        public PointF CircleCenter => new PointF(PnlImage.Width /2f, PnlImage.Height /2f);

        [Browsable(false), Description("半径")]
        public float CircleRadius => PnlImage.Width /2f - _dotSize;

        [Browsable(true), Category("Appearance"), Description("设置\"点\"的前景色")]
        public Color Color { get; set; }

        #endregion 属性  

        #region 字段  

        [Description("工作是否完成")]
        public bool IsWorkCompleted;

        [Description("工作动作")]
        private ParameterizedThreadStart _workAction;

        [Description("工作动作参数")]
        private object _workActionArg;

        [Description("工作线程")]
        private Thread _workThread;

        [Description("工作异常")]
        public Exception WorkException { get; private set; }

        [Description("点数组")] private readonly LoadingDot[] _dots;

        [Description("UITimer")] private readonly UITimer _tmrGraphics;

        [Description("ThreadingTimer")] private ThreadingTimer _tmrAction;

        [Description("点大小")] private float _dotSize;

        [Description("是否活动")] private bool _isActived;

        [Description("是否绘制:用于状态重置时挂起与恢复绘图")] private bool _isDrawing = true;

        [Description("Timer计数:用于延迟启动每个点 ")] private int _timerCount;

        #endregion 字段  

        #region 常量  

        [Description("动作间隔(Timer)")] private const int ActionInterval = 30;

        [Description("计数基数:用于计算每个点启动延迟:index * timerCountRadix")] private const int TimerCountRadix = 45;

        #endregion 常量  

        #region 方法  

        /// <summary>
        /// 设置工作动作
        /// </summary>
        /// <param name="workAction"></param>
        /// <param name="arg"></param>
        public void SetWorkAction(ParameterizedThreadStart workAction, object arg)
        {
            _workAction = workAction;
            _workActionArg = arg;
        }

        /// <summary>
        /// 执行工作动作
        /// </summary>
        private void ExecWorkAction()
        {
            try
            {
                var workTask = new Task(arg =>
                {
                    _workAction(arg);
                }, _workActionArg);
                workTask.Start();
                Task.WaitAll(workTask);
            }
            catch (Exception exception)
            {
                WorkException = exception;
            }
            finally
            {
                IsWorkCompleted = true;
            }
        }

        /// <summary>
        /// 检查是否重置
        /// </summary>
        /// <returns></returns>
        private bool CheckToReset()
        {
            return _dots.Count(d => d.Opacity > 0) == 0;
        }

        /// <summary>
        /// 初始化点元素
        /// </summary>
        private void CreateLoadingDots()
        {
            for (var i = 0; i < _dots.Length; ++i)
                _dots[i] = new LoadingDot(CircleCenter, CircleRadius);
        }

        /// <summary>  
        /// 开始  
        /// </summary>  
        public void Start()
        {
            CreateLoadingDots();
            _timerCount = 0;
            foreach (var dot in _dots)
            {
                dot.Reset();
            }
            _tmrGraphics.Start();
            //初始化动作timer  
            _tmrAction = new ThreadingTimer(
                state =>
                {
                    //动画动作  
                    for (var i = 0; i < _dots.Length; i++)
                    {
                        if (_timerCount++ > i*TimerCountRadix)
                        {
                            _dots[i].LoadingDotAction();
                        }
                    }
                    //是否重置  
                    if (CheckToReset())
                    {
                        //重置前暂停绘图  
                        _isDrawing = false;
                        _timerCount = 0;
                        foreach (var dot in _dots)
                        {
                            dot.Reset();
                        }
                        //恢复绘图  
                        _isDrawing = true;
                    }
                    _tmrAction.Change(ActionInterval, Timeout.Infinite);
                },
                null, ActionInterval, Timeout.Infinite);
            _isActived = true;
        }

        /// <summary>  
        /// 停止  
        /// </summary>  
        public void Stop()
        {
            _tmrGraphics.Stop();
            _tmrAction.Dispose();
            _isActived = false;
        }

        #endregion 方法  

        #region 重写  

        protected override void OnPaint(PaintEventArgs e)
        {
            if (IsWorkCompleted)
            {
                Stop();
                Close();
            }
        }

        private void PnlImage_Paint(object sender, PaintEventArgs e)
        {
            if (_isActived && _isDrawing)
            {
                //抗锯齿  
                e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
                using (var bitmap = new Bitmap(200, 200))
                {
                    //缓冲绘制  
                    using (var bufferGraphics = Graphics.FromImage(bitmap))
                    {
                        //抗锯齿  
                        bufferGraphics.SmoothingMode = SmoothingMode.HighQuality;
                        foreach (var dot in _dots)
                        {
                            var rectangleF = new RectangleF(
                                new PointF(dot.Location.X - _dotSize / 2, dot.Location.Y - _dotSize / 2),
                                new SizeF(_dotSize, _dotSize));
                            bufferGraphics.FillEllipse(new SolidBrush(Color.FromArgb(dot.Opacity, Color)),
                                rectangleF);
                        }
                    }
                    //贴图  
                    e.Graphics.DrawImage(bitmap, new PointF(0, 0));
                } //bmp disposed  
            }
            base.OnPaint(e);
        }

        private void PnlImage_Resize(object sender, EventArgs e)
        {
            PnlImage.Height = PnlImage.Width;
            _dotSize = PnlImage.Width / 12f;
            OnResize(e);
        }

        #endregion 重写  
    }
}
3.执行工作,展示Loading
using System.Dynamic;
using System.Threading;
using System.Windows.Forms;

namespace Loading
{
    public class LoadingHelper
    {
        /// <summary>
        /// 开始加载
        /// </summary>
        /// <param name="message">消息</param>
        /// <param name="ownerForm">父窗体</param>
        /// <param name="work">待执行工作</param>
        /// <param name="workArg">工作参数</param>
        public static void ShowLoading(string message, Form ownerForm, ParameterizedThreadStart work, object workArg = null)
        {
            var loadingForm = new FrmLoading(message);
            dynamic expandoObject = new ExpandoObject();
            expandoObject.Form = loadingForm;
            expandoObject.WorkArg = workArg;
            loadingForm.SetWorkAction(work, expandoObject);
            loadingForm.ShowDialog(ownerForm);
            if (loadingForm.WorkException != null)
            {
                throw loadingForm.WorkException;
            }
        }
    }
}
4.调用
            LoadingHelper.ShowLoading("有朋自远方来,不亦乐乎。", this, (obj) =>
            {
                //这里写处理耗时的代码,代码处理完成则自动关闭该窗口
                Thread.Sleep(10000);
            });
github地址: https://github.com/afresh/Loading

猜你喜欢

转载自blog.csdn.net/danding_ge/article/details/79117498
今日推荐