之前先后發表過:《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)
其余邏輯實現保持不變。
使用更簡單,如下圖示: