Chinaunix首页 | 论坛 | 博客
  • 博客访问: 530124
  • 博文数量: 576
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5020
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(576)

文章存档

2011年(1)

2008年(575)

我的朋友

分类:

2008-10-14 14:51:37

用VC++创建自定义向导程序
作者: 网站:



向导是一种用来简化用户操作的程序。在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 下编译通过。
--------------------next---------------------

非常感谢.
有几个问题请教:
我的Wizard要根据前一页来决定后边显示哪儿一页..
我的每一页上面的的操作生效不想单独加按钮,而想在每一页的点击下一页按钮事件中实现...

有什么好的解决方案没? 我一开始在OnShowWindow中来实现,但总觉得不太好. ( zhz 发表于 2006-4-29 16:57:00)
 
非常感谢! ( zhwang_2 发表于 2003-7-2 17:11:00)
 
原来的问题是创建的是CDialog,消息由CDialog的窗口过程来处理了,派生类的中的消息无法处理所以响应不了。另外修改了内存泄漏。 ( wangjun 发表于 2003-4-28 20:04:00)
 
本文已修正!
谢谢runner111! ( wangjun 发表于 2003-4-28 19:59:00)
 
不要加delete pStep1和delete pStep2 不然会出错的 ( runner111 发表于 2003-4-27 15:38:00)
 
原作者的这个自定义对话框有缺陷!他虽然能新建对话框,但是原作者反了一个错误------他在struct 中声明了个CDialog指针对象,但是他在AddPage 中用new CDialog对对象进行付值。然后又用Create 创建。看起来好像没错,运行也能成功,但是你会发现Step对话框是无法响应消息的!为什么呢?因为vc虽然根据你给的资源号创建了对话框,但是他不知道这个对话框和什么cpp关联!所以无法响应消息。我做了个小小的改动:
void CWizardDlg::AddPage(CDialog* pDialog,UINT nID)
{
struct PAGELINK* pTemp=pPageLink;
struct PAGELINK* pNewLink=new PAGELINK;
pNewLink->pDialog=pDialog;
pNewLink->pDialog->Create(nID,this);
......//以下和源代码相同
然后在CWizardDlg::OnInitDialog() 中这么写:
CStep1* pStep1=new CStep1;
CStep2* pStep2=new CStep2;
this->AddPage(pStep1,IDD_DIALOG_STEP1);
this->AddPage(pStep2,IDD_DIALOG_STEP2);
this->ShowPage(1);
delete pStep1;
delete pStep2;
就可以使用了。再次感谢原作者的辛勤劳动 ( runner111 发表于 2003-4-27 15:20:00)
 
加的方法如下:
if (pTemp->nNum==nPos)
{
pTemp->pDialog->ShowWindow(SW_SHOW);
pTemp->pDialog->EnableWindow(TRUE);
} ( scienceMode 发表于 2003-4-18 13:49:00)
 
可是我加了EnableWindow(true)还是没有用那该怎么办? ( scienceMode 发表于 2003-4-18 13:48:00)
 
Release空白的原因是ASSERT宏造成,这是个Debug下调试用的,去掉或改成VERIFY

不能被激活可在ShowWindow(SW_SHOW)的后面加上EnableWindow(true);即可。 ( AntGhazi 发表于 2003-1-8 8:33:00)
 
那个向导程序中的其中一个程序在调试时,向导页显示正常,而在"Release"方式编译时,向导页内容一片空白! ( 高守 发表于 2002-7-24 15:08:00)
 
.......................................................

--------------------next---------------------

阅读(237) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~