[C#] (原創)進度等待窗口(附:自定義控件的使用)

一、前言

技術沒有先進與落後,只有合適與不合適。

在程序當中,經常有耗時較長的操作,為了給用戶更好的體驗,就需要給用戶一個及時的反饋,這種時候就需要用到進度等待窗口。

實現進度等待窗口的技術有很多,比如:BackgroundWorker、Thread等。

不過技術不是難點,難點在於怎麼使等待窗口美觀實用。所以本文中就基於前幾篇的自定義控件:LProgressBar和LLabel,去實現進度等窗口。

相關文章:

[C#] (原創)一步一步教你自定義控件——04,ProgressBar(進度條)

[C#] (原創)一步一步教你自定義控件——05,Label(原生控件)  

 

相信看完的你,一定會有所收穫。

本文地址://www.cnblogs.com/lesliexin/p/14121618.html


 

二、前期分析

(一)為什麼需要進度等窗口?

為了在執行耗時操作時,給用戶更好的、更直觀的體驗。

(二)預期功能效果

1,功能

(1),支持「取消」操作,當然也支持「不能取消」操作。

(2),支持進度明確與進度不明確時顯示不同樣式。

(3),支持明細進度。

2,效果

Win7:

Win10:

 


 

三、開始實現

(一)布局窗體

 1,新建窗體

(1),在工程上右擊,選擇「添加」->「窗體(Windows 窗體)」,命名為:LProgress.cs。

 

(2),修改窗體相關屬性

Font:微軟雅黑,9pt。比之默認的「宋體,9pt」的效果更加美觀。

BackColor:White。更加美觀,特別是在Win10上。

ForeColor:Black。為了防止被某些系統主題影響而顯示的不是黑色。

FormBorderStyle:FixedDialog。使用戶不可調用窗口尺寸。

MaximizeBox:False。不顯示最大化按鈕。

MinimizeBox:False。不顯示最小化按鈕。

ShowIcon:False。不顯示窗口圖標。

ShowInTaskbar:False。不在任務欄上顯示圖標。

TopMost:True。置頂顯示窗口。

 

2,添加控件

這裡需要說明一下,在添加控件時,如果如本文這樣窗體與自定義控件工程在同一個解決方案中,那麼在工具欄的最上方會自動顯示當前工具中的自定義控件,選中即可使用。

如果是窗體與自定義控件工程不在同一個解決方案中,比如引用的是自定義控件的DLL文件,那麼就需要將自定義控件DLL拖到工具欄上,此時工具欄上就會顯示出裏面的自定義控件。

 

(1)總體控件布局

(2)lPBar_Main(主進度)

控件:LProgressBar

關鍵屬性:

 

(3)lPBar_Child(子進度)

控件:LProgressBar

關鍵屬性:

 

 

(4)lbl_Main(主進度文本)

控件:LLabel

關鍵屬性:

 

 

(5)lbl_Child(子進度文本)

控件:LLabel

關鍵屬性:

 

 

(6)btn_Cancel(取消按鈕)

控件:Button

關鍵屬性:

 

 

(7)相關說明

 這裡說下lbl_Main和lbl_Child為什麼不使用原生Label控件,而使用自定義LLabel控件。

因為在實際運行時,窗口大小是固定不變的,所以如果內容過多時,如果使用原生Label,就會在鼠標移上去後顯示懸浮提示,對整體外觀有所影響。如果內容變化快的話,懸浮提示也會頻繁顯示,但提示的文本卻早已過去,沒有提示的意義。

所以此處使用了基於原生控件改造的自定義控件:LLabel。(詳見:[C#] (原創)一步一步教你自定義控件——05,Label(原生控件)  )

這樣通過屬性」L_EnableAutoTip=False「,就可以不再顯示懸浮提示。

 

 (二)添加屬性

1,是否顯示「取消」按鈕

對於耗時操作,有的時候是可以讓用戶取消的;而有的時候,則是不能讓用戶取消。所以需要一個屬性去控制是否顯示「取消」按鈕。

同時,為了美觀,在不顯示「取消」按鈕時,調整窗口高度到合適位置。

 

2,主進度條模式(進度已知/進度未知)

一些操作的進度是已知的,比如下載進度、複製進度;而有一些操作的的進度是未知的,比如查詢操作、調用其他耗時任務等。

針對進度已知還是進度未知,則進度條的樣式也需要有相應的改變。

 

 

3,窗口標題

當前進度等待窗口的標題

 

 

4,默認顯示的主進度文本

默認的主進度文本

 

 

5,是否顯示子進度條

因為大部分時候只需要一個進度,所以子進度條默認是不顯示的。而且,子進度條的作用決定了子進度條本身是可以靈活顯示/不顯示的。

本屬性只是提供一個初始化的狀態。

 

 

6,自定義參數

因為進度等待窗口是一個單獨的窗體,而耗時操作也是在本窗口中執行,所以就需要進行數據的交互,而此參數即是為了進行數據的交互。

 

 

(三)添加構造函數

為了方便調用,為窗口增加一帶參數的構造函數。

這裡給大家一個小技巧,在添加構造函數時,可以借用VS的「快速操作和重構…」功能來快速生成構造函數。

 

(四)添加事件

 本篇中執行耗時操作所採用的方法是開一新線程去執行,此方法非常簡單且使用方便,所以需要添加一個事件,以讓調用者在事件中執行耗時任務。

 

(五)添加公共方法

因為採用的是線程+事件,所以需要開放一些公共方法,以供調用者在事件實現中使用。

1,更新主進度文本

此方法用於更新主進度文本的內容。

 

(1),在方法中判斷本次更新文本是否與當前顯示文本是否一致:if(sMsg!=sLastMainMsg,是為了減少不必要的賦值,減少閃爍,提高性能。(下同將不再贅述)

(2),因為是在新線程中操作控件,所以如果直接操作控件的話,比如給控件賦值,將會提示「線程間操作無效。從不是創建控件’XXX’的線程訪問它」的錯誤,這種情況下需要通過委託的方式去去操作控件。(當然也可以使用不安全代碼去解除這種限制,但不推薦使用。)(下同將不再贅述)

 

2,更新主進度條進度值

此方法用於在主進度條是進度已知時,更新主進度條的進度。

 

3,更新子進度文本

此方法用於更新子進度文本。

4,更新子進度條進度值

此方法用於在子進度條是進度已知時,更新子進度條的進度。

 

5,改變子進度條顯示狀態

因為子進度條是靈活顯示的,所以提供本方法去改變進度條的顯示狀態。

 

6,改變子進度條樣式(進度已知/進度未知)

因為子進度條是靈活顯示的,所以提供本方法去改變進度條的樣式,是進度已知時樣式還是進度未知時樣式。

當是進度未知時,要自動啟動進度條動畫。

 

7,退出

在事件中實現操作時,可以使用本方法手動結束等待窗口。

 

(六)事件實現

1,窗口加載事件

在窗口加載時,我們要滿足以下條件:

(1),為了更加美觀,所以我們不會隱藏掉「關閉」按鈕,但是「關閉」按鈕會和「取消」按鈕功能相衝突,特別是在不能取消時。

所以我們要將「關閉」按鈕無效化。主要使用到了系統API:GetSystemMenu和RemoveMenu

(2),如果是進度未知時,要令主進度條開始動畫。

(3),開啟新線程。此線程即是用來執行耗時任務的,所以要在此線程方法中觸發事件委託,以供調用者實現。

 

所以,加載的實現如下:

2,「取消」按鈕事件

點擊「取消」時,要滿足以下幾個條件:

(1),如果主進度條是進度未知,則停止進度條動畫。

(2),安全退出。

 

所以,「取消」按鈕的實現如下:


 

四、效果演示

(1),新建窗體,添加LSwitchButton、Button、Label,如下:

 

(2),在「進度窗口」點擊事件中調用進度等待窗口。

 

(3),實現耗時任務

(4),運行

 


 

 

五、結束語

本篇所實現的進度等待窗口,技術上很簡單,但在美觀上、功能上並不弱,而且使用起來也簡單。作為日常使用也是足夠的。

本篇文章的目的更多的是為了給大家一個使用自定義控件的例子,畢竟自定義控件要在實際的應用中才能體現出價值。

不要被常規思維所束縛,相信自己所掌握的知識。

 


 

六、源代碼及工程下載

//files.cnblogs.com/files/lesliexin/05,LProgress.7z