实现类似Excel和Visual C++里文件夹式样的标签控制(一)
2007-11-01 20:23:03 来源:WEB开发网接下来要做的事情很重要,首先是绘制标签。CFolderTabCtrl::OnPaint在循环中遍历所有标签,对每一个标签调用CFolderTab::Draw来进行绘制处理。这里有两个技巧:一个是必须在最后绘制当前选中的标签(m_iCurItem),以便它看起来重叠在最上面。另一个是要绘制其它标签,必须让其它标签知道自己的位置——也就是定义标签的梯形坐标。这是此标签控制的重点所在。下面就来看看实际代码是怎么做的。
CFolderTabCtrl有一个RecomputeLayout函数,它计算所有标签的位置。只要你改变控制的版面,则必须调用它,如添加或删除某个标签以及修改某个标签的名字(它会影响标签大小)。RecomputeLayout的关键代码如下:
int x = 0;
for (int i=0; i<GetItemCount(); i++) {
CFolderTab* pTab = GetTab(i);
if (pTab) x += pTab->ComputeRgn(dc, x) - CXOFFSET;
}
RecomputeLayout为每一个标签调用CFolderTab的成员函数ComputeRgn。ComputeRgn计算出标签的梯形大小并返回算出的宽度,RecomputeLayout将它与当前x轴坐标相加,然后作为下一个标签的起始x轴坐标进行参数传递,最后减去形状修饰因子CXOFFSET,使得它们看起来有重叠的效果。之所以这么做是因为给定的标签只能决定其大小,不能决定其绝对位置,它需要更多的x轴信息。 一旦ComputeRgn有了x轴坐标,它就可以计算出一个足够大的梯形来容纳标签文本,注意要加一些边空,使文本的显示不会产生混乱。用DT_CALCRECT 调用CDC::DrawText计算文本所占的矩形,然后用结果计算梯形的大小。私有函数GetTrapezoid计算与文本矩形相配的梯形。用象素进行计算确实是一件麻烦和头疼的事情,我不想让你为此也痛苦一番。当CFolderTab::ComputeRgn计算出梯形的坐标,它调用CRgn::CreatePolygonRgn函数强行创建一个多边形区域。
int CFolderTab::ComputeRgn(CDC& dc, int x)
{
CRect& rc = m_rect;
dc.DrawText(m_sText, &rc, DT_CALCRECT);
// tweak rc to add margins
……
CPoint pts[4];
GetTrapezoid(rc, pts);
m_rgn.CreatePolygonRgn(pts, 4, WINDING);
return rc.Width();
}
当标签的区域确定后,绘制这些标签的工作使你又陷入另一个艰难的象素处理环境。CFolderTab::Draw对选中标签(选中和未选中状态)要显示的颜色和字体进行处理。因为标签将梯形数据存储在CRgn中,因此只要调用CDC::FillRgn即可绘制标签。然后用MoveTos 和LineTos以适当的颜色绘制线条。最后调用DrawText绘制文本。
图六选中/未选中的标签状态
图六是程序运行时标签选中和未选中的状态示意图。注意线条有的是黑色,有的是灰色以便呈现3D效果(这不是我的设计,而是从Excel借用的)。选中标签为白色(COLOR_WINDOW)并且顶边没有黑色边线。这样做便于与上面的视窗融于一体。为了简单起见,例子程序没有创建这样的视窗,但是在实际应用中,一般都会象Excel和Visual Studio那样有一个甚至多个视窗与每个选中的标签对应,这在后续文章中会慢慢扩充。本文例子程序里选中标签的另外一个特点是使用了小字体,这是从Visual Studio借鉴过来。说到字体,到底应该使用哪一种呢?这里CFolderTabCtrl默认为Arial,你完全可以改用其它的字体,为此CFolderTabCtrl类中提供了一个改变字体的成员函数CFolderTabCtrl::SetFonts。以上讨论的都是标签的绘制问题, 下面来看看事件及行为。有关标签区域计算的重点和难点问题都已经解决,剩下的问题就简单多了。通过解决这些问题,我总结出一条经验,希望与大家分享:无论你是做控制设计还是系统设计,开始都要问一问自己,“我需要什么样的数据结构才能使问题更容易处理?”然后确定如何创建这些数据结构。通常应该只在一个地方进行难点和重点处理,其它的问题便会容易得多,至少你应该这样努力。 当CFolderTabCtrl获悉OnLButtonDown事件,它调用函数HitTest找出鼠标位于哪一个标签上。HitTest在每一个CFolderTab对象上循环,调用CFolderTab的同名函数CFolderTab::HitTest,这个函数再用已经计算好的的梯形坐标调用CRgn::PtInRegion。CRgn必须在这里做好自己的工作。如果HitTest有返回TRUE,则CFolderTabCtrl::HitTest返回标签的索引,此时OnLButtonDown调用的另一个函数是CFolderTabCtrl::SelectItem,以此选中标签。SelectItem没有什么玄机,它将新的值赋给m_iCurItem并使的值失效,选中新标签后重画。完成SelectItem的调用后,OnLButtonDown创建一个NMFOLDERTAB结构并将信息填写到结构,然后向父窗口发送WM_NOTIF消息。对话框和主应用就是这样把握着所发生的一切。
OK,大功告成,例子程序虽然粗糙,但所要的功能基本都实现了。有许多实用的特性我作了保留,例如,用键盘切换标签(这一点可以在主应用中以加速键的方式实现),以及标签的禁用——即防止用选中某个标签。如果你的程序需要这些特性,那就尽一切办法实现它们吧!重要的是实现你需要的功能,而不要画蛇添足。下一部分的内容我们将尝试把CFolderTabCtrl集成到一个实际的MFC程序当中,使得我们的CFolderTabCtrl不再只是一个纯粹的例子。(待续)
更多精彩
赞助商链接