Splitter2

一、關於CSplitterWnd類

  我們在使用CuteFtp或者NetAnt等工具的時候,一般都會被其複雜的界面所吸引,在這些界面中
窗口被分割爲若干的區域,真正做到了窗口的任意分割。 那麼我們自己如何創建類似的界面,也實現
窗口的任意的分割呢 ?在VC6.0中這就需要使用到CSplitterWnd類。CSplitterWnd看上去像是一種
特殊的框架窗口,每個窗口都被相同的或者不同的視圖所填充。當窗口被切分後用戶可以使用鼠標移動
切分條來調整窗口的相對尺寸。雖然VC6.0支持從AppWizard中創建分割窗口,但是自動加入的分割條
總是不能讓我們滿意,因此我們還是通過手工增加代碼來熟悉這個類。

  CSplitterWnd的構造函數主要包括下面三個。


BOOL Create(CWnd* pParentWnd,int nMaxRows,int nMaxCols,SIZE sizeMin,
CCreateContext* pContext,DWORD dwStyle,UINT nID);
  功能描述:該函數用來創建動態切分窗口。 參數含義:pParentWnd 切分窗口的父框架窗口。
     nMaxRows,nMaxCols是創建的最大的列數和行數。 sizeMin是窗格的現實大小。 pContext 大多數
     情況下傳給父窗口。 nID是字窗口的ID號.
BOOL CreateStatic(CWnd* pParentWnd,int nRows,int nCols,DWORD dwStyle,UINT nID)
  功能描述:用來創建切分窗口。 參數含義同上。
BOOL CreateView (int row,int col,CruntimeClass* pViewClass,SIZE sizeinit,
CcreateContext* pContext);
  功能描述:爲靜態切分的窗口的網格填充視圖。在將視圖於切分窗口聯繫在一起的時候必 須先將切分
     窗口創建好。

  參數含義:同上。

  從CSplitterWnd源程序可以看出不管是使用動態創建Create還是使用靜態創建CreateStatic,在函
     數中都調用了一個保護函數CreateCommon,從下面的CreateCommon函數中的關鍵代碼可以看出創建
     CSplitterWnd的實質是創建了一系列的MDI子窗口。

DWORD dwCreateStyle = dwStyle & ~(WS_HSCROLL|WS_VSCROLL);
if (afxData.bWin4)
     dwCreateStyle &= ~WS_BORDER; //create with the same wnd-class as MDI-Frame (no erase bkgnd)
if (!CreateEx(0, _afxWndMDIFrame, NULL, dwCreateStyle,
           0, 0, 0, 0,pParentWnd->m_hWnd, (HMENU)nID, NULL))
        return FALSE; // create invisible
          
  二、創建嵌套分割窗口

  2.1創建動態分割窗口

  動態分割窗口使用Create方法。下面的代碼將創建2x2的窗格。

m_wndSplitter.Create(this,2,2,CSize(100,100),pContext);
  但是動態創建的分割窗口的窗格數目不能超過2x2,而且對於所有的窗格,都必須共享同一個視圖,所受
     的限制也比較多,因此我們不將動態創建作爲重點。我們的主要精力放在靜態分割窗口的創建上。

  2.2創建靜態分割窗口

  與動態創建相比,靜態創建的代碼要簡單許多,而且可以最多創建16x16的窗格。不同的窗格我們可以使
     用CreateView填充不同的視圖。

  在這裏我們將創建CuteFtp的窗口分割。CuteFtp的分割情況如下:


CCuteFTPView
CView2 CView3
CView4

  創建步驟:

  ▲ 在創建之前我們必須先用AppWizard生成單文檔CuteFTP,生成的視類爲 CCuteFTPView.同時在增加
     三個視類或者從視類繼承而來的派生類CView2,CView3 CView4.

  ▲ 增加成員:

  在Cmainfrm.h中我們將增加下面的代碼:

CSplitterWnd wndSplitter1;
CSplitterWnd wndSplitter2;

  ▲ 重載CMainFrame::OnCreateClient()函數:
BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT   /*lpcs*/, CCreateContext* pContext)
{ //創建一個靜態分欄窗口,分爲三行一列
      if(m_wndSplitter1.CreateStatic(this,3,1)==NULL)
               return FALSE;
   //將CCuteFTPView連接到0行0列窗格上
      m_wndSplitter1.CreateView(0,0,RUNTIME_CLASS(CCuteFTPView),CSize(100,100), pContext);
      m_wndSplitter1.CreateView(2,0,RUNTIME_CLASS(CView4),CSize(100,100),pContext);
   //將CView4連接到0行2列
      if(m_wndSplitter2.CreateStatic(&m_wndSplitter,1,2,WS_CHILD|WS_VISIBLE,
           m_wndSplitter.IdFromRowCol(1, 0))==NULL)
                return FALSE; //將第1行0列再分開1行2列
   //將CView2類連接到第二個分欄對象的0行0列
           m_wndSplitter2.CreateView(0,0,RUNTIME_CLASS(CView2),CSize(400,300),pContext);
   //將CView3類連接到第二個分欄對象的0行1列
           m_wndSplitter2.CreateView(0,1,RUNTIME_CLASS(CView3),CSize(400,300),pContext);
                return TRUE;
}
  2.3實現各個分割區域的通信

  ■有文檔相連的視圖之間的通信

  由AppWizard生成的CCuteFTPView是與文檔相連的,同時我們也讓CView2與文檔相連,因此我們需要修改
     CCuteFTPApp的InitInstance()函數,我們將增加下面的部分。

AddDocTemplate (new CMultiDocTemplate(IDR_VIEW2TYPE,
          
           RUNTIME_CLASS(CMainDoc),
           RUNTIME_CLASS(CMDIChildWnd),
           RUNTIME_CLASS(CView2)));
  我們現在來實現CCuteFTPView與CView2之間的通信。由於跟文檔類相連的視圖類 是不能安全的與除文檔
     類之外的其餘的視圖類通信的。因此我們只能讓他們都與文檔 類通信。在文檔中我們設置相應的指針以用
     來獲的各個視圖。我們重載 CCuteFTPView::OnOpenDocument()函數。


CCuteFTPView* pCuteFTPView;CView2* pView2;
POSITION pos;
CView* pView;
while(pos!=NULL)
{
       pView=GetNextView(pos);
       if(pView->IsKindOf(RUNTIME_CLASS(CCuteFTPView))==NULL)
           pCuteFTPView=(CCuteFTPView*)pView;
       else(pView->IsKindOf(RUNTIME_CLASS(CCuteFTPView))==NULL)
           pView2=(CView2*)pView;
}
  這樣我們在文檔類中就獲的了跟它相連的所有的視圖的指針。

  如果需要在 CCuteFTPView中調用CView2中的一個方法DoIt()則代碼如下:

CCuteFTPDoc* pDoc=GetDocument();CView2* pView2=pDoc->pView3;pView3.DoIt();
  ■無文檔視圖與文檔關聯視圖之間的通信

  CView3和CView4都是不與文檔相關聯的。我們現在實現CView3與CView2的通信.正如前面所說,CView2
     只能安全的與CCuteFTPDoc通信,因此,CView3如果需要跟CView2通信,也必須藉助於文檔類。因此程序
     的關鍵是如何在CView3中獲得文檔的指針。視圖類中沒有這樣的類成員可以用來直接訪問文檔類。但是我
     們知道在主窗口類MainFrame中我們可以獲得程序的任意窗口類的指針。因此我們只要獲得程序主窗口了
     的指針,就可以解決問題了。代碼實現在CView3中訪問CView2中的DoIt()方法。

  CView3中的代碼如下:
CMainFrame* MainFrame=(CMainFrame*)this->GetParent()->GetParent();
          
           CCuteFTPDoc* Doc=(CCuteFTPDoc*)MainFrame->GetActiveDocument();
           if(Doc!=NULL) Doc->DoIt();
          
           CCuteFTPDoc中的相應的處理函數DoIt()代碼如下:
          
           CView2* pView2;
           POSITION pos;
           CView* pView;
           while(pos!=NULL)
           {
                   pView=GetNextView(pos);
                   if(pView->IsKindOf(RUNTIME_CLASS(CView2))==NULL)
                   pView2=(CView2*)pView;
           }
           pView2->DoIt();
  無文檔關聯視圖之間的通信

  CView3和CView4都是不跟文檔相連的,如何實現他們之間的通信呢。 正如我們在上面所說的那樣,由於
     在主框架中我們可以訪問任意的視圖,因此我們的主要任 務還是在程序中獲得主框架的指針。在CView3
     中訪問CView4中的方法DoIt()。

CMainFrame* MainFrame=(CMainFrame*)this->GetParent()->GetParent();
          
           CView4* View4=(CView4*)MainFrame->m_wndSplitter1.GetPane(2,0);
           View4->DoIt();
  到現在我們已經實現了CuteFTP的主窗口的框架並且能夠實現他們之間相互通信的框架。 同樣的我們可以
     實現其他的一些流行界面例如NetAnts,Foxmail的分割。
  三、關於對話框的分割

  到目前爲止,只有基於文檔/視圖的程序才能使用CSplitterWnd,而基於對話框的應用程序卻不支持
     CSplitterWnd,但是如果我們在繼承類中重載一些虛擬方法,也能使CSplitterWnd 在對話框程序中使用。
     從MFC的源程序WinSplit.cpp中可以看出,爲了獲得父窗口的地方程序都調用了虛擬方法GetParentFrame(),
     因此如果在對話框中使用,我們必須將它改爲GetParent();因此我們將CSplitterWnd的下面幾個方法重載。


virtual void StartTracking(int ht);
virtual CWnd* GetActivePane(int* pRow = NULL, int* pCol   = NULL);
virtual void SetActivePane( int row, int col, CWnd* pWnd   = NULL );
virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
virtual BOOL OnNotify( WPARAM wParam, LPARAM lParam, LRESULT* pResult );
virtual BOOL OnWndMsg( UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult );
  具體實現如下,實現中我將給出原有代碼的主要部分以及修改後的代碼以作對比。

  在cpp文件中加入下面的枚舉類型。


enum HitTestValue
{
                   noHit = 0,//表示沒有選中任何對象
                   vSplitterBox = 1,
                   hSplitterBox = 2,
                   bothSplitterBox = 3,
                   vSplitterBar1 = 101,//代表各個方向的水平分割條
                   vSplitterBar15 = 115,
                   hSplitterBar1 = 201,//代表垂直方向的各個分割條
                   hSplitterBar15 = 215,
                   splitterIntersection1 = 301,//代表各個交叉點
                   splitterIntersection225 = 525
};
           CWnd* CxSplitterWnd::GetActivePane(int* pRow, int* pCol)
{
                 ASSERT_VALID(this);
                //獲得當前的獲得焦點的窗口
                   //下面註釋粗體的是原有的代碼的主要部分。
                   // CWnd* pView = NULL;
                //CFrameWnd* pFrameWnd = GetParentFrame();
                //ASSERT_VALID(pFrameWnd);
                //pView = pFrameWnd->GetActiveView();
                //if (pView == NULL)
                // pView = GetFocus();
                CWnd* pView = GetFocus();
                if (pView != NULL && !IsChildPane(pView, pRow, pCol))
                  pView = NULL;
                return pView;
}
          
void CxSplitterWnd::SetActivePane( int row, int col, CWnd* pWnd)
{
                   CWnd* pPane = pWnd == NULL ? GetPane(row, col) : pWnd;
                   //下面加註釋粗體的是原有代碼的主要部分。
                   //FrameWnd* pFrameWnd = GetParentFrame();
                   //ASSERT_VALID(pFrameWnd);
                   //pFrameWnd->SetActiveView((CView*)pPane);
                   pPane->SetFocus();//修改後的語句
}
          
void CxSplitterWnd::StartTracking(int ht)
{
                   ASSERT_VALID(this);
                   if (ht == noHit)
                           return;
                   // GetHitRect will restrict ''''m_rectLimit'''' as appropriate
          
                   GetInsideRect(m_rectLimit);
                   if (ht >= splitterIntersection1 && ht <= splitterIntersection225)
          
                   {
                           // split two directions (two tracking rectangles)
          
                           int row = (ht - splitterIntersection1) / 15;
          
                           int col = (ht - splitterIntersection1) % 15;
          
                           GetHitRect(row + vSplitterBar1, m_rectTracker);
          
                           int yTrackOffset = m_ptTrackOffset.y;
                           m_bTracking2 = TRUE;
                           GetHitRect(col + hSplitterBar1, m_rectTracker2);
          
                           m_ptTrackOffset.y = yTrackOffset;
                   }
                   else if (ht == bothSplitterBox)
                   {
                   // hit on splitter boxes (for keyboard)
                   GetHitRect(vSplitterBox, m_rectTracker);
                   int yTrackOffset = m_ptTrackOffset.y;
                   m_bTracking2 = TRUE;
                   GetHitRect(hSplitterBox, m_rectTracker2);
                   m_ptTrackOffset.y = yTrackOffset; // center it
                   m_rectTracker.OffsetRect(0, m_rectLimit.Height()/2);
                   m_rectTracker2.OffsetRect(m_rectLimit.Width()/2,
           0);
                   }
                   else
                   {
                   // only hit one bar
                   GetHitRect(ht, m_rectTracker);
                   }
          
           //下面加註釋的將從程序中刪去。
           //CView* pView = (CView*)GetActivePane();
           //if (pView != NULL && pView->IsKindOf(RUNTIME_CLASS(CView)))
           //{
           // ASSERT_VALID(pView);
           // CFrameWnd* pFrameWnd = GetParentFrame();
           //ASSERT_VALID(pFrameWnd);
           //pView->OnActivateFrame(WA_INACTIVE, pFrameWnd);
           // }
           // steal focus and capture
                   SetCapture();
                   SetFocus();
                   // make sure no updates are pending
                   RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_UPDATENOW);
          
                   // set tracking state and appropriate cursor
                   m_bTracking = TRUE;
                   OnInvertTracker(m_rectTracker);
                   if (m_bTracking2)
                           OnInvertTracker(m_rectTracker2);
                   m_htTrack = ht;
                   SetSplitCursor(ht);
}
          
BOOL CxSplitterWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
                   if (CWnd::OnCommand(wParam, lParam))
                           return TRUE;
                   //下面粗體的是原程序的語句
           //return GetParentFrame()->SendMessage(WM_COMMAND, wParam, lParam);
          
                   return GetParent()->SendMessage(WM_COMMAND, wParam, lParam);
          
}
BOOL CxSplitterWnd::OnNotify( WPARAM wParam, LPARAM lParam, LRESULT* pResult )
{
                   if (CWnd::OnNotify(wParam, lParam, pResult))
                           return TRUE;
                   //下面粗體的是源程序的語句
                   //*pResult = GetParentFrame()->SendMessage(WM_NOTIFY,
           wParam, lParam);
                   *pResult = GetParent()->SendMessage(WM_NOTIFY, wParam, lParam);
                   return TRUE;
}
          
BOOL CxSplitterWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
                   // The code line below is necessary if using CxSplitterWnd
           in a regular dll
                   // AFX_MANAGE_STATE(AfxGetStaticModuleState());
                   return CWnd::OnWndMsg(message, wParam, lParam, pResult);
          
}
  這樣我們就可以在對話框中使用CxSplitterWnd類了。

  四、CSplitterWnd的擴展

  CSplitterWnd擴展話題是很多的,我們可以通過對原有方法的覆蓋或者增加新的方法來擴展CSplitterWnd。
     我們在此僅舉兩個方面的例子。

  4.1鎖定切分條

  當用戶創建好分割窗口後,有時並不希望通過拖動切分條來調節窗口的大小。這時就必須鎖定切分條。
     鎖定切分條的最簡單的方法莫過於不讓CSplitterWnd來處理WM_LBUTTONDOWN,WM_MOUSEMOVE,WM_SETCURSOR
     消息,而是將這些消息交給CWnd窗口進行處理,從而屏蔽掉這些消息。拿WM_LBUTTONDOWN處理過程來說。
     修改爲如下:


void CXXSplitterWnd::OnLButtonDown(UINT nFlags,CPoint point) {
         CWnd::OnLButtonDown(nFlags,point);
}
  其餘的處理方法類似。

  4.2切分條的定製

  由Window自己生成的切分條總是固定的,沒有任何的變化,我們在使用一些軟件比如ACDSee的時候卻能發現
     它們的切分條卻是和自動生成的切分條不一樣的。那麼如何定製自己的切分條呢?通過重載CSplitterWnd的
     虛方法OnDrawSplitter和OnInvertTracker可以達到這樣的目的。下面的代碼生成的效果是分割窗口的邊界
     顏色爲紅色,分割條的顏色爲綠色.代碼如下:


void CSplitterWndEx::OnDrawSplitter(CDC *pDC, ESplitType nType, const CRect &rectArg)
{
                   if(pDC==NULL)
                   {
                   RedrawWindow(rectArg,NULL,RDW_INVALIDATE|RDW_NOCHILDREN);
                   return;
                   }
                   ASSERT_VALID(pDC);
                   CRect rc=rectArg;
                   switch(nType)
                   {
                   case splitBorder:
                   //重畫分割窗口邊界,使之爲紅色
                           pDC->Draw3dRect(rc,RGB(255,0,0),RGB(255,0,0));
                           rc.InflateRect(-CX_BORDER,-CY_BORDER);
                           pDC->Draw3dRect(rc,RGB(255,0,0),RGB(255,0,0));
          
                           return;
                   case splitBox:
                           pDC->Draw3dRect(rc,RGB(0,0,0),RGB(0,0,0));
                           rc.InflateRect(-CX_BORDER,-CY_BORDER);
                           pDC->Draw3dRect(rc,RGB(0,0,0),RGB(0,0,0));
                           rc.InflateRect(-CX_BORDER,-CY_BORDER);
                           pDC->FillSolidRect(rc,RGB(0,0,0));
                           pDC->Draw3dRect(rc,RGB(0,0,0),RGB(0,0,0));
                           return;
                   case splitBar:
                   //重畫分割條,使之爲綠色
                           pDC->FillSolidRect(rc,RGB(255,255,255));
                           rc.InflateRect(-5,-5);
                           pDC->Draw3dRect(rc,RGB(255,0,0),RGB(255,0,0));
          
                           return;
                   default:
                           ASSERT(FALSE);
                   }
                   pDC->FillSolidRect(rc,RGB(0,0,255));
}
void CSplitterWndEx::OnInvertTracker(CRect &rect)
{
                   ASSERT_VALID(this);
                   ASSERT(!rect.IsRectEmpty());
                   ASSERT((GetStyle()&WS_CLIPCHILDREN)==0);
                   CRect rc=rect;
                   rc.InflateRect(2,2);
                   CDC* pDC=GetDC();
                   CBrush* pBrush=CDC::GetHalftoneBrush();
                   HBRUSH hOldBrush=NULL;
                   if(pBrush!=NULL) hOldBrush=(HBRUSH)SelectObject(pDC->m_hDC,pBrush->m_hObject);
                   pDC->PatBlt(rc.left,rc.top,rc.Width(),rc.Height(),BLACKNESS);
          
                   if(hOldBrush!=NULL)
                   SelectObject(pDC->m_hDC,hOldBrush);
                   ReleaseDC(pDC);
}
  同樣我們只要繼承CSplitterWnd中的其餘的一些虛擬方法就可以生成具有自己個性的分割窗口了。

發佈了0 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章