谈一谈属性页和Tab控件

Windows 专栏收录该内容
28 篇文章 1 订阅

最近给新带的一个小师弟布置了一个小作业:很简单就是有三个Tab页面,每个Tab页面有1个编辑框,在第一个和第二个编辑框界面各输入一个数字,在切换第三个Tab时编辑框内显示前两个Tab页的编辑框输入数字的和。本来只是为了锻炼他对控件的应用,以为很简单,可是小师弟反应不知道如何在将第1、2个Tab的值传递到第三个Tab页面,我想可能新手容易在这里产生困惑,在此记下来供有同样问题的新手学习,同时讲解了用属性页对话框和Tab控件实现同样效果的Tab页面以及他们之间的差别。

首先看一下,要实现的效果

在第1、2个页面输入值



切换到第三个Tab页面时显示前两个Tab页面的输入值得和


属性页实现Tab页面

首先说用属性页的方法来实现这一效果

具体的过程我不会详述,类似的教程网上一搜一大把,无非就是使用VC创建PROPERTY PAGE类型的对话框,每个类型的对话框关联一个CPropertyPage派生类,在主对话框中创建三个类成员变量对应三个CPropertyPage派生对话框类,然后在主对话框中一个函数中创建一个CPropertySheet对象,将之前的三个CPropertyPage对话框成员变量添加到CPropertySheet对象中,然后DoModal弹出Tab页对话框进行操作。

我们给三个Tab页面的编辑框分别关联int变量m_value1,m_value2和m_result

在第1、2个页面的OnKillActive函数中调用UpdateData(TRUE)使界面上的值传到成员变量m_value1和m_value2

这时候就到了我们的问题了,当切换到第3个Tab中OnSetActive中如何获得前两个界面上的值呢

很简单,我们找到包含这三个对话框类的节点,就是主对话框,通过它来得到其他两个页面数据

代码如下

BOOL CDlgProp3::OnSetActive() 
{
	// TODO: Add your specialized code here and/or call the base class
	//计算前两页的和,通过主窗口传递值
	m_result = ((CPropertySheetDlg *)(AfxGetApp()->m_pMainWnd))->m_prop1.m_value1 +
			   ((CPropertySheetDlg *)(AfxGetApp()->m_pMainWnd))->m_prop2.m_value2;

	UpdateData(FALSE);

	return CPropertyPage::OnSetActive();
}
这里,新手们是不是明白了一些呢, 记住找到共同关联起来的节点,通过它来完成彼此的数据传递

有些人会说这里能使用GetParent得到父节点吗?当然是可以的,但是可能你得多套几个GetParent,因为首先在主窗口上弹出了一个子窗口CPropertySheet,在CPropertySheet中又包含了几个CPropertyPage,那么代码是这样的

BOOL CDlgProp3::OnSetActive() 
{
	// TODO: Add your specialized code here and/or call the base class
	//如果是Wizard的话设置按钮
	((CPropertySheet *)GetParent())->SetWizardButtons(PSWIZB_FINISH | PSWIZB_BACK);

	//计算前两页的和,通过主窗口传递值
	m_result = ((CPropertySheetDlg *)(GetParent()->GetParent()))->m_prop1.m_value1 +
			   ((CPropertySheetDlg *)(GetParent()->GetParent()))->m_prop2.m_value2;

	UpdateData(FALSE);

	return CPropertyPage::OnSetActive();
}

对话框如下


还有人会说其实不用成员变量,直接使用界面上来获得就行

BOOL CDlgProp3::OnSetActive() 
{
	// TODO: Add your specialized code here and/or call the base class

	//直接使用界面获得
	m_result = ((CPropertySheet *)GetParent())->GetPage(0)->GetDlgItemInt(IDE_VALUE1) + 
			   ((CPropertySheet *)GetParent())->GetPage(1)->GetDlgItemInt(IDE_VALUE2);

	UpdateData(FALSE);

	return CPropertyPage::OnSetActive();
}
有过你愿意,还有好几种实现方法,恕不赘诉,不过你要明白的就是无论如何找到一个共同的节点(界面的或者是类成员变量)

那么在原来的问题上我又扩展了一下,在Tab窗口点击了确定后会自动在主窗口上将当前的计算结果显示

同样我们是以主窗口为节点来获得数据的,代码如下

void CPropertySheetDlg::OnTabPropsheet() 
{
	// TODO: Add your control notification handler code here
	CPropertySheet tabSheet("Tab属性页");
	tabSheet.AddPage(&m_prop1);
	tabSheet.AddPage(&m_prop2);
	tabSheet.AddPage(&m_prop3);
	
	if(IDOK == tabSheet.DoModal())
	{
		CString csTemp;
		csTemp.Format(TEXT("Tab属性页计算结果为%d+%d=%d"), m_prop1.m_value1, m_prop2.m_value2, m_prop3.m_result);
		GetDlgItem(IDE_INPUT)->SetWindowText(csTemp);
	}
}

这时候你要注意的问题是:当点击了确定以后DoModal函数执行完成,此时窗口是已经销毁了的,但是他们的窗口类还在,之前我们将窗口上的数据保存到了窗口类的成员变量中,所以在窗口销毁了以后我们还能操作窗口数据,很多新手容易混淆窗口类和窗口,记住窗口只是窗口类的一种表现形式


使用属性页的好处在于你可以很方便快捷的将他们转为一个向导,代码如下

void CPropertySheetDlg::OnWizardPropsheet() 
{
	// TODO: Add your control notification handler code here
	CPropertySheet tabSheet("向导属性页");
	tabSheet.AddPage(&m_prop1);
	tabSheet.AddPage(&m_prop2);
	tabSheet.AddPage(&m_prop3);
	
	tabSheet.SetWizardMode();
	if(ID_WIZFINISH == tabSheet.DoModal())
	{
		CString csTemp;
		csTemp.Format(TEXT("向导属性页计算结果为%d+%d=%d"), m_prop1.m_value1, m_prop2.m_value2, m_prop3.m_result);
		GetDlgItem(IDE_INPUT)->SetWindowText(csTemp);
	}
}

对话框如下图

所有的计算不变,唯一的差别是需要设置向导按钮和在调用DoMoal前SetWizardMode

Tab控件实现属性页

那么,可以看到上面实现的属性页多了我们不希望看到的按钮,这当然让人不爽了,也没找到比较好的去除方法(可以用Hack的方法找到窗体句柄并隐藏它)。
在实际应用中很多人选择使用Tab控件来自己写Tab属性页(实际上我们使用属性页的方法归根结底也是MFC帮助我们使用Tab控件完成了Tab属性页)。使用Tab控件完成Tab属性页的流程也不复杂,基本思路就是在Tab控件切换的时候显示对应的窗口隐藏其他窗口,造成一种切换的效果,如果你用JS+HTML写过网页图片切换显示的话应该不会陌生这种方法。

截取核心代码如下

BOOL CDlgTabControl::OnInitDialog() 
{
	CDialog::OnInitDialog();

	//添加Tab项
	m_tabSelect.InsertItem(0, TEXT("第一个值"));
	m_tabSelect.InsertItem(1, TEXT("第二个值"));
	m_tabSelect.InsertItem(2, TEXT("结果"));
	m_tabSelect.SetCurSel(0);

	//创建所有下层窗口
	DownDlgCreate();
	DownDlgSelect(0);
	
	// TODO: Add extra initialization here
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CDlgTabControl::DownDlgCreate()
{
	m_pProp1 = new CDlgTabControl1;
	ASSERT(m_pProp1);
    m_pProp1->Create(IDD_TABCONTROL1, GetDlgItem(IDS_DLG_CONTAINER));
	
	m_pProp2 = new CDlgTabControl2;
	ASSERT(m_pProp2);
    m_pProp2->Create(IDD_TABCONTROL2, GetDlgItem(IDS_DLG_CONTAINER));

	m_pProp3 = new CDlgTabControl3;
	ASSERT(m_pProp3);
    m_pProp3->Create(IDD_TABCONTROL3, GetDlgItem(IDS_DLG_CONTAINER));
}

void CDlgTabControl::DownDlgSelect( UINT uiIndex )
{
	//选择显示窗口
	m_pProp1->ShowWindow(0 == uiIndex);
	m_pProp2->ShowWindow(1 == uiIndex);
	m_pProp3->ShowWindow(2 == uiIndex);
}

void CDlgTabControl::OnSelchangeTabSelect(NMHDR* pNMHDR, LRESULT* pResult) 
{
	// TODO: Add your control notification handler code here
	// TODO: Add your control notification handler code here
	//选择显示相应窗口
	DownDlgSelect(m_tabSelect.GetCurSel());

	//在这里处理所有数据相加
	if (2 == m_tabSelect.GetCurSel())
	{
		m_pProp1->UpdateData(TRUE);
		m_pProp2->UpdateData(TRUE);

		m_pProp3->m_result = m_pProp1->m_value1 + m_pProp2->m_value2;

		m_pProp3->UpdateData(FALSE);
	}
	
	*pResult = 0;
}
同样我们在三个子平行窗口类的节点处理数据,在切换到第三个Tab页面时获得前两个窗口的数据并计算显示。这里为了在窗口销毁后还能访问到每个子窗口的数据,调用了UpdateData函数将界面输入传到成员变量中。

那么基本上要说的就说完了,其实说来说去就是一个核心: 找到子平行类的节点同时注意窗口类和窗口的差别

最后再啰嗦一句属性页和Tab控件的不同:可以看到属性页构建Tab属性页要方便那么一点点,同时很容易转换成向导模式,但是很烦人的是多了我们不想要的按钮,使用Tab控件更加灵活,你可以选择在一个窗体上放自己想要的按钮或将它嵌入到其他窗口中。至于如何选择,就看你自己了。


本文全部演示代码下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

  • 2
    点赞
  • 3
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 3 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:撸撸猫 设计师:马嘣嘣 返回首页

打赏作者

文大侠

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值