VC实现类似Excel文件夹式样的标签控制 | |||||||
---|---|---|---|---|---|---|---|
http://www.sina.com.cn 2006年01月26日 09:48 天极yesky | |||||||
众所周知,微软的Excel中的工作簿可以有多个工作表单(worksheet),每个表单可以通过左下角的标签控制灵活切换,Visual C++也有类似的控制,如在"Output"窗口中设置有:Build,Debug,Find in Files和Results等标签控制。本例中我们将这种界面称为文件夹式样的标签控制,以下简称标签控制,而将MFC中的Tab Control称为标签控件。那么标签控制是如何实现的呢?MFC中有没有现成的控件可以利用?看了本文以后,读者朋友对这个问题应该能找到一个圆满的答案。 MFC固然给编程带来了极大的方便,但是它并不能代替程序员的编程,MFC只是提供了一个编程框架,应用的实质性代码还是必须由程序员自己来写。同时,MFC的问题也是显而易见的,那就是其GUI素材太丰富,以至于程序员们过分依赖MFC,当想要实现MFC中没有的GUI特性时便不知所措。对于如何实现文件夹式样的标签控制界面,有人可能想到了从现成的标签控件TabControl入手,但是经验证明:如果自己创建一个窗口类,能够让你完全控制代码的修改,不必顾及因现有控件版本的变化而对自己的代码造成的巨大影响和麻烦,微软的开发人员肯定也是这么做,如果用Spy++查看一下Excel和Visual C++的界面,你就会发现其文件夹式样的标签控制并不是TabControl,而是另外创建的窗口类。本实例通过定义一个CFolderTabCtrl实现了我们所要的界面功能。程序运行后的界面效果如图一所示:
一、实现方法 有关CFolderTabCtrl的实现细节请参考源代码。其头文件为Ftab.h,实现文件为Ftab.cpp。在分析CFolderTabCtrl的实现原理之前,让我先来说明一下这个类的使用方法。当FldrTab程序的InitInstance()函数获得控制权时,它创建一个主对话框的实例CMyDialog dlg;,并运行这个对话框。CMyDialog有两个控制:一个是m_wndStaticInfo,另一个是m_wndFolderTab,顾名思义,第一个控制为一个静态文本窗口,它显示选中的标签,第二个是标签控制本身,即CFolderTabCtrl实例。通过在CMyDialog::OnInitDialog()函数中调用SubclassDlgItem()函数,对话框以常规方式子类化静态文本,遗憾的是它不能子类化标签控制,因为对话框中并没有实际的标签控制窗口。在OnDialogInit()函数中通过调用一个特殊的函数m_wndFolderTab.CreateFromStatic(IDC_FOLDERTAB, this),在程序运行时将静态文本替换成标签控制。 CFolderTabCtrl::CreateFromStatic()函数不仅在静态文本控件的位置上创建一个标签控制,它还负责删除静态文本控件。在调用Create()函数之前,CreateFromStatic()调用CFolderTab::GetDesiredHeight()来获得控制的高度,而忽略静态文本控件的高度。在非对话框应用中不能调用CreateFromStatic();而是要直接调用CFolderTab::Create()。创建了标签控制后,接下来必须设置标签名字。这里是在CMyDialog中调用m_wndFolderTab.Load(IDR_FOLDERTABS) 函数来装载字符串资源。其中 IDR_FOLDERTABS是字符串资源的ID,它是一个包含新行指示符("\n")分割的标签名。 仅仅通过上述处理CfolderTabCtrl类还不能做任何事情,你还必须处理它的通知消息,当用户按下一个标签时,CFolderTabCtrl便用特殊代码FTN_TABCHANGED向对话框发送一个WM_NOTIFY消息。然后对话框处理这条消息,也就是在上面的静态文本控件中显示一条信息:
NMFOLDERTAB结构在FTab.h文件中定义:
这个结构除了NMHDR所包含的成员之外,还有项目索引和指向当前标签CFolderTab的指针,它与CFolderTabCtrl有所不同,从CFolderTab中你可以得到标签的文本。以上就是CFolderTabCtrl的使用方法。 下面我们就来揭示这个C++类的实现原理。前面已经对CreateFromStatic()进行了描述,那么CFolderTabCtrl::Load是个什么样的函数呢,这个函数的功能是加载一个串标签名,这个串是用新行指示符("\n")分割的字符串,吸取其中的子串,并调用CFolderTabCtrl::AddItem()将它添加到每一个标签上:
就这么简单,创建一个新的CFolderTab对象并将它添加到一个列表中。与AddItem()相对的是RemoveItem()函数,它们的实现都在Ftab.cpp文件中,这两个函数分别负责动态添加和删除标签页,而不是存取资源串。然后是GetItem()和GetItemCount()函数,一看它们的名字你就应该明白它们的作用,前者用来获取CFolderTab标签的索引号(从0开始),后者则返回m_lsTabs.GetCount,即总共有多少标签。此外,还需要有个函数来获取和设置标签文本,没问题,每一个CFolderTab对象都有一个m_sText成员变量来存储标签名,存取方法是GetText()和SetText()。 接下来要做的事情很重要,首先是绘制标签,CFolderTabCtrl::OnPaint()函数在循环中遍历所有标签,对每一个标签调用CFolderTab::Draw()函数来进行绘制处理。这里有两个技巧:一个是必须在最后绘制当前选中的标签(m_iCurItem),以便它看起来重叠在最上面。另一个是要绘制其它标签,必须让每个标签知道自己的位置--也就是定义标签的梯形坐标。这是此标签控制的重点所在。下面就来看看实际代码是怎么做的。 CFolderTabCtrl有一个RecomputeLayout()函数,它计算所有标签的位置。只要你改变控制的版面,则必须调用它,如添加或删除某个标签以及修改某个标签的名字(它会影响标签大小)等操作。RecomputeLayout()函数的关键代码如下:
RecomputeLayout()为每一个标签调用CfolderTab类的成员函数ComputeRgn()。ComputeRgn()计算出标签的梯形大小并返回算出的宽度,RecomputeLayout()将它与当前x轴坐标相加,然后作为下一个标签的起始x轴坐标进行参数传递,最后减去形状修饰因子CXOFFSET,使得它们看起来有重叠的效果。之所以这么做是因为给定的标签只能决定其大小,不能决定其绝对位置,它需要更多的x轴信息。 一旦ComputeRgn()有了x轴坐标,它就可以计算出一个足够大的梯形来容纳标签文本,注意要加一些边空,使文本的显示不会产生混乱。用DT_CALCRECT 调用CDC::DrawText()来计算文本所占的矩形,然后用结果计算梯形的大小。私有函数GetTrapezoid()计算与文本矩形相配的梯形。当CFolderTab::ComputeRgn()计算出梯形的坐标,它调用CRgn::CreatePolygonRgn函数强行创建一个多边形区域。
|