C# Winform 加载窗体/对象时的等待页面设计

在设计应用程序过程中,有时候加载对象需时较长,我们可以显示一个Loading等待页面,对用户来说就比较友好了。

这个还是涉及到多线程,下面是步骤。

一、创建好Loading窗体:

一个Panel用于显示转圈动画(仿Win10的Loading),一个Loading文本标签。动画的代码来自网络。

    public partial class Fm20Loading : Form
    {
        public Fm20Loading()
        {
            InitializeComponent();
            //LblMessage.Text = MultiLang.Surface(null, "OnLoading", "目标对象正在加载中, 请您稍等...");

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

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="message"></param>
        public Fm20Loading(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.CadetBlue;
            Message = message;
        }

        private void Fm20Loading_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 Fm20Loading_Shown(object sender, EventArgs e)
        {
            if (_workAction != null)
            {
                _workThread = new Thread(ExecWorkAction)
                {
                    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 重写  

        private void LblMessage_DoubleClick(object sender, EventArgs e)
        {
            this.Close();
        }
    }

Loading窗体代码

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

绘制圆点动画的LoadingDot代码

二、窗体和动画有了,怎么使用呢?

        private void ShowLoadingForm()
        {
            if (Debugger.IsAttached)
            {
                return;
            }
            Fm20Loading fm20Loading = new Fm20Loading
            {
                Name = "Fm20Loading" + DateTime.Now.Ticks
            };
            Thread.Sleep(100);
            fm20Loading.ShowDialog();
            return ;
        }

        private void CloseLoadingForm()
        {
            if (Debugger.IsAttached) return;
            for (int i = (Application.OpenForms.Count-1); i >=0; i--)
            {
                Form tForm = Application.OpenForms[i];
                string fmName = tForm.GetType().Name;
                if (OString.Left(fmName,11) == "Fm20Loading")
                {
                    tForm.Close();
                }
            }
        }

创建和关闭页面代码

三、调用创建和关闭代码的代码(有点绕了)

                try
                {
                    Form child = ActiveChildForm(dllFormNameWithNameSpace);
                    if (child != null) return child;

                    Action handler = new Action(ShowLoadingForm);
                    handler.BeginInvoke(null, null);  //在另外一个线程打开,否则会阻塞
                    Form form = OpenPluginFormInMainDomain(dllFileSimpleName, dllFormNameWithNameSpace, initParam);

                    if (form != null && form is Form)
                    {
                        child = form as Form;
                        ((Fm11Base)child).RightsList = rightsList.ToLower();
                        ((Fm11Base)child).OnLoadParams = onLoadParams;
                        child.Text = tagTitle;
                        child.MdiParent = (Form)this;
                        child.FormClosed += Child_FormClosed;
                        child.Show();
                        child.WindowState = FormWindowState.Maximized;
                        this.ActivateMdiChild(child);
                        if (child.HasChildren)
                        {
                            child.Controls[0].Focus();
                        }
                        CloseLoadingForm();
                        return child;
                    }
                    else
                    {
                        CloseLoadingForm();
                        return null;
                        throw new Exception("未找到窗体文件或加载了未知的窗体类型!");
                    }
                }
                catch (Exception ex)
                {
                    CloseLoadingForm();
                    MyMsg.Information("窗体实例化出错,请重试.", ex.Message);
                    return null;
                }

  这部分可以改成你喜欢的使用环境。

  如此,一个友好的加载等待页面就完成了。它和耗时后台任务提示窗口两种界面结合,可以解决大部分的友好提示界面需求。

原文地址:https://www.cnblogs.com/imes/p/9779331.html

时间: 2024-10-11 03:40:49

C# Winform 加载窗体/对象时的等待页面设计的相关文章

winform 加载窗体时弹出另一个窗体并显示进度条的源码

winform 加载窗体时弹出另一个窗体并显示进度条的源码 //frmA: 源窗体 //------------------------------------------ //引用 using System.Threading; BackgroundWorker worker; public frmA() { InitializeComponent(); worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandl

WinForm 加载自定义控件闪烁问题

WinForm加载多个自定义控件时,会出现很严重的闪烁问题,很卡,一块一块的加载(像打开网页时,网络很卡的那种感觉)简直没法忍受. 在网上搜索了好久,网上大部分的方法是一下4种,但是都不能有效的解决问题. 1.将DoubleBuffered 设置 true,用双缓存处理Form界面内容加载,可以提高页面显示质量.或者 SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.Opti

有效解决Android加载大图片时内存溢出的问题

首先解析一下基本的知识: 位图模式,bitmap颜色位数是1位 灰度模式,bitmap颜色位数是8位,和256色一样 RGB模式,bitmap颜色位数是24位 在RGB模式下,一个像素对应的是红.绿.蓝三个字节 CMYK模式,bitmap颜色位数是32位  在CMYK模式下,一个像素对应的是青.品.黄.黑四个字节 图像文件的字节数(Byte) = 图像分辨率*颜色深度/8(bit/8) 例如:一幅640*480图像分辨率.RGB色一般为24位真彩色,图像未经压缩的数据容量为:640X480X24

【Java基础】Java类的加载和对象创建流程的详细分析

相信我们在面试Java的时候总会有一些公司要做笔试题目的,而Java类的加载和对象创建流程的知识点也是常见的题目之一.接下来通过实例详细的分析一下. 实例问题 实例代码 Parent类 1 package mytest.javaBase; 2 3 public class Parent { 4 int a = 10; 5 static int b = 11; 6 // 静态代码块 7 static { 8 System.out.println("Parent静态代码块:b=" + b)

图片--Android有效解决加载大图片时内存溢出的问题

Android有效解决加载大图片时内存溢出的问题 博客分类: Android Android游戏虚拟机算法JNI 尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存. 因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView

[转] 从 dll 程序集中动态加载窗体

无涯 原文 从 dll 程序集中动态加载窗体 [原创] 昨天晚上花了一晚上时间写了一个从程序集中动态加载窗体的程序.将任何包含窗体的代码编译成 dll 文件,再把 dll 文件拷贝到本程序的目录下,本程序运行时即可动态检查到 dll 文件中的窗体,将窗体类的类型在程序菜单中显示出来,点击菜单即可运行对应的窗体. 本程序主要用到了 Assembly 类动态加载程序集,再得到程序集中包含类的 Type 类型,动态生成类实例,动态调用类方法.个人觉得这是一种提供高度松耦合,可随意扩展的程序结构框架,希

【转】C# winform 加载网页 模拟键盘输入自动接入访问网络

[转]C# winform 加载网页 模拟键盘输入自动接入访问网络 声明: 本文原创,首发于博客园 http://www.cnblogs.com/EasyInvoice/p/6070563.html 转载请注明出处. 背景: 由于所在办公室网络限制,笔者每天都使用网络都要先连接无线网.如下图,输入授权用户信息登录后才能使用WIFI. 丧心病狂的是该网页Cookie 过期时间为24小时,所以每天重复以下动作:打开浏览器 -> 手动输入 工号密码.密码 -> 点击"登录"按钮.

Android之根布局动态加载子布局时边距设置无效问题

Android大部分的控件都会有padding和layout_margin两个属性,一般来说它们的区别是: padding:控件中的内容离控件边缘的距离. margin:  控件离它的父控件边缘的距离. 今天做了一个由根布局动态加载子布局的实验,结果发现子布局中的这两个属性可以按预期的效果显示,但是给根布局设置的padding并没有对被加载的子布局产生效果. 代码如下: 根布局文件名为activity_main.xml,其xml文件定义的内容为: <LinearLayout xmlns:andr

getContext在谷歌浏览器中,使用时要先加载canvas对象,否则会提示&#39;getContext is null&#39;

<body> <canvas id="myCanvas" width="200" height="100" style="border:1px solid #c3c3c3;"> Your browser does not support the canvas element. </canvas> <script type="text/javascript">