向導是一種用來簡化用戶操作的程序。在Microsoft 的所有產品中都存在向導,如Office2000 中的Web 頁向導就是一個十分典型的向 導(如下圖所示),還有常用的VC++向導。
一個基本的向導程序應該包含以下幾個基本按鈕: 取消、上一步、下一步、完成、幫助。
一、標准向導程序
在 VC++中,可以使用類CPropertySheet和類CPropertyPage方便地編寫一個向導程序。
首先我們來介紹一下類CPropertySheet 和類CPropertyPage。
1. 類CPropertyPage 是從CDiaglog中派生出來的,具有Diaglog的基本性質,需要注意的是它的樣式必須是Child。
2. 類CPropertySheet 是一個屬性表,也是一個窗體,相當一個容器,用來存放所有的CpropertyPage。它不是 從CDialog 派生出來的,但是它可以象普通對話框類似的操作, 如DoModal(),當用 DoModal()顯示 後,它就包含了“取消”、“上一步”、“下一步” 等基本按鈕。
下面給出一個實例
① 新建一個 VC++ MFC AppWizard 工程,命名為TraditionalWizard,並選擇Dialog Based 樣式。
② 在自動生成 的Dialog 資源中加入一個按鈕IDC_BENGINWIZ 用來啟動向導。
③ 創建 CPropertyPage。新建Dialog 資源,命名為IDD_STEP1,注意一定要將新建對話框的Style屬性設置成Child 和邊界屬性設置為Thin,並且不要生成一個新類。
用ClassWizard 生成一個新類,命名為CStep1,基類為CPropertyPage,且將Dialog ID 設置為剛生成的資源IDD_STEP1。這樣就生成了一個新屬性頁Step1。如此操作就可以 同樣生成Step2、Step3 屬性頁。為了方便顯示,在每個對話框都放置了一個控件,用來表示當前是哪一步。
④ 創建 CPropertySheet。新建一個類,命名為CWizard,基類為CPropertySheet。並將屬性頁和屬性表關聯起來。代碼為
//將代碼放在按鈕IDC_BEGINWIZ的Click事件中
CWizard MyWizard(_T("我的向導 "),this,1); //生成一個屬性表
CStep1 MyStep1; //屬性頁1
CStep2 MyStep2; //屬性頁2
CStep3 MyStep3; //屬性頁3
MyWizard.AddPage(&MyStep1); //添加屬性頁1
MyWizard.AddPage(&MyStep2); //添加屬性頁2
MyWizard.AddPage(&MyStep3); //添加屬性頁3
MyWizard.SetWizardMode(); //將屬性表設置成向導樣式
MyWizard.SetActivePage(&MyStep1); //設置第一頁為第一步
MyWizard.DoModal(); //顯示屬性表
⑤協調顯示。在每一頁為當前頁時,都會觸發OnSetActive事件,故對每一個屬性頁都要重載該函數,在CStep1類上選擇Add Virtual Function ...。因為顯示第一頁時,不存在“上一步”,故在CStep1的 OnSetActive函數中需要添加如下代碼:
//代碼放在OnSetActive函數中
CPropertySheet* pParent=(CPropertySheet*)GetParent(); // 獲得屬性表的指針
pParent->SetWizardButtons(PSWIZB_NEXT); // 設置屬性表的顯示按鈕只為下一步
SetDlgItemText(IDC_TEXT1,"這是向導的第一步");
同樣在顯示中間頁時應該設置成即有“上一步”,也有“下一步”,代碼為:
CPropertySheet* pParent=(CPropertySheet*)GetParent();
pParent->SetWizardButtons(PSWIZB_NEXT|PSWIZB_BACK);
SetDlgItemText(IDC_TEXT2,"這是向導的第二步");
最後在顯示最後一頁時只顯示“完成”和“上一步”,代碼為:
CPropertySheet* pParent=(CPropertySheet*)GetParent();
pParent->SetWizardButtons(PSWIZB_FINISH|PSWIZB_BACK);
SetDlgItemText(IDC_TEXT3,"這是向導的第三步");
這樣一個基本的向導程序就完成了,其效果如圖所示
二、自定義向導程序
通過上面的例子,我們不難發現標准的向導基本能滿足要求,但仍然存在一些缺陷:
1.不能改變向導按鈕的樣式,如想在“上一步”、“下一步就”按鈕上添加圖標
2.不能象上面的Web向導一樣有個“完成”按鈕進行默認設置
3.不能修改向導按鈕的位置
上述缺陷是因為我們采用了CPropertySheet類,而CPropertySheet類不是一個可修改的資源。
為了達到個性化向導的目的,我們可以不使用CPropertySheet類和CPropertyPage類。
設計的基本思路:
1.采用標准的向導的工作方式。每一步就是一個對話框,向導本身也是一個對話框,用來容納每步對話框.
2.每步的對話框應 該沒有Title、沒有邊界、樣式為Child,當點擊“下一步”或“上一步”時,將這個 對話框定位到要顯示的位置。
3.因為向導一般都包含很多步,為了管理這些頁,我們可以創建一個鏈表來管理每一步的對話框。
4.為了方便對話框定位,可以事先定義好位置。
三、自定義向導的實現
1.工程的建立與基本界面的生成
生成一個MFC APPWIZARD 新工程,命名為CustomWizard,在Step1 中選擇基於Dialog Based樣式。
在自動生成的Dialog 資源中加入一個按鈕IDC_BENGINWIZ 用來啟動向導。
新建一個對話框 資源,命名為IDC_WIZARD,用來顯示自定義向導界面,如圖
依次創建向導的每頁 的對話框資源,命名為IDD_STEP1,IDD_STEP2,IDD_STEP3,
(圖4)
2.生成所需要的類
為了方便敘述,表1將所用的類進行了歸納
(表1)
類名 基類 說明 CWizard CDialog 向導的框架 CStep1 CDialog 向導的第一步 CStep2 CDialog 向導的第二步 CStep3 CDialog 向導的第三步 CCustomWizardDlg CDialog 啟動向導
3. 在CWizard添加要使用的數據結構
為了方便描述,表2列出了使用到的成員變量
(表2)
成員變量 類型 說明 rectPage CRect 每頁顯示的范圍 nPageCount UINT 頁的總數 nCurrentPage UINT 正在顯示的頁 nPageLink PAGELINK* 用來鏈接所有的頁 typedef struct PAGELINK{
UINT nNum;
CDialog* pDialog;
struct PAGELINK* Next;};
nNum為頁的編號pDialog為頁所對應的對話框的指針
4. CWizard所使用到的函數 添加一個新頁到Wizard框架,入口參數為要添加的對話框指針和ID
void CWizard::AddPage(CDialog* pDialog, UINT nID)
{
struct PAGELINK* pTemp = pPageLink;
//插入新生成的結點
struct PAGELINK* pNewPage = new PAGELINK;
pNewPage->pDialog = pDialog;
pNewPage->pDialog->Create(nID,this); // 以無模式創建窗口
ASSERT(::IsWindow(pNewPage->pDialog->m_hWnd));
// 檢查每頁的樣式
DWORD dwStyle = pNewPage->pDialog->GetStyle();
ASSERT((dwStyle & WS_CHILD) != 0); // 子窗口
ASSERT((dwStyle & WS_BORDER) == 0); // 無邊界
// 顯示
pNewPage->pDialog->ShowWindow(SW_HIDE); //先隱藏,需要時再顯示
pNewPage->pDialog->MoveWindow(rectPage);
//移動對話框到制定位置,rectPage已經初始化了
pNewPage->Next=NULL;
pNewPage->nNum=++nPageCount; //計數器加1
if (pTemp) //插入到鏈表
{ //如果不是空鏈表
while (pTemp->Next) pTemp=pTemp->Next; // 移動鏈表末尾
pTemp->Next=pNewPage;
}
else // 空鏈表
pPageLink=pNewPage; //若是第一個節點
}
顯示的頁,入口參數為要顯示的某特定頁的編碼
void CWizard::ShowPage(UINT nPos)
{
struct PAGELINK* pTemp=pPageLink;
while(pTemp)
{
if(pTemp->nNum==nPos)
{
pTemp->pDialog->ShowWindow(SW_SHOW);
}
else
//不顯示
pTemp->pDialog->ShowWindow(SW_HIDE);
pTemp=pTemp->Next;
}
if (nPos>=nPageCount) //最後一頁
{
nCurrentPage=nPageCount;
SetWizButton(2);
return;
}
if (nPos<=1) //首頁
{
nCurrentPage=1;
SetWizButton(0);
return;
}
//如果是中間步
SetWizButton(1);
}
為了與顯示統一,需要相應的設置按鈕
void CWizard::SetWizButton(UINT uFlag)
{
GetDlgItem(IDC_CANCEL)->EnableWindow(TRUE);
GetDlgItem(IDC_PREV)->EnableWindow(TRUE);
GetDlgItem(IDC_NEXT)->EnableWindow(TRUE);
GetDlgItem(IDC_FINISH)->EnableWindow(TRUE);
switch(uFlag)
{
case 0: //第一步
GetDlgItem(IDC_PREV)->EnableWindow(FALSE);
break;
case 1: //中間步
break;
case 2: //最後一步
GetDlgItem(IDC_NEXT)->EnableWindow(FALSE);
break;
}
}
點擊“上一步”、“下一步”、“完成”、“取消”代碼
void CWizard::OnPrev()
{
// TODO: Add your control notification handler code here
ShowPage(--nCurrentPage);
}
void CWizard::OnNext()
{
// TODO: Add your control notification handler code here
ShowPage(++nCurrentPage);
}
void CWizard::OnFinish()
{
// TODO: Add your control notification handler code here
AfxMessageBox("采用默認值完成向導");
CDialog::OnOK();
}
void CWizard::OnCancel()
{
// TODO: Add your control notification handler code here
if (AfxMessageBox(IDS_QUIT,MB_OKCANCEL|MB_ICONQUESTION)==IDCANCEL)
return;
CDialog::OnCancel();
}
5. 輔助代碼,如初始化等
BOOL CWizard::OnInitDialog()
{
CDialog::OnInitDialog();
//獲得每頁顯示的范圍
CRect Rect1;
GetWindowRect(&Rect1); // 獲得主窗口的位置
int nCaption = ::GetSystemMetrics(SM_CYCAPTION); // 系統Title高度
int nXEdge = ::GetSystemMetrics(SM_CXEDGE);
int nYEdge = ::GetSystemMetrics(SM_CYEDGE);
CRect Rect2;
GetDlgItem(IDC_POS)->GetWindowRect(&Rect2); // 獲得框架的位置
Rect1.top=Rect1.top+nCaption+nYEdge; // 相對坐標
Rect1.left=Rect1.left+2*nXEdge;
//計算機位置
rectPage.top=Rect2.top-Rect1.top;
rectPage.left=Rect2.left-Rect1.left;
rectPage.bottom=Rect2.bottom-Rect1.top;
rectPage.right=Rect2.right-Rect1.left;
//頁示的添加要顯
CStep1* pStep1 = new CStep1;
CStep2* pStep2 = new CStep2;
CStep3* pStep3 = new CStep3;
AddPage(pStep1, IDD_STEP1);
AddPage(pStep2, IDD_STEP2);
AddPage(pStep3, IDD_STEP3);
//顯示第一頁
ShowPage(1);
return TRUE;// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
因為是無模式窗體,所以要自己銷毀窗體
void CWizard::OnDestroy()
{
CDialog::OnDestroy();
// TODO: Add your message handler code here
//每頁依次消除
struct PAGELINK* pTemp=pPageLink;
while(pTemp)
{
struct PAGELINK* pNextTemp = pTemp->Next;
pTemp->pDialog->DestroyWindow();
delete pTemp->pDialog;
delete pTemp;
pTemp = pNextTemp;
}
}
6.啟動向導需要在IDC_BEGINWIZ 按鈕的Click事件中加入下列代碼:
CWizard MyWiz; //顯示向導
MyWiz.DoModal();
四、測試
上述兩個程序在Win2000、VC++ 6.0 下編譯通過。
本文配套源碼