VC++深入详解(6):MFC对话框(二)

先新建一个MFC工程,然后选择基于对话框的应用程序。我们发现,跟单文档应用程序相比,它只有3个类:
CAboutDlg:与单文档的相同,用来显示帮助。这里其实可以完全不要。


CTestApp:MFC应用程序必不可少的类,它的全局对象theApp代表了应用程序本身


CTestDlg:基于对话框的MFC应用程序的主界面,从CDialog派生出来的。
我们先看一个简单的问题:逃跑按钮的创建。他其实看起是这样一件事件:当你把鼠标移动到某个按钮上时,这个按钮却移动到了另外一个地方,所以你总是点不到它。而它的实现其实是这样的:我们有两个按钮,每当鼠标移动到第一个按钮上时,它就把自己隐藏起来,并且调用函数显示第二个按钮。按照这种思路,我们展开设计。

首先,在哪里捕获鼠标移动的消息?肯定不能是CTestDlg类中,否则以移动到对话框上,就会显示或者隐藏按钮了。我们可以创建一个从CButton派生来的新类CNewButton,使用classWizard来完成;然后将按钮控件与这种新类相关联。在这两个按钮上点击右键,使用类向导添加CNewButton类的变量m_btn1,m_btn2.我们需要在CTestDlg类的头文件中包含:#include "NewButton.h"。下面让CNewButton捕获鼠标移动消息。为这个类添加WM_MOUSEMOVE的消息响应函数,在其中调用ShowWindow,参数选择SW_SHOW来显示一个窗口,SW_HIDE来隐藏一个窗口。但是,我们要隐藏自己时,也得显示另外一个窗口,所以我们需要为我们的CTestDlg添加一个成员变量,这个变量中有一个指向CNewButton的指针,里面保存的是另一个按钮的地址。这个初始化工作在OnInitDialog中添加:

	m_btn1.m_pBtn = &m_btn2;
	m_btn2.m_pBtn = &m_btn1;

然后我们的OnMouseMove就简单了:

void CNewButton::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	ShowWindow(SW_HIDE);
	m_pBtn->ShowWindow(SW_SHOW);
	CButton::OnMouseMove(nFlags, point);
}

为了美观起见,我们把两个按钮中的一个设置为不可见,然后就更像回事了。


下面看如何生成属性表单和向导的创建。


下面讲何如生成属性表单:
插入->资源->属性页,插入3个属性页,将它们的ID分别设为IDD_PROP1、IDD_PROP2、IDD_PROP3,将它们的标签设置为Page1,Page2,Page3.
在Page1中放入一个组框:组框的的作用是把相关的内容放在一起,让用户使用起来方便,将它的标题改为“请选择你的职业:”;然后再组框内放置3个按钮,名字分别设置为:“程序员”、系统工程师、项目经理;然后再组框的旁边放置一个列表框,然后在他上面放置一个静态文本框,标题改为:“请选择你的工作地点”。
在Page2中放置一个组框,名字改为请选择你的兴趣爱好,里面放4个复选框,名字改为“足球”、“篮球”、“排球”、“游泳”。
在Page3中放1个组合框,组合框有3种类型:简易式、下拉式、下拉列表式。我们这里选择下拉列表。然后在它的上面放一个静态文框,将名字改为“请选择你的薪资水平”。
我们有3个资源以后,要为他们生成对应的类使用查看->建立类向导->增加新类,名字分别命名为:CProp1,CProp2,CProp3。确保它们的ID与这个三个资源相对应,并将它们的基类设为CPropertyPage。


有了这些类和资源之后,我们才能创建属性表单:首先创建一个CPropSheet对象,然后为它添加前面的3个类的成员变量m_prop1、m_prop2、m_prop3。注意,这里需要包含它们对应的头文件。然后在CPropSheet的构造函数中调用AddPage函数将这3个属性页增加到属性表中。
最后,我们给菜单增加一个菜单项,并在VIEW类中响应点击这个菜单项的消息。

void CPropView::OnPropertysheet() 
{
	// TODO: Add your command handler code here
	CPropSheet propSheet("微信");
	propSheet.DoModal();
}

编译运行以后,发现上面的汉字是乱码,在属性页把语言改成中国,把字体改为中文的字体就可以了。
总结一下,整个工作分为下面几步:
1.创建属性页。
2.将属性页与对应的类相关联。
3.创建属性表类,为属性表添加与属性页相关的变量,并在构造函数中通过AddPage将属性页添加到属性表中。
4.新增一个菜单项,在响应这个菜单项的消息中,调用属性表的DoModal函数显示模态属性表。


向导的创建
创建向导与创建属性表单类似,但是要在DoModal之前,调用CPropSheet的成员SetWizardMode函数。
调用以后,我们发现默认的表单中,第一页有上一步,最后一页还有下一步,这都是我们不希望的,我们可以通过SetWizardButtons函数来选择每一页上到底有些什么。通过MSDN,我们可以知道,最好是在 CPropertyPage的OnSetActive函数中调用这个函数。而OnSetActive是一个虚函数,在我们的派生类CProp1、CProp2、CProp3中,可以覆盖它们:

BOOL CProp1::OnSetActive() 
{
	// TODO: Add your specialized code here and/or call the base class
	((CPropertySheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT);
	return CPropertyPage::OnSetActive();
}
注意,通过GetParent获取基类指针以后,应将它们转化为CPropertySheet*类型。
下面,我们希望如果用户没有选择向导中的某一项,就不能进行下一步。
首先我们要将这些选项与某个对象相关联,来记录它是否被选中。在单选框上点击右键,选择建立类向导,然后选择成员变量,我们发现这里并没有出现我们单选框的ID号:IDC_RADIO1,这是因为,对于单选框,MFC是增加了一个“group”标记来表示从这个单选框到下一个单选框(不包含)之间的选项只能选一个。我们将第一个单选框的group选项选中,就ok了。我们可以为他关联一个int型成员变量m_occupation。我们看到,在构造函数中,将它初始化为-1,表示并没有选择。当选择第一个选项时,m_occupation就会被设为0,第二个选项时被设为1,依次类推。
所以,当用户点击属性页上的“下一步”按钮时,应该先要判断m_occupation。而获取点击“下一步”的操作,是通过给CProp1添加虚函数OnWizardNext来实现的。而派生类的函数中,还是调用了基类的OnWizardNext函数,这个函数如果返回0,则会自动进入到下一页,如果返回-1,则会停留在该页,所以我们可以这样写:

LRESULT CProp1::OnWizardNext() 
{
	// TODO: Add your specialized code here and/or call the base class
	//获取值的时候需要使用这个函数
	UpdateData();
	if(-1 == m_occupation)
	{
		MessageBox("请选择一个职业!");
		return -1;
	}
	else
		return CPropertyPage::OnWizardNext();
}

下面我们对列表框的内容进行判断。首先,我们要为列表框增加几个选项:

BOOL CProp1::OnInitDialog() 
{
	CPropertyPage::OnInitDialog();
	
	// TODO: Add extra initialization here
	((CListBox*)GetDlgItem(IDC_LIST1))->AddString("北京");
	((CListBox*)GetDlgItem(IDC_LIST1))->AddString("天津");
	((CListBox*)GetDlgItem(IDC_LIST1))->AddString("上海");
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}
与列表框对应的类是CListBox,通过它的AddString函数来增加选项。然后进行判断,同样的,我们需要将它和CString类型的成员变量m_workAddr关联起来:

	if("" == m_workAddr)
	{
		MessageBox("请选择一个工作地点!");
		return -1;	
	}

下面处理第二个界面,我们要为这4个复选框按钮添加4个成员变量m_football、m_basketball、m_volleyball、m_swim,在构造函数中,它们都被初始化为false,所以我们需要判断它们中的一个是否为TRUE,如果是,就能进行下一步了:

LRESULT CProp2::OnWizardNext() 
{
	// TODO: Add your specialized code here and/or call the base class
	UpdateData();
	if(m_basketball | m_football | m_volleyball | m_swim)
	{
	return CPropertyPage::OnWizardNext();
	}
	else
	{
		MessageBox("请选择一个爱好!");
		return -1;
	}
}
下面我们处理第3页。与第1页的列表框相似,与组合框相对应的类是CComboBox,通过AddString函数来添加下拉列表的成员:

BOOL CProp3::OnInitDialog() 
{
	CPropertyPage::OnInitDialog();
	
	// TODO: Add extra initialization here
	((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("1000元以下");
	((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("1000元到2000");
	((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("2000元到3000");
	((CComboBox*)GetDlgItem(IDC_COMBO1))->AddString("3000元以上");


	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

运行程序时,我们发现它显示的顺序与我们希望的不同,这是因为MFC自带了排序功能,我们在这个组合框的属性->样式中,去掉分类项就可以了。
另外,我们希望这个列表框中有默认的选项,这可以通过SetCurSel来实现。

	((CComboBox*)GetDlgItem(IDC_COMBO1))->SetCurSel(0);

因为有了默认选项,我们就不需要判断用户是否选择了。


下面,为我们的选项进行简单的响应:当点击完成时,把选择结果显示在view类中。首先在Prop3中添加响应点击完成的虚函数OnWizardFinish,然后为其添加一个CString类型的成员变量m_strSalary用来承载我们选择的薪资。通过GetCurSel获取用户选择的是哪个选项,通过GetLBText把这个选项的内容存储到m_strSalary中:

BOOL CProp3::OnWizardFinish() 
{
	// TODO: Add your specialized code here and/or call the base class
	int index;
	index = ((CComboBox*)GetDlgItem(IDC_COMBO1))->GetCurSel();
	((CComboBox*)GetDlgItem(IDC_COMBO1))->GetLBText(index,m_strSalary);
	return CPropertyPage::OnWizardFinish();
}

下面正式开始我们的显示工作。在我们的view类中添加变量来保存选择的结果:

	int m_iOccupation;
	CString m_strWorkAddr;
	BOOL m_bLie[4];
	CString m_strSalary;

并在构造函数中初始化它们:

CCH_8_PROPView::CCH_8_PROPView()
{
	// TODO: add construction code here
	m_iOccupation = -1;
	m_strWorkAddr = "";
	memset(m_bLike,0,sizeof(m_bLike));
	m_strSalary = "";
}

有了这些数据,如果用户点击的是“完成”,那么我们就该显示这些数据,如果是“取消”,那么就不显示。那么如何判断用户点击的是什么呢?是通过DoModal函数的返回值判断的:

void CCH_8_PROPView::OnPropertysheet() 
{
	// TODO: Add your command handler code here
	CPropSheet propSHeet("属性表单");
	propSHeet.SetWizardMode();
	if (ID_WIZFINISH  == propSHeet.DoModal())
	{
		m_iOccupation = propSHeet.m_prop1.m_occupation;
		m_strWorkAddr = propSHeet.m_prop1.m_workAddr;
		m_bLike[0] = propSHeet.m_prop2.m_football;
		m_bLike[0] = propSHeet.m_prop2.m_basketball;
		m_bLike[0] = propSHeet.m_prop2.m_volleyball;
		m_bLike[0] = propSHeet.m_prop2.m_swim;
		m_strSalary = propSHeet.m_prop3.m_strSalary;
		Invalidate();
		
	}
}

我们将向导中的选项全部存在view类里的变量里,然后调用Invalidate();是得客户区变为无效,引起重绘。
下来,我们就能够在OnDraw函数中显示这些信息了:

void CCH_8_PROPView::OnDraw(CDC* pDC)
{
	CCH_8_PROPDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
	CString strtmp;
	strtmp = "你的职业:";
	switch(m_iOccupation)
	{
	case 0:
		strtmp += "程序员";
		break;
	case 1:
		strtmp += "系统工程师";
		break;
	case 2:
		strtmp += "项目经理";
		break;
	default:
		break;

	}
	pDC->TextOut(0,0,strtmp);
	strtmp = "你的工作地点:";
	strtmp += m_strWorkAddr;
	TEXTMETRIC tm;
	pDC->GetTextMetrics (&tm);
	pDC->TextOut(0,tm.tmHeight, strtmp);
	strtmp = "你的兴趣爱好:";
	if(m_bLike[0])
		strtmp += "足球";
	if(m_bLike[1])
		strtmp += "篮球";
	if(m_bLike[2])
		strtmp += "排球";
	if(m_bLike[3])
		strtmp += "游泳";
	pDC->TextOut(0,tm.tmHeight*2, strtmp);

	strtmp = "你的薪资水平:";
	strtmp += m_strSalary;
	pDC->TextOut(0,tm.tmHeight*3, strtmp);
}

其中,为了能够在下一行显示,而不会重复显示,我们获取的系统的字体,利用了它的高度信息。

你可能感兴趣的:(windows,exception,工作,mfc,initialization,construction)