基于CDialogBar的IE多標簽欄的實現(xiàn)

字號:

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;
    }