在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; } }
代碼中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);
使用效果如下:
大家可以將上述代碼直接復制到新建的窗體中,即可立即使用,上述代碼若有不足之處,還請大家評論并指出,謝謝!
文章列表