盡管MFC提供了標準的進度指示器控件(progress control),但是不能在狀態(tài)欄里直接使用這個控件,因此我創(chuàng)建了自己的可重用C++類來實現(xiàn)進度指示。這個類從CStatusBar派生。整個實現(xiàn)過程不是很難,思路是在狀態(tài)欄創(chuàng)建一個進度指示器控制,把它作為子窗口來對待,然后根據(jù)不同的狀態(tài)來顯示或者隱藏進度指示器。本文提供了一個范例程序pgrsbar,這個程序的框架使用了MFC的文檔/視圖結構,在編輯視圖里顯示文本文件。打開文件的時候,pgrsbar仿真長時間的加載過程并在狀態(tài)欄里顯示進度指示,如圖一所示。我將這個含有進度指示器的狀態(tài)欄封裝在了一個CStatusBar派生的類中——CProgStatusBar。
下面是這個類的詳細說明和使用方法:
CProgStatusBar是從標準的MFC類CStatusBar派生而來。我在CProgStatusBar派生類中加了一個CProgressCtrl類型的數(shù)據(jù)成員——m_wndProgBar,并且實現(xiàn)了三個重要的成員函數(shù)或方法:OnCreate、OnSize和OnProgress。下面是這三個函數(shù)的詳細說明:
OnCreate負責在狀態(tài)欄第一次被創(chuàng)建時接收控制,繼而創(chuàng)建進度指示器并將它初始化為一個子窗口,
int CProgStatusBar::OnCreate(LPCREATESTRUCT lpcs)
{
lpcs->style = WS_CLIPCHILDREN;
VERIFY(CStatusBar::OnCreate(lpcs)==0);
VERIFY(m_wndProgBar.Create(WS_CHILD, CRect(), this, 1));
m_wndProgBar.SetRange(0,100);
return 0;
}
OnCreate在狀態(tài)欄的式樣中加了一個WS_CLIPCHILDREN,它告訴Windows不要繪制子窗口以下的狀態(tài)欄區(qū)域,這樣可以減少屏幕閃爍。接著OnCreate創(chuàng)建進度指示器控制并將它的范圍設置成[0,100]。注意在這里創(chuàng)建進度指示器控制時沒有用WS_VISIBLE,因為我想在程序開始的時候隱藏它。
無論何時,只要你在某個窗口里添加子窗口,那么一定要負責管理它的大小尺寸,也就是說,當父窗口大小改變后,子窗口的大小也要跟著作相應的改變。一般來說,這個工作在父窗口的WM_SIZE/OnSize處理例程中完成: // 狀態(tài)欄大小改變以后,子窗口的尺寸跟著變
void CProgStatusBar::OnSize(...)
{
CStatusBar::OnSize(...);
CRect rc;
GetItemRect(0, rc);
m_wndProgBar.MoveWindow(rc,FALSE);
}
CProgStatusBar::OnSize 負責移動進度指示器到你期望的位置:例子程序是把它放在了狀態(tài)欄的第一個窗格,這個窗格通常用來顯示程序的“就緒”信息和命令提示信息。注意這里不論進度指示器時處于可見狀態(tài)還是隱藏狀態(tài),MoveWindow都照樣起作用——所以即便是進度指示器處于隱藏狀態(tài),其窗口大小同樣是可調(diào)的。
說完窗口大小的調(diào)整,下面我們來看看進度指示器的顯示,進度指示器狀態(tài)的顯示在CProgStatusBar::OnProgress中完成。它有一個類型為UINT的入口參數(shù):參數(shù)值的范圍從0到100,表示進度百分比,0表示進度沒開始,100表示全部完成。如果這個參數(shù)的值大于0,則OnProgress顯示進度控制并設置指示器的位置;如果參數(shù)值等于0,則 OnProgress隱藏進度控制。
雖然我們常常都把子窗口控制放在父窗口能繪制的區(qū)域的最上面,但這樣做在繪制方面是有一定風險的。在你隱藏/顯示進度控制時尤其如此,你會發(fā)現(xiàn)有兩個問題:第一,因為進度指示器顯示在狀態(tài)欄的第一個窗格位置,所以如果指示器顯示時已經(jīng)顯示有狀態(tài)信息,那么進度指示器和狀態(tài)信息文本就會有沖突,相互干擾。如圖二所示。之所以會這樣,是因為進度控制假設其繪制背景是干凈的,并且只繪制進度控制的著色部分。解決這個問題最簡單的方法是調(diào)用CStatusBar::SetWindowText(NULL)函數(shù)在顯示進度指示器之前打掃一下環(huán)境衛(wèi)生,清除以前的文本。對于狀態(tài)欄來說,SetWindowText函數(shù)的作用是設置狀態(tài)欄第一個窗格的文本。
反之,當調(diào)用OnProgress(0)清除進度控制時也存在類似的問題,CProgStatusBar::OnProgress 隱藏進度控制后,狀態(tài)欄第一個窗格該顯示什么信息呢?一般顯示“就緒”或其它的提示信息。當應用程序不做任何事情時,MFC程序總是在這個位置顯示資源串AFX_IDS_IDLEMESSAGE表示的文本,其缺省值為“就緒”,但你可以在RC文件中任意修改這個值。以我個人的觀點,我總是認為在這里顯示就緒信息有點兒土。不管怎樣,在MFC程序的狀態(tài)欄中顯示就緒信息很容易: // 在 CProgStatusBar::OnProgress函數(shù)中
// WM_SETMESSAGESTRING 的定義在文件
GetParent()->PostMessage(WM_SETMESSAGESTRING,AFX_IDS_IDLEMESSAGE);
如果你愿意,完全可以創(chuàng)建不同的ID和消息,如用ID_DONE_LOADING表示“加載完成”,以此取代“就緒”。
CProgStatusBar實現(xiàn)了含有進度控制的狀態(tài)欄。它的使用方法很簡單:用CProgStatusBar代替CStatusBar聲明實例,然后在任何想要顯示進度控制指示的地方調(diào)用CProgStatusBar::OnProgress。詳細代碼請參考本文例子程序。創(chuàng)建CProgStatusBar很容易,它的原理與標準的狀態(tài)欄CStatusBar一樣——你只要明白什么時候以及在哪里調(diào)用OnProgress即可。
實際應用中要根據(jù)你的應用而定。我建議你按照例子程序的方法來使用它。不要直接暴露CProgStatusBar::OnProgress函數(shù),例子程序的主窗口實現(xiàn)了一個特殊的消息——MYWM_PROGRESS,它將WPARAM傳遞到CProgStatusBar::OnProgress: // MYWM_PROGRESS 在resource.h文件中定義
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_MESSAGE(MYWM_PROGRESS,OnProgress)
END_MESSAGE_MAP()
// 處理MYWM_PROGRESS消息
LRESULT CMainFrame::OnProgress(WPARAM wp, LPARAM lp)
{
m_wndStatusBar.OnProgress(wp);
return 0;
}
這樣想要報告進度指示的任何對象都可以通過發(fā)送一個消息到主框架來代替直接對狀態(tài)欄進行調(diào)用。例如,在例子程序中,文檔的Serialize函數(shù)利用Sleep函數(shù)仿真耗時加載,每隔150毫秒報告一次進度狀態(tài)。通常,你肯定想讓文檔這樣的低級對象盡可能少地包含UI代碼。雖然在實踐中很少有程序員遵守這一原則,但最好不用UI操作你的文檔類,因為你很可能有一天想在某個服務中或命令行程序中使用它。不管怎么說,發(fā)送消息到框架總比暴露框架的內(nèi)部成員要好得多。為了安全起見,文檔的Serialize函數(shù)在發(fā)送消息前最好檢查一下框架是否存在。如果你不想從文檔發(fā)送Windows消息,可以用MFC的視圖更新機制來做。你可以發(fā)明一個“暗示”代碼以及一個小結構來保存進度百分比數(shù)據(jù),并通過向框架發(fā)送MYWM_PROGRESS消息調(diào)用暗示信息。這是從文檔到視圖/框架傳遞進度控制信息的最省事的方式。
CProgStatusBar假設你報告的進度指示數(shù)據(jù)總是0到100的整數(shù),并且假設你中途可以中斷進度指示器。以便你在想要改變設置的時候,可以修改CProgStatusBar或者調(diào)用CProgStatusBar::GetProgressCtrl來直接存取進度控制狀態(tài)。