程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 基於CDialogBar的IE多標簽欄的實現

基於CDialogBar的IE多標簽欄的實現

編輯:關於VC++

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。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved