Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9495084
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 21:40:25

实现类似Excel和Visual C 里文件夹式样的标签控制(二)
——实例应用

编译/



第一部分我们创建了一个类CFolderTabCtrl,用这个类实现了类似Excel和Visual C 应用中文件夹式样的标签控制。在阅读本文之前,最好先看上一篇文章及其例子代码FldrTab。FldrTab纯粹是一个测试CFolderTabCtrl类的例子,没有什么实质性的用途。为了真正模仿出Excel和Visual C 的文件夹式样标签控制的效果,本文拟将CFolderTabCtrl应用到一个实际的MFC程序中。并且标签的旁边也象Excel一样有水平滚动条,如图一所示:


图一 标签和滚动条

本文的例子原来是一个显示位图(bitmap)文件及其BITMAPINFOHEADER结构信息的程序。图像及其BITMAPINFOHEADER结构信息都是显示在同一个视图画面里。如图二所示:


图二 图像和信息在同一画面

为了将CFolderTabCtrl标签控制类引入到这个程序,我们创建了两个新类,CFolderFrame和CFolderView。此外,我们还要对原来的程序进行改进,使它能够在不同的标签页里分别显示图像和BITMAPINFOHEADER结构信息。如图三和图四:


图三 显示位图图像


图四 显示位图文件格式头结构信息

另外标签控制页中还有一个用于显示原始图像十六进制数据的Hex标签,不过这是一个虚设的标签,我并没有实现它,如果哪位朋友有兴趣,可以自己去完成,做好后别忘了把源代码也给我一份哦!
从个人的角度来讲,我很怀疑用这样的方法来改进程序的可行性,因为我觉得将信息显示在一个画面中会更直观。但是,本文的目的是示范,并不指望拿它去获得UI设计的奖项,仅仅是用它来作为例子,示范如何同时实现CFolderCtrlTab标签控制和滚动条控制,仅此而已。
此外,要记住一点,每当你要修改或增强MFC应用程序框架特性时,尽量少的触及MFC框架本身。模仿它而不要去破坏它。以此为原则,我采取的策略是在框架和视图之间插入新窗口。如图五所示:


图五 插入的新框架与主框架的关系

上图说明了基本的框架关系。主框架(或MDI子框架)包含作为子窗口的CFolderFrame,然后CFolderFrame包含水平滚动条和标签控制并管理它们之间的交互。
CFolderFrame的使用很简单,自己要做的事情并不多。首先必须改写主框架的OnCreateClient函数以便创建CFolderFrame表示的窗口。

   BOOL CMainFrame::OnCreateClient(..., 

     CCreateContext* pcc)

   {

     return m_wndFolderFrame.Create(this,

       RUNTIME_CLASS(CDIBView), pcc,

       IDR_FOLDERTABS);

   }      
因为原来的位图显示程序同时支持SDI和MDI版本。对于MDI的情况,你要在MDI子框架中改写OnCreateClient,而OnCreateClient函数通常是MFC创建视图的地方,如今应该在此创建CFolderFrame子框架。不要调用基类的OnCreateClient!然后,CFolderFrame子框架用运行时类创建一个视图和你要传递的上下文信息。IDR_FOLDERTABS是串资源的ID,用它存储标签名。如果你想指定动态的标签名,可以省略这个参数并调用CFolderFrame::GetFolderTabCtrl来获得标签控制,然后用CFolderTabCtrl::AddItem添加标签页。有关细节请参考上一篇文章和附带的源代码。
接下来,你必须自己添加一些视图代码。其中最重要的事情是从CFolderView派生自己的视类,而不是从CScrollView类派生。因为要让标签控制和滚动条协调操作,所以这是问题的重点所在。如果你不使用滚动视图,那就不存在这个问题——你可以将标签控制创建成一个子视图。一旦你从CFolderView派生了自己的视类,那么便可以处理来自标签控制的通知消息。为此改写虚拟函数CFolderView::OnChangedFolder即可。当用户点击新的标签时,CFolderView便调用这个函数。这个函数的实现细节很简单,主要负责存储新标签页并重画视图:
 void CDIBView::OnChangedFolder(int iPage)

 {

   m_iPage = iPage;

   UpdateScrollSizes();

   Invalidate();

 }      
记住不要忘了修改视图的OnDraw函数,让它绘制正确的标签页,改进后的程序需要在标签页之间来回切换,m_iPage表示页索引,它的值分别为0,1,2,三个标签页分别用来绘制图像、显示BITMAPINFOHEADER结构信息和显示十六进制数据。最后,你必须在CDIBView::OnInitialUpdate中加一行显示CFolderFrame框架控制的代码:
 // 在 CDIBView::OnInitialUpdate 函数中

 GetFolderFrame()->ShowControls(pDIB ? CFolderFrame::bestFit : CFolderFrame::hide);      
CFolderFrame::ShowControls可以让你隐藏和显示标签控制和滚动条。这样当程序为SDI并且启动空框架时——也就是说没有文档/视。这时程序中的pDIB==NULL,CDIBView::OnInitialUpdate传递CFolderFrame::hide来隐藏控制;否则传递CFolderFrame::bestFit来指示CFolderFrame根据需要的宽度显示所有标签,然后用剩下的宽度显示滚动条。如果你想用其它的算法也未尝不可,你可以计算宽度,然后用这个调用CFolderFrame::ShowControls。
综上所述,你必须在主框架的OnCreateClient中创建CFolderFrame,此外还必需从CFolderView派生自己的视类,并按照前面所讲的方法进行必要的修改。这样不用费太多的周折就可以将标签控制应用到MFC程序框架中。
下面就让我们到幕后看一看CFolderFrame和CFolderView时如何运作的。CFolderFrame 中有保存滚动条和标签控制宽度的成员数据。当你调用CFolderFrame::Create的时候,它首先创建CFolderFrame框架,然后创建视图。它象MFC所做的那样用上下文信息来创建视图。但是要注意CFolderFrame创建的是自身的子视图,不是主框架(或者MDI子框架)。对于框架中的标签控制和水平/垂直滚动条以及调整大小的机关,都由CFolderFrame在OnCreate来创建,实现起来也不难,细节请参见源代码。
在代码中有一个小地方虽然不起眼,但是它很重要,就是将CFolderFrame框架的式样设置成WS_EX_CLIENTEDGE,以便使你的窗口保持与Windows 9x及Windows NT一致的凹陷效果。CFolderFrame在PreCreateWindow中做这件事。
一旦CFolderFrame创建了属于自己的窗口,你就必须管理它们的大小。通常这是OnSize的事情。具体细节纯粹是一些算法问题,我就不再这里赘言了,请自己参考源代码。
CFolderFrame从m_cxFolderTabCtrl获取标签的宽度。用SetFolderTabWidth函数来设置宽度,但一般情况下你不必使用这个函数,因为CFolderFrame总是试图用CFolderTabCtrl::GetDesiredWidth返回理想的宽度。只要OnSize工作正常,你就能看到CFolderFrame是如何仿真文件夹式样标签控制的,就像图四所示的那样,效果很好,选中的标签上边缘白色边缘与其上视图的背景无缝连接,尽管它们是两个不同的窗口,但看不出痕迹。
这一切好象挺简单,不过我还有最精彩的一部分没有讲到,那就是滚动条如何工作?派生的视图如何让MFC在CFolderFrame中操作滚动条,而不是象MFC所想象的那样使用滚动条?当我刚开始实现单独的滚动条时——我预料到会碰上很头疼的问题,因为原来的位图显示程序中CDIBView2视类是从CScrollView派生的,它是以子窗口方式隐藏或显示滚动条。但我此时想让视图使用自己的滚动条,一个在CFolderFrame中的滚动条——至少水平滚动条是这样。如何改变它的大小,使它与标签控制共处一室呢?于是我开始钻研CScrollView的内部运作机制,看看能否找到解决问题的办法。最后我发现只需要改写一下虚函数CWnd::GetScrollBarCtrl就能搞掂。这个函数有一个参数,其取值要么是SB_HORZ,要么是SB_VERT,最后返回相应的滚动条窗口。缺省实现返回Null。MFC走了一条很长的逻辑链来创建它需要的滚动条(通过调用::ShowScrollBar)。而CFolderView对GetScrollBarCtrl的改写很简单:
   CScrollBar* CFolderView::GetScrollBarCtrl(int nBar) const

   {return GetFolderFrame()->GetScrollBar(nBar);}      
它将控制传递到CFolderFrame:
   CScrollBar* CFolderFrame::GetScrollBar (int nBar)

   {

     return nBar==SB_HORZ ? &m_wndSBHorz

       : nBar==SB_VERT ? &m_wndSBVert : NULL;

   }      
这比预料的要简单多了!MFC设计的灵活性真是令人吃惊。要想使用自己创建的与CScrollView一致的滚动条,仅仅改写GetScrollBarCtrl就可以了。例如,你可以发明有一个有迷幻色彩的超级滚动条……哈哈,帅呆了。
标签控制还有一个地方要说明:因为滚动条是CFolderFrame框架的一个子窗口,而不是视图的子窗口,CFolderFrame传递滚动消息(WM_HSCROLL 和 WM_VSCROLL),所以还必须自己编写代码将它们转发到视图:
   void CFolderFrame::OnHScroll(...)

   {

       GetView()->SendMessage(WM_HSCROLL, ...);

   }      
对于WM_VSCROLL消息也同样如法炮制。一旦你解决的这些鸡毛蒜皮似的问题,滚动条的运行便OK了,它工作起来就像CScrollView一样。
其实,GetScrollBarCtrl是CFolderView存在一个主要理由,虽然CFolderView也将通知消息FTN_TABCHANGED从标签控制转换成轻松的OnChangedFolder虚函数调用,这使得通知消息的处理也变成了仅仅在视图中改写虚函数这么简单。
好了,看了这篇文章,你肯定觉得CFolderFrame和CFolderView并没有实现每一个想要的有关标签的UI特性,例如,没有提供左右以及开始/结束按钮,就像Excel界面那样(如图一所示)——说得没错,但是CFolderFrame和CFolderView已经提供了基本的框架,你可以在此基础上添加想要的特性。把按钮作为标签控制的附加控制添加进去,并在上下文的逻辑链中传递BN_CLICKED即可。总之,没有做不到的,只有想不到的,你可以对自己的程序进行所随心所欲的控制。下一部分我们将对标签页的数量进行扩展,并加上移动控制。(待续)
阅读(298) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~