示例工程下载:http://download.csdn.net/user/kissyfish
IE浏览器的多标签模式已日趋占据浏览器市场的主流模式,回想IE6.0的单文档多实例年代,那时候浏览多网页是非常痛苦的事情。原本有限的空间就要被那些烦琐的网页所占据,要从这些烦琐的网页中切换到自己要查看的网页更是要浪费很多时间。现在许多第三方IE浏览器针对IE浏览器所显示出来的漏洞进行修补以后,多标签浏览器也应运而生。如今遨游、世界之窗、TT等都是在多标签浏览器的基础之上,占领软件市场的。微软也不该示弱,IE7.0也就相继问世。
IE多标签栏的主要特点是:单实例多文档模式,文档间的切换是通过标签实现。一个实例就可以包容多个文档,查看网页是何其的方便。
构成IE多标签栏的界面要素包括工具栏和标签。工具栏采用CDialogBar做为标签的容器,标签采用自绘按钮来实现,CTabCtrl做标签不容易实现自绘(自绘的时候有灰色的border出现)。
在CDialogBar的宽度发生改变的时候,其上面的按钮标签应做适当的调整。当然在足够容纳按钮标签的时,可以给一个设定值。若空间有限的话,则就适当缩小标签的大小,其上的内容通过提示框提示。所以只要响应CDialogBar的WM_SIZE来调整标签的摆放方式。至于标签栏的自绘通过响应WM_PAINT消息就可以做到。
- void CTabBar::OnSize(UINT nType, int cx, int cy)
- {
- CDialogBar::OnSize(nType, cx, cy);
- CRect rcClient;
- GetClientRect(rcClient);
- int nBarWidth = rcClient.Width();
- int nTabWidth = nBarWidth-120;
- int nCount = m_ptrArray.GetCount();
- if(nCount*m_nWidth>nTabWidth)
- {
- //平均分配位置
- int nAveWidth = nTabWidth*1.0/nCount;
- for(int i=0; i<nCount; i++)
- {
- CRect rcBtn;
- TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
- pTabInfo->pTabButton->GetClientRect(&rcBtn);
- pTabInfo->pTabButton->MoveWindow(nAveWidth*i, (rcClient.Height()-rcBtn.Height())/2, nAveWidth, m_nHeight, FALSE);
- }
- }
- else
- {
- //固定大小
- for(int i=0; i<nCount; i++)
- {
- CRect rcBtn;
- TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
- pTabInfo->pTabButton->GetClientRect(&rcBtn);
- pTabInfo->pTabButton->MoveWindow(m_nWidth*i, (rcClient.Height()-rcBtn.Height())/2, m_nWidth, m_nHeight, FALSE);
- }
- }
- Invalidate();
- }
当选中标签后,鼠标移动到标签右边的时候,则会出现一个关闭按钮,用来关闭文档。所以这个里面涉及到按钮的自绘原理,这样的文章比较多,这里就不多做说明。不过俺想提个问题,对于按钮的0nDrawItem与DrawItem,是否有什么区别,如果将下面换成OnDrawItem消息响应函数是否可以?实在不懂得的话可以去跟踪CButton的源代码,就知道了。
- void CTabButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
- {
- ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);
- CRect rcItem = lpDrawItemStruct->rcItem;
- HWND hWnd = lpDrawItemStruct->hwndItem;
- UINT nState = lpDrawItemStruct->itemState;
- HDC hDC = lpDrawItemStruct->hDC;
- CDC dc;
- dc.Attach(hDC);
- dc.SetBkMode(TRANSPARENT);
- CString strTitle;
- GetWindowText(strTitle);
- if (m_bSel)
- {
- if(!m_bmpBmpBkgnd.IsNull())
- {
- m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(0,0,10, m_bmpBmpBkgnd.GetHeight()),
- CRect(m_bmpBmpBkgnd.GetWidth()/4*2, 0, m_bmpBmpBkgnd.GetWidth()/4*2+10, m_bmpBmpBkgnd.GetHeight()));
- m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(4,0,rcItem.right-10, m_bmpBmpBkgnd.GetHeight()),
- CRect(m_bmpBmpBkgnd.GetWidth()/4*2+10, 0, m_bmpBmpBkgnd.GetWidth()/4*3-10, m_bmpBmpBkgnd.GetHeight()));
- m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(rcItem.right-10,0,rcItem.right, m_bmpBmpBkgnd.GetHeight()),
- CRect(m_bmpBmpBkgnd.GetWidth()/4*3-10, 0, m_bmpBmpBkgnd.GetWidth()/4*3, m_bmpBmpBkgnd.GetHeight()));
- }
- if(!m_bmpIcon.IsNull())
- {
- m_bmpIcon.Draw(dc.m_hDC, CRect(4, 4, 4+m_bmpIcon.GetWidth(), 4+m_bmpIcon.GetHeight()),
- CRect(0,0,m_bmpIcon.GetWidth(), m_bmpIcon.GetHeight()));
- }
- CRect rcLabel;
- rcLabel.left = rcItem.left+4*2+16;
- rcLabel.top = rcItem.top;
- rcLabel.right = rcItem.right - (4*2+16);
- rcLabel.bottom = rcItem.bottom;
- dc.SetTextColor(RGB(0xff,0xff,0xff));
- dc.DrawText(strTitle, &rcLabel, DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS);
- if(m_bOverCloseButton)
- {
- if(!m_bmpClose.IsNull())
- {
- m_bmpClose.Draw(dc.m_hDC, CRect(rcItem.Width()-m_bmpClose.GetWidth()/3-5,(rcItem.Height()-m_bmpClose.GetHeight())/2,rcItem.Width()-2, (rcItem.Height()-m_bmpClose.GetHeight())/2+m_bmpClose.GetHeight()),
- CRect(m_bmpClose.GetWidth()/3, 0, m_bmpClose.GetWidth()/3*2, m_bmpClose.GetHeight()));
- }
- }
- else
- {
- if(!m_bmpClose.IsNull())
- {
- m_bmpClose.Draw(dc.m_hDC, CRect(rcItem.Width()-m_bmpClose.GetWidth()/3-5,(rcItem.Height()-m_bmpClose.GetHeight())/2,rcItem.Width()-2, (rcItem.Height()-m_bmpClose.GetHeight())/2+m_bmpClose.GetHeight()),
- CRect(0, 0, m_bmpClose.GetWidth()/3, m_bmpClose.GetHeight()));
- }
- }
- }
- else
- {
- if(!m_bmpBmpBkgnd.IsNull())
- {
- m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(0,0,10, m_bmpBmpBkgnd.GetHeight()),
- CRect(0, 0, 10, m_bmpBmpBkgnd.GetHeight()));
- m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(10,0,rcItem.right-10, m_bmpBmpBkgnd.GetHeight()),
- CRect(10, 0, m_bmpBmpBkgnd.GetWidth()/4-10, m_bmpBmpBkgnd.GetHeight()));
- m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(rcItem.right-10,0,rcItem.right, m_bmpBmpBkgnd.GetHeight()),
- CRect(m_bmpBmpBkgnd.GetWidth()/4-10, 0, m_bmpBmpBkgnd.GetWidth()/4, m_bmpBmpBkgnd.GetHeight()));
- }
- if(!m_bmpIcon.IsNull())
- {
- m_bmpIcon.Draw(dc.m_hDC, CRect(4, 4, 4+m_bmpIcon.GetWidth(), 4+m_bmpIcon.GetHeight()),
- CRect(0,0,m_bmpIcon.GetWidth(), m_bmpIcon.GetHeight()));
- }
- CRect rcLabel;
- rcLabel.left = rcItem.left + 4*2+16;
- rcLabel.top = rcItem.top;
- rcLabel.right = rcItem.right - (4*2+16);
- rcLabel.bottom = rcItem.bottom;
- dc.SetTextColor(RGB(0xff,0xff,0xff));
- dc.DrawText(strTitle, &rcLabel, DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS);
- }
- dc.Detach();
- }
值得注意的是,CdialogBar的按钮命令消息是先发给主框架窗口处理,如果主框架窗口没有为其提供相应的响应函数,则命令的消息路由就中断,按钮则会出现disabled状态。或者你也可以获取到这个命令消息后,重载OnCmdMsg再将这个命令消息发往其他窗口。然后就可以通过按钮的消息反射机制,使按钮能够自己处理自己的事件,比较贴近面向对象的设计。由于命令消息响应函数里面没有将发命令消息的对象传到CtabBar中,这样CtabBar如果靠按钮ID来设置主键唯一标志一个标签按钮的话,那那就会占用很多的ID。本设计并没有这么做,在给ctabbar提供相的相关通知,是在里面发送一个通知消息到ctabbar的,其中LPARAM就可以保存按钮标签对象。这样标签栏就可以实现对底下标签的操作。以下就是按钮发送通知消息给标签栏的实现细节。
- #define NM_TBSEL WM_USER+1
- void CTabButton::OnBnClicked()
- {
- CTabBar* pTabBar = (CTabBar*)GetParent();
- pTabBar->SendMessage(NM_TBSEL, 0, (LPARAM)this);
- }
- HRESULT CTabBar::OnNmTbSel(WPARAM wParam, LPARAM lParam)
- {
- CTabButton* pTabButton = (CTabButton*)lParam;
- SetCurSel(pTabButton);
- CChildFrame* pChildFrame = GetChildFrameByTabButton(pTabButton);
- pChildFrame->MDIActivate();
- pChildFrame->MDIMaximize();
- return TRUE;
- }
总体的设计思想是:标签栏应该用一个数组来存放标签,但由于一个标签又是跟一个CchildFrame一一对应起来,两者应该可以相互访问,效率比较高的话可以用cmap映射来实现。本设计采用一个结构体来存储这两个关键要素。
- typedef struct _TABINFO
- {
- CChildFrame* pChildFrame;
- CTabButton* pTabButton;
- }TABINFO, *PTABINFO;
这个标签栏就可以通过一个数组维护这样的结构。至于CchildFrame和CtabButton的对应关系可以通过下面来实现,不过是个轮询过程,的确很浪费时间。
- CTabButton* CTabBar::GetTabButtonByChildFrame(CChildFrame* pChildFrame)
- {
- int nCount = m_ptrArray.GetCount();
- for(int i=0; i<nCount; i++)
- {
- TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
- if(pTabInfo->pChildFrame==pChildFrame)
- {
- return pTabInfo->pTabButton;
- }
- else
- {
- continue;
- }
- }
- return NULL;
- }
- CChildFrame* CTabBar::GetChildFrameByTabButton(CTabButton* pTabButton)
- {
- int nCount = m_ptrArray.GetCount();
- for(int i=0; i<nCount; i++)
- {
- TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
- if(pTabInfo->pTabButton==pTabButton)
- {
- return pTabInfo->pChildFrame;
- }
- else
- {
- continue;
- }
- }
- return NULL;
- }
以下是我实现的浏览器的一个截图,并附有一个vs2003的demo,具体细节可以察看这个demo。