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

51 篇文章 4 订阅

在桌面程序编程中,我们经常需要执行耗时较长的代码。为了良好的用户体验,仿照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);

 

});

---------------------
作者:Afresh_Kedou
来源:CSDN
原文:https://blog.csdn.net/klo220/article/details/79117498
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值