IE瀏覽器的多標簽?zāi)J揭讶遮呎紦?jù)瀏覽器市場的主流模式?;貞汭E6.0時代的單文檔多實例年代,那瀏覽多網(wǎng)頁是何等的痛苦。原本有限的空間就要被那些煩瑣的網(wǎng)頁所占據(jù),要從這些煩瑣的網(wǎng)頁中切換到自己目的網(wǎng)頁更是何等的不便?,F(xiàn)在很多第三方IE瀏覽器對IE瀏覽器所顯示出來的弊病虎視眈眈許久,多標簽瀏覽器也應(yīng)運而生。遨游、世界之窗、TT等如今都是拜多標簽瀏覽器之福,早早占領(lǐng)了市場,占據(jù)了一席之地。而如今微軟也知道自己瀏覽器帝國的根基也岌岌可危,其怎可示弱,IE7.0也就相繼問世。
IE多標簽欄的主要特點是:單實例多文檔模式,文檔間的切換是通過標簽實現(xiàn)。一個實例就可以包容多個文檔,察看網(wǎng)頁是何其的方便。
構(gòu)成IE多標簽欄的界面要素包括工具欄和標簽。工具欄采用CDialogBar做為標簽的容器,標簽采用自繪按鈕來實現(xiàn),CtabCtrl做標簽不容易實現(xiàn)自繪(自繪的時候有灰色的border出現(xiàn))。
在CDialogBar的寬度發(fā)生改變的時候,其上面的按鈕標簽應(yīng)做適當?shù)恼{(diào)整。當然在足夠容納按鈕標簽的時,可以給一個設(shè)定值。若空間有限的話,則就適當縮小標簽的大小,其上的內(nèi)容通過提示框提示。所以只要響應(yīng)CdialogBar的WM_SIZE來調(diào)整標簽的擺放方式。至于標簽欄的自繪通過響應(yīng)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 {
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 {
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();
}當選中標簽后,鼠標移動到標簽右邊的時候,則會出現(xiàn)一個關(guān)閉按鈕,用來關(guān)閉文檔。所以這個里面涉及到按鈕的自繪原理,這樣的文章比較多,這里就不多做說明。不過俺想提個問題,對于按鈕的0nDrawItem與DrawItem,是否有什么區(qū)別,如果將下面換成OnDrawItem消息響應(yīng)函數(shù)是否可以?實在不懂得的話可以去跟蹤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的按鈕命令消息是先發(fā)給主框架窗口處理,如果主框架窗口沒有為其提供相應(yīng)的響應(yīng)函數(shù),則命令的消息路由就中斷,按鈕則會出現(xiàn)disabled狀態(tài)?;蛘吣阋部梢垣@取到這個命令消息后,重載OnCmdMsg再將這個命令消息發(fā)往其他窗口。然后就可以通過按鈕的消息反射機制,使按鈕能夠自己處理自己的事件,比較貼近面向?qū)ο蟮脑O(shè)計。由于命令消息響應(yīng)函數(shù)里面沒有將發(fā)命令消息的對象傳到CtabBar中,這樣CtabBar如果靠按鈕ID來設(shè)置主鍵標志一個標簽按鈕的話,那那就會占用很多的ID。本設(shè)計并沒有這么做,在給ctabbar提供相的相關(guān)通知,是在里面發(fā)送一個通知消息到ctabbar的,其中LPARAM就可以保存按鈕標簽對象。這樣標簽欄就可以實現(xiàn)對底下標簽的操作。以下就是按鈕發(fā)送通知消息給標簽欄的實現(xiàn)細節(jié)。#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;
}總體的設(shè)計思想是:標簽欄應(yīng)該用一個數(shù)組來存放標簽,但由于一個標簽又是跟一個CchildFrame一一對應(yīng)起來,兩者應(yīng)該可以相互訪問,效率比較高的話可以用cmap映射來實現(xiàn)。本設(shè)計采用一個結(jié)構(gòu)體來存儲這兩個關(guān)鍵要素。typedef struct _TABINFO
{
CChildFrame* pChildFrame;
CTabButton* pTabButton;
}TABINFO, *PTABINFO;這個標簽欄就可以通過一個數(shù)組維護這樣的結(jié)構(gòu)。至于CchildFrame和CtabButton的對應(yīng)關(guān)系可以通過下面來實現(xiàn),不過是個輪詢過程,的確很浪費時間。CTabButton*CTabBar::GetTabButtonByChildFrame(CChildFrame* pChildFrame)
{
int nCount = m_ptrArray.GetCount();
for(int i=0; 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 {
TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
if(pTabInfo->pTabButton==pTabButton)
{
return pTabInfo->pChildFrame;
}
else
{
continue;
}
}
return NULL;
}
IE多標簽欄的主要特點是:單實例多文檔模式,文檔間的切換是通過標簽實現(xiàn)。一個實例就可以包容多個文檔,察看網(wǎng)頁是何其的方便。
構(gòu)成IE多標簽欄的界面要素包括工具欄和標簽。工具欄采用CDialogBar做為標簽的容器,標簽采用自繪按鈕來實現(xiàn),CtabCtrl做標簽不容易實現(xiàn)自繪(自繪的時候有灰色的border出現(xiàn))。
在CDialogBar的寬度發(fā)生改變的時候,其上面的按鈕標簽應(yīng)做適當?shù)恼{(diào)整。當然在足夠容納按鈕標簽的時,可以給一個設(shè)定值。若空間有限的話,則就適當縮小標簽的大小,其上的內(nèi)容通過提示框提示。所以只要響應(yīng)CdialogBar的WM_SIZE來調(diào)整標簽的擺放方式。至于標簽欄的自繪通過響應(yīng)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
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
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();
}當選中標簽后,鼠標移動到標簽右邊的時候,則會出現(xiàn)一個關(guān)閉按鈕,用來關(guān)閉文檔。所以這個里面涉及到按鈕的自繪原理,這樣的文章比較多,這里就不多做說明。不過俺想提個問題,對于按鈕的0nDrawItem與DrawItem,是否有什么區(qū)別,如果將下面換成OnDrawItem消息響應(yīng)函數(shù)是否可以?實在不懂得的話可以去跟蹤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的按鈕命令消息是先發(fā)給主框架窗口處理,如果主框架窗口沒有為其提供相應(yīng)的響應(yīng)函數(shù),則命令的消息路由就中斷,按鈕則會出現(xiàn)disabled狀態(tài)?;蛘吣阋部梢垣@取到這個命令消息后,重載OnCmdMsg再將這個命令消息發(fā)往其他窗口。然后就可以通過按鈕的消息反射機制,使按鈕能夠自己處理自己的事件,比較貼近面向?qū)ο蟮脑O(shè)計。由于命令消息響應(yīng)函數(shù)里面沒有將發(fā)命令消息的對象傳到CtabBar中,這樣CtabBar如果靠按鈕ID來設(shè)置主鍵標志一個標簽按鈕的話,那那就會占用很多的ID。本設(shè)計并沒有這么做,在給ctabbar提供相的相關(guān)通知,是在里面發(fā)送一個通知消息到ctabbar的,其中LPARAM就可以保存按鈕標簽對象。這樣標簽欄就可以實現(xiàn)對底下標簽的操作。以下就是按鈕發(fā)送通知消息給標簽欄的實現(xiàn)細節(jié)。#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;
}總體的設(shè)計思想是:標簽欄應(yīng)該用一個數(shù)組來存放標簽,但由于一個標簽又是跟一個CchildFrame一一對應(yīng)起來,兩者應(yīng)該可以相互訪問,效率比較高的話可以用cmap映射來實現(xiàn)。本設(shè)計采用一個結(jié)構(gòu)體來存儲這兩個關(guān)鍵要素。typedef struct _TABINFO
{
CChildFrame* pChildFrame;
CTabButton* pTabButton;
}TABINFO, *PTABINFO;這個標簽欄就可以通過一個數(shù)組維護這樣的結(jié)構(gòu)。至于CchildFrame和CtabButton的對應(yīng)關(guān)系可以通過下面來實現(xiàn),不過是個輪詢過程,的確很浪費時間。CTabButton*CTabBar::GetTabButtonByChildFrame(CChildFrame* pChildFrame)
{
int nCount = m_ptrArray.GetCount();
for(int i=0; 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
TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
if(pTabInfo->pTabButton==pTabButton)
{
return pTabInfo->pChildFrame;
}
else
{
continue;
}
}
return NULL;
}