之前先后发表过:《Winform应用程序实现通用遮罩层》、《Winform应用程序实现通用消息窗口》,这两款遮罩层其实都是基于弹出窗口的,今天为大家分享一个比较简单但界面相对友好的另一种实现方案,废话不多说,直接进入主题。
一、实现思路(解决问题顺序):
透明遮罩:
1.实现可设置透明的Panel控件(MaskPanel);
2.Panel控件(MaskPanel)能够覆盖父容器(一般是当前窗体form对象)客户区区域(即:与父容器客户区区域大小相同),并处于最上层,保证父容器上的任何控件都被盖住并保证不可用;
3.Panel控件(MaskPanel)必需实现随着父容器大小的改变而改变;
4.Panel控件(MaskPanel)上可呈现以表示正在加载的动图或者文字,并且居中;
异步:
实现的方法有很多,比如异步委托、Task等,而这是在winform项目中,此次就直接使用BackgroundWorker
二、关键解决方案:
1.可设置透明控件:通过自定义控件,并重写CreateParams(其中: cp.ExStyle |= 0x00000020;)、OnPaint(其中:labelBorderPen、labelBackColorBrush的Color=Color.FromArgb(_alpha, this.BackColor))两个方法即可;
2.能够覆盖父容器客户区区域:this.Size = this.Parent.ClientSize;this.Left = 0;this.Top = 0;
3.随着父容器大小的改变而改变:this.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
4.呈现以表示正在加载的动图或者文字,并且居中:
添加PictureBox,设置Image为loading.gif动图,SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; Point Location = new Point(this.Location.X + (this.Width - pictureBox_Loading.Width) / 2, this.Location.Y + (this.Height - pictureBox_Loading.Height) / 2);//居中
好了,最后贴出实现的源代码:
MaskPanel:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
public partial class MaskPanel : Control { private System.ComponentModel.Container components = new System.ComponentModel.Container(); private bool _isTransparent = true ; //是否透明 [Category( "透明" ), Description( "是否使用透明,默认为True" )] public bool IsTransparent { get { return _isTransparent; } set { _isTransparent = value; } } private int _alpha = 125; //设置透明度 [Category( "透明" ), Description( "设置透明度" )] public int Alpha { get { return _alpha; } set { _alpha = value; } } public MaskPanel(Control parent) : this (parent, 125) { } /// <summary> /// 初始化加载控件 /// </summary> /// <param name="Alpha"透明度</param> public MaskPanel(Control parent, int alpha) { SetStyle(ControlStyles.Opaque, true ); //设置背景透明 base .CreateControl(); _alpha = alpha; parent.Controls.Add( this ); this .Parent = parent; this .Size = this .Parent.ClientSize; this .Left = 0; this .Top = 0; this .Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; this .BringToFront(); PictureBox pictureBox_Loading = new PictureBox(); pictureBox_Loading.BackColor = System.Drawing.Color.Transparent; pictureBox_Loading.Image = Properties.Resources.loading; pictureBox_Loading.Name = "pictureBox_Loading" ; pictureBox_Loading.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; Point Location = new Point( this .Location.X + ( this .Width - pictureBox_Loading.Width) / 2, this .Location.Y + ( this .Height - pictureBox_Loading.Height) / 2); //居中 pictureBox_Loading.Location = Location; pictureBox_Loading.Anchor = AnchorStyles.None; this .Controls.Add(pictureBox_Loading); this .Visible = false ; } protected override CreateParams CreateParams { get { CreateParams cp = base .CreateParams; cp.ExStyle |= 0x00000020; // 开启 WS_EX_TRANSPARENT,使控件支持透明 return cp; } } protected override void OnPaint(PaintEventArgs pe) { Pen labelBorderPen; SolidBrush labelBackColorBrush; if (_isTransparent) { Color cl = Color.FromArgb(_alpha, this .BackColor); labelBorderPen = new Pen(cl, 0); labelBackColorBrush = new SolidBrush(cl); } else { labelBorderPen = new Pen( this .BackColor, 0); labelBackColorBrush = new SolidBrush( this .BackColor); } base .OnPaint(pe); pe.Graphics.DrawRectangle(labelBorderPen, 0, 0, this .Width, this .Height); pe.Graphics.FillRectangle(labelBackColorBrush, 0, 0, this .Width, this .Height); } protected override void Dispose( bool disposing) { if (disposing) { if (!((components == null ))) { components.Dispose(); } } base .Dispose(disposing); } } |
为了实现通用,同时保证所有的窗体都有异步执行并显示遮罩效果,故此处采用定义一个窗体基类:FormBase,里面定义一个受保护的DoWorkAsync方法, 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
public partial class FormBase : Form { public FormBase() { InitializeComponent(); this .StartPosition = FormStartPosition.CenterParent; } /// <summary> /// 多线程异步后台处理某些耗时的数据,不会卡死界面 /// </summary> /// <param name="workFunc">Func委托,包装耗时处理(不含UI界面处理),示例:(o)=>{ 具体耗时逻辑; return 处理的结果数据 }</param> /// <param name="funcArg">Func委托参数,用于跨线程传递给耗时处理逻辑所需要的对象,示例:String对象、JObject对象或DataTable等任何一个值</param> /// <param name="workCompleted">Action委托,包装耗时处理完成后,下步操作(一般是更新界面的数据或UI控件),示列:(r)=>{ datagirdview1.DataSource=r; }</param> protected void DoWorkAsync(Func< object , object > workFunc, object funcArg = null , Action< object > workCompleted = null ) { var bgWorkder = new BackgroundWorker(); //Form loadingForm = null; Control loadingPan = null ; bgWorkder.WorkerReportsProgress = true ; bgWorkder.ProgressChanged += (s, arg) => { if (arg.ProgressPercentage > 1) return ; #region Panel模式 var result = this .Controls.Find( "loadingPan" , true ); if (result == null || result.Length <= 0) { loadingPan = new MaskPanel( this ) { Name = "loadingPan" }; } else { loadingPan = result[0]; } loadingPan.BringToFront(); loadingPan.Visible = true ; #endregion }; bgWorkder.RunWorkerCompleted += (s, arg) => { #region Panel模式 if (loadingPan != null ) { loadingPan.Visible = false ; } #endregion bgWorkder.Dispose(); if (workCompleted != null ) { workCompleted(arg.Result); } }; bgWorkder.DoWork += (s, arg) => { bgWorkder.ReportProgress(1); var result = workFunc(arg.Argument); arg.Result = result; bgWorkder.ReportProgress(100); }; bgWorkder.RunWorkerAsync(funcArg); } } |
使用示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private void button1_Click( object sender, EventArgs e) { int startNo = 20; button1.Enabled = false ; this .DoWorkAsync((o) => //耗时逻辑处理(此处不能操作UI控件,因为是在异步中) { int result = 0; for ( int i = 1; i <= Convert.ToInt32(o); i++) { result += i; Thread.Sleep(500); } return result; }, startNo, (r) => //显示结果(此处用于对上面结果的处理,比如显示到界面上) { label1.Text = r.ToString(); button1.Enabled = true ; }); } |
效果图就不贴出来了,大家可以COPY上面的所有代码,即可测试出效果。
2017年3月15日优化补充:
为了提高异步加载编码的方便,特优化了DoWorkAsync方法,将返回值由object改为dynamic,这样就比较方便,直接返回,直接使用
方法签名如下:
protected void DoWorkAsync(Func<object, dynamic> workFunc, object funcArg = null, Action<dynamic> workCompleted = null)
其余逻辑实现保持不变。
使用更简单,如下图示: