你的位置:首页 > ASP.net教程

[ASP.net教程]Winform应用程序实现通用遮罩层


在WEB上,我们在需要进行大数据或复杂逻辑处理时,由于耗时较长,一般我们会在处理过程中的页面上显示一个半透明的遮罩层,上面放个图标或提示:正在处理中...等字样,这样用户体验就比较好了,然而如果在Winform客户端程序,通常遮罩层的处理就显得不那么简单或不那么好看,而我今天要说明的是,我实现的这个Winform通用遮罩层,却可以实现类似WEB上的遮罩层,既可以透明,而且还可以显示动态图片以及文字,那如何实现的呢,我现在一一讲解。

首先要明确我们要实现的效果:透明+动态图标+文字

透明:这个简单,只需要将窗体的Opacity设为100%以下的值就可以了,这里我采用85%;

动态图标:这个相对复杂一些,因为Winform目前没有现成的支持直接显示动图的控件,但幸好有一个组件ImageAnimator支持逐帧动画,我们只需要将图片绑定到ImageAnimator的Animate方法上(即:ImageAnimator.Animate(m_Image,EventHandler委托);),然后重写窗体的OnPaint即可,具体的代码实现见下面公布的源码。

文字:这个简单,放在一个Label控件即可

还有为了能够让图标与文字在相对的位置(即不论大小)保持居中,我这里采用了一个TableLayoutPanel,分成两行,上行放置Label,并设为居中,下行放置Panel,提供绘制动图的区域。

完整代码实现如下(部份代码参考网络上它人的文章):

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Reflection;using System.Threading;using System.Threading.Tasks;namespace TEMS{  public partial class FrmProcessing : Form  {    private static Image m_Image = null;    private EventHandler evtHandler = null;    private ParameterizedThreadStart workAction = null;    private object workActionArg = null;    private Thread workThread = null;    public string Message    {      get      {        return lbMessage.Text;      }      set      {        lbMessage.Text = value;      }    }    public bool WorkCompleted = false;    public Exception WorkException    { get; private set; }    public void SetWorkAction(ParameterizedThreadStart workAction, object arg)    {      this.workAction = workAction;      this.workActionArg = arg;    }    public FrmProcessing(string msg)    {      InitializeComponent();      this.Message = msg;    }    protected override void OnPaint(PaintEventArgs e)    {      base.OnPaint(e);      if (m_Image != null)      {        //获得当前gif动画下一步要渲染的帧。        UpdateImage();        //将获得的当前gif动画需要渲染的帧显示在界面上的某个位置。        int x = (int)(panImage.ClientRectangle.Width - m_Image.Width) / 2;        int y = 0;        //e.Graphics.DrawImage(m_Image, new Rectangle(x, y, m_Image.Width, m_Image.Height));        panImage.CreateGraphics().DrawImage(m_Image, new Rectangle(x, y, m_Image.Width, m_Image.Height));      }      if (this.WorkCompleted)      {        this.Close();      }    }    private void FrmProcessing_Load(object sender, EventArgs e)    {      if (this.Owner != null)      {        this.StartPosition = FormStartPosition.Manual;        this.Location = new Point(this.Owner.Left, this.Owner.Top);        //MessageBox.Show(string.Format("X={0},Y={1}", this.Owner.Left, this.Owner.Top));        this.Width = this.Owner.Width;        this.Height = this.Owner.Height;      }      else      {        Rectangle screenRect = Screen.PrimaryScreen.WorkingArea;        this.Location = new Point((screenRect.Width - this.Width) / 2, (screenRect.Height - this.Height) / 2);      }      //为委托关联一个处理方法      evtHandler = new EventHandler(OnImageAnimate);      if (m_Image == null)      {        Assembly assy = Assembly.GetExecutingAssembly();        //获取要加载的gif动画文件        m_Image = Image.FromStream(assy.GetManifestResourceStream(assy.GetName().Name + ".Resources.loading2.gif"));      }      //调用开始动画方法      BeginAnimate();    }    //开始动画方法    private void BeginAnimate()    {      if (m_Image != null)      {        //当gif动画每隔一定时间后,都会变换一帧,那么就会触发一事件,该方法就是将当前image每变换一帧时,都会调用当前这个委托所关联的方法。        ImageAnimator.Animate(m_Image, evtHandler);      }    }    //委托所关联的方法    private void OnImageAnimate(Object sender, EventArgs e)    {      //该方法中,只是使得当前这个winform重绘,然后去调用该winform的OnPaint()方法进行重绘)      this.Invalidate();    }    //获得当前gif动画的下一步需要渲染的帧,当下一步任何对当前gif动画的操作都是对该帧进行操作)    private void UpdateImage()    {      ImageAnimator.UpdateFrames(m_Image);    }    //关闭显示动画,该方法可以在winform关闭时,或者某个按钮的触发事件中进行调用,以停止渲染当前gif动画。    private void StopAnimate()    {      m_Image = null;      ImageAnimator.StopAnimate(m_Image, evtHandler);    }    private void FrmProcessing_Shown(object sender, EventArgs e)    {      if (this.workAction != null)      {        workThread = new Thread(ExecWorkAction);        workThread.IsBackground = true;        workThread.Start();      }    }    private void ExecWorkAction()    {      try      {        var workTask = new Task((arg) =>                {                  this.workAction(arg);                },              this.workActionArg);        workTask.Start();        Task.WaitAll(workTask);      }      catch (Exception ex)      {        this.WorkException = ex;      }      finally      {        this.WorkCompleted = true;      }    }  }}

以下是自动生成的代码:

namespace TEMS{  partial class FrmProcessing  {    /// <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.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();      this.lbMessage = new System.Windows.Forms.Label();      this.panImage = new System.Windows.Forms.Panel();      this.tableLayoutPanel1.SuspendLayout();      this.SuspendLayout();      //       // tableLayoutPanel1      //       this.tableLayoutPanel1.ColumnCount = 1;      this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());      this.tableLayoutPanel1.Controls.Add(this.lbMessage, 0, 0);      this.tableLayoutPanel1.Controls.Add(this.panImage, 0, 1);      this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;      this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);      this.tableLayoutPanel1.Name = "tableLayoutPanel1";      this.tableLayoutPanel1.RowCount = 2;      this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));      this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));      this.tableLayoutPanel1.Size = new System.Drawing.Size(582, 318);      this.tableLayoutPanel1.TabIndex = 1;      //       // lbMessage      //       this.lbMessage.BackColor = System.Drawing.Color.Transparent;      this.lbMessage.Dock = System.Windows.Forms.DockStyle.Fill;      this.lbMessage.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));      this.lbMessage.Location = new System.Drawing.Point(3, 0);      this.lbMessage.Name = "lbMessage";      this.lbMessage.Padding = new System.Windows.Forms.Padding(0, 0, 0, 30);      this.lbMessage.Size = new System.Drawing.Size(576, 159);      this.lbMessage.TabIndex = 1;      this.lbMessage.Text = "lbMessage\r\nadsfadsf";      this.lbMessage.TextAlign = System.Drawing.ContentAlignment.BottomCenter;      //       // panImage      //       this.panImage.Dock = System.Windows.Forms.DockStyle.Fill;      this.panImage.Location = new System.Drawing.Point(3, 162);      this.panImage.Name = "panImage";      this.panImage.Size = new System.Drawing.Size(576, 153);      this.panImage.TabIndex = 2;      //       // FrmProcessing      //       this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;      this.BackColor = System.Drawing.SystemColors.Control;      this.ClientSize = new System.Drawing.Size(582, 318);      this.Controls.Add(this.tableLayoutPanel1);      this.Cursor = System.Windows.Forms.Cursors.WaitCursor;      this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;      this.Name = "FrmProcessing";      this.Opacity = 0.85D;      this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;      this.Text = "FrmProcessing";      this.Load += new System.EventHandler(this.FrmProcessing_Load);      this.Shown += new System.EventHandler(this.FrmProcessing_Shown);      this.tableLayoutPanel1.ResumeLayout(false);      this.ResumeLayout(false);    }    #endregion    private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;    private System.Windows.Forms.Label lbMessage;    private System.Windows.Forms.Panel panImage;  }}

View Code

代码中SetWorkAction方法是用来设置异步需要处理的委托方法,在窗体显示出来后(FrmProcessing_Shown),创建新线程,用以处理耗时的逻辑代码段,其中有一个WorkCompleted属性,这个主要是表明处理耗时的逻辑代码已完成(不论是否报错),在窗体重绘时(OnPaint),会持续判断该值是否为true,若为true则关闭当前窗口。

另之所以没重写Panel的OnPaint方法,原因是虽然可以显示动图,但由于局部重绘,造成动图出现闪屏,所以仍需要采用窗体重绘

为了便于通用,我还定义了一个通用方法,专门用来显示遮罩层窗体,方法定义如下:

  public static class Common  {    public static void ShowProcessing(string msg, Form owner, ParameterizedThreadStart work, object workArg = null)    {      FrmProcessing processingForm = new FrmProcessing(msg);      dynamic expObj = new ExpandoObject();      expObj.Form = processingForm;      expObj.WorkArg = workArg;      processingForm.SetWorkAction(work, expObj);      processingForm.ShowDialog(owner);      if (processingForm.WorkException != null)      {        throw processingForm.WorkException;      }    }      }

现在使用就很简单了,如下:

Common.ShowProcessing("正在处理中,请稍候...", this, (obj) =>{   //这里写处理耗时的代码,代码处理完成则自动关闭该窗口},null);

 使用效果如下:

大家可以将上述代码直接复制到新建的窗体中,即可立即使用,上述代码若有不足之处,还请大家评论并指出,谢谢!