摘要
本文旨在剖析VC++工程中加入 SplashScreen 的原理,並給出在VC++ MFC(exe)工程中加入 SplashScreen 的步驟。
關鍵字 SplashScreen,原理
環境:Windows 98SE/2000,VC++ 6.0
SplashScreen - 我們使用 Word 之類的軟件在啟動的短暫時間裡就會看到它的身影。它通常用以在程序啟動時顯示程序及用戶名稱,版權信息等。我也不知道它准確的名稱是什麼(是閃屏嗎?),就這樣稱呼吧。也許你也想在自己的工程裡加入這樣的特性,本文將以創建實際工程的方式逐步剖析其實現原理。
注意:為避免實際所使用工程名給類或對象名帶來的干擾,除非特別說明,在本文中將使用基類名如CWinApp、CMainFrame、CDialog來代替實際工程中的相應派生類名進行描述。
Visual C++是一個相當強大的C++開發工具,它內嵌了對SplashScreen的支持。但是在MFC EXE類型工程中只是對帶有主框架類的SDI或MDI工程提供了這一支持,基於對話框類的工程則被排除在外。現在讓我們開始吧。第一步是在SDI工程中加入SplashScreen。
首先利用AppWizard生成一個SDI工程,除了其中Docking ToolBar必須選擇外(我認為這是MFC的一個Bug,當然這與本文討論的SplashScreen沒有關系),其他的文檔-視圖支持、狀態條之類的都可以不要,這樣可以盡量減少無用的代碼。
通過IDE中的菜單Project->Add to Project->Components and Controls,我們就可以從Visual C++ Components中選擇Splash Screen這個組件插入工程。
在點擊了"Insert"後會彈出一個如下圖所示的對話框,這是設置插入該工程中的SplashScreen的類名、顯示用位圖的ID及文件名,采用缺省值即可。
通過以上幾步的操作,就會在工程目錄下生成Splash.CPP和Splash.H文件,這便是CSplashWnd類的實現文件與頭文件。同時工程中CWinApp與CMainFrame類中的部分代碼也會被修改,以實現CSplashWnd窗口的消息處理。
接著我們來看看 CSplashWnd 類的聲明與主要的代碼(已經過刪減):
//類的聲明
class CSplashWnd : public CWnd
{
CSplashWnd();
~CSplashWnd();
virtual void PostNcDestroy();
static void EnableSplashScreen(BOOL bEnable = TRUE);
static void ShowSplashScreen(CWnd* pParentWnd = NULL);
static BOOL PreTranslateAppMessage(MSG* pMsg);
BOOL Create(CWnd* pParentWnd = NULL);
void HideSplashScreen();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnPaint();
afx_msg void OnTimer(UINT nIDEvent);
CBitmap m_bitmap; //SplashScreen窗口顯示用的位圖對象
static BOOL c_bShowSplashWnd; //是否要顯示SplashScreen的標志量
static CSplashWnd* c_pSplashWnd;
};
//是否使用SplashScreen
void CSplashWnd::EnableSplashScreen(BOOL bEnable)
{
c_bShowSplashWnd = bEnable;
}
//創建CsplashWnd對象,並調用Create()創建窗口
void CSplashWnd::ShowSplashScreen(CWnd* pParentWnd)
{
//如果不要顯示SplashScreen或SplashWnd對象已經被創建則返回
if (!c_bShowSplashWnd || c_pSplashWnd != NULL)
return;
c_pSplashWnd = new CSplashWnd;
if (!c_pSplashWnd->Create(pParentWnd))
delete c_pSplashWnd;
else
c_pSplashWnd->UpdateWindow();
}
//裝入SplashScreen欲顯示位圖,通過CreateEx()激發OnCreate()完成窗口創建與設置
BOOL CSplashWnd::Create(CWnd* pParentWnd)
{
if (!m_bitmap.LoadBitmap(IDB_SPLASH))
return FALSE;
BITMAP bm;
m_bitmap.GetBitmap(&bm);
return CreateEx(0,
AfxRegisterWndClass(0,AfxGetApp()->LoadStandardCursor(IDC_ARROW)),
NULL,
WS_POPUP | WS_VISIBLE,
0,
0,
bm.bmWidth,
bm.bmHeight,
pParentWnd->GetSafeHwnd(),
NULL);
}
//銷毀窗口,刷新框架
void CSplashWnd::HideSplashScreen()
{
DestroyWindow();
AfxGetMainWnd()->UpdateWindow();
}
//利用窗口創建結構創建窗口,並設置定時器在750ms後觸發OnTimer()
int CSplashWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
CenterWindow(); //窗口居中顯示
SetTimer(1, 750, NULL); //設置定時器
return 0;
}
//將鍵盤和鼠標消息傳遞給CSplashWnd對象,以銷毀窗口
BOOL CSplashWnd::PreTranslateAppMessage(MSG* pMsg)
{
if (c_pSplashWnd == NULL)
return FALSE;
if (pMsg->message == WM_KEYDOWN ||
pMsg->message == WM_SYSKEYDOWN ||
pMsg->message == WM_LBUTTONDOWN ||
pMsg->message == WM_RBUTTONDOWN ||
pMsg->message == WM_MBUTTONDOWN ||
pMsg->message == WM_NCLBUTTONDOWN ||
pMsg->message == WM_NCRBUTTONDOWN ||
pMsg->message == WM_NCMBUTTONDOWN)
{
c_pSplashWnd->HideSplashScreen();
return TRUE;
}
return FALSE;
}
void CSplashWnd::OnTimer(UINT nIDEvent)
{
HideSplashScreen();
}
再看看CWinApp和CMainFrame類中發生了什麼樣的改變:
(1)在CWinApp::InitInstance()中調用CSplashWnd::EnableSplashScreen()設置c_bShowSplashWnd;
在PreTranslateMessage()中調用CSplashWnd::PreTranslateAppMessage(),將鍵盤和鼠標消息傳遞給CSplashWnd對象,從而進一步調用CSplashWnd::HideSplashScreen()實現SplashScreen窗口的自身銷毀。
(2)在CMainFrame對象的OnCreate()中調用CSplashWnd::ShowSplashScreen()創建一個靜態的SplashScreen窗口對象c_pSplashWnd,並設置其父窗口為CMainFrame。在這個過程中,CSplashWnd自身會通過創建來設置一個定時器,然後定時器在第一個周期觸發時便調用HideSplashScreen()銷毀自己。
(3) 而CMainFrame對象的窗口創建消息則是由CWinApp對象在InitInstance()中通過
m_pMainWnd->ShowWindow()調用觸發的。
整個過程可以用下圖表示,基本原理就是由CMainFrame來創建CSplashWnd,然後由CSplashWnd自己的定時器觸發定時消息來銷毀窗口。所以 CSplashWnd 的加入與SDI還是MDI都沒有關系。
第二步,我們再來看看如何在基於對話框的工程中加入 SplashScreen。
通過對以上SDI工程中加入SplashScreen原理的剖析,我想大家也想到如何在基於對話框的工程中加入這一特性了。其實質就是由CDialog類完成SDI工程中CMainFrame類的工作,實現步驟如下:
(1)利用ClassWizard為CMyDialog添加WM_CREATE消息的處理函數OnCreate();(這裡使用CMyDialog是為了與函數內的基類名CDialog區別。)
int CMyDialog::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
CSplashWnd::ShowSplashScreen(this);
return 0;
}
(2)利用ClassWizard為CWinApp添加消息轉發處理函數PreTranslateMessage();
BOOL CWinApp::PreTranslateMessage(MSG* pMsg)
{
if (CSplashWnd::PreTranslateAppMessage(pMsg))
return TRUE;
return CWinApp::PreTranslateMessage(pMsg);
}
(3)CWinApp::InitInstance()中加入如下調用: CSplashWnd::EnableSplashScreen(TRUE);
(4)當然你還需要將上一個SDI工程中生成的Splash.CPP與Splash.H文件拷貝到當前工程目錄下,並利用Project->Add to Project->Files將這兩個文件引入工程。同時還要在CWinApp與CMainFrame的實現文件中#include "Splash.H"。
(5)然後在資源管理器裡添加一個ID為IDB_SPLASH的位圖。由於VC++的IDE只能顯示256色以下的位圖,所以如果你想顯示一幅真彩色的位圖,就請用Import方式導入一幅預先制作好的位圖。當然VC++會提示位圖已經成功導入,只是無法在IDE的位圖編輯器中顯示,而在程序運行時就會顯示了。如果你想象Word那樣顯示用戶名等信息,可以在CSplashWnd::Create()中裝載位圖之後增加自己的代碼來修改位圖。
通過以上這幾步操作,我們就完成了在基於對話框的工程中加入SplashScreen的工作。清楚了嗎?
本文配套源碼