netant:用VC++实现应用程序窗口的任意分割 2024-04-23 09:09:33 0 0 我们在使用OutLook或者NetAnt等工具的时候,一般都会被其复杂的界面所吸引,在这些界面中窗口被分割为若干的区域,真正做到了窗口的任意分割。 那么我们自己如何创建类似的界面,也实现窗口的任意的分割呢?要解决这个问题,在Visual C++6.0编程中就需要使用到MFC提供的CSplitterWnd类。CSplitterWnd看上去像是一种特殊的框架窗口,每个窗口都被相同的或者不同的视图所填充。当窗口被切分后用户可以使用鼠标移动切分条来调整窗口的相对尺寸。虽然VC6.0支持从AppWizard中创建分割窗口,但是自动加入的分割条总是不能让我们满意,因此我们还是通过手工增加代码来熟悉这个类。本实例采用多模板模式,即实现了窗口的任意分割,又介绍了各个视图如何相互通信。程序编译运行后的界面效果如图一所示: 图一、窗口任意分割效果图 一、实现方法 Visual C++中MFC提供了CSplitterWnd类来实现窗口的分割,它的构造函数主要包括下面三个: BOOL Create(CWnd* pParentWnd,int nMaxRows,int nMaxCols,SIZE sizeMin, CCreateContext* pContext,DWORD dwStyle,UINT nID); 该函数用来创建动态切分窗口,参数pParentWnd表示切分窗口的父框架窗口;参数nMaxRows,nMaxCols是创建切分窗口的最大列数和行数;sizeMin是窗格的最小尺寸;参数pContext 大多数情况下传给父窗口;nID是切分窗口的ID号。例如下面的代码将创建2x2的窗格。 m_wndSplitter.Create(this,2,2,CSize(100,100),pContext); 动态创建的分割窗口的窗格数目不能超过2x2,而且对于所有的窗格,都必须共享同一个视图,所受的限制也比较多,因此我们不将动态创建作为重点。我们的主要精力放在静态分割窗口的创建上。 BOOL CreateStatic(CWnd* pParentWnd,int nRows,int nCols,DWORD dwStyle,UINT nID) ; 该函数用来用来创建切静态分窗口,参数含义同上。 BOOL CreateView (int row,int col,CruntimeClass* pViewClass,SIZE sizeinit,CcreateContext* pContext); 此函数向静态切分的窗口的网格填充视图。在将视图于切分窗口联系在一起的时候必须先将切分窗口创建好。参数含义同上。与动态创建相比,静态创建的代码要简单许多,而且可以最多创建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连接到2行0列 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; } 在应用程序中拆分窗口后,还有一个重要的工作就是实现各个视图之间的数据通信,有两种方法解决这个问题,一是利用公用的文档;二是利用程序的主框架。为了说明问题,我们让CCuteFTPView、CView2通过文档来实现通信,CView3、CView4通过主框架来通信。对于第一种方法,由AppWizard生成的CCuteFTPView是与文档相连的,同时我们也让CView2与文档相连,因此我们需要修改CCuteFTPApp的InitInstance()函数,增加下面的代码: AddDocTemplate (new CMultiDocTemplate(IDR_VIEW2TYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CMDIChildWnd), RUNTIME_CLASS(CView2))); 然后我们重载 CDocument::OnOpenDocument()函数;在该函数中定义如下变量:CCuteFTPView* pCuteFTPView、CView2* pView2、POSITION pos,并添加如下代码: pos=GetFirstViewPosition( ) while(pos!=NULL) { pView=GetNextView(pos); if(pView->IsKindOf(RUNTIME_CLASS(CCuteFTPView))==NULL) pCuteFTPView=(CCuteFTPView*)pView; else pView2=(CView2*)pView; } 这样我们在文档类中就获的了跟它相连的所有的视图的指针。如果需要在 CCuteFTPView中调用CView2中的一个方法DoIt()则代码如下: CCuteFTPDoc* pDoc=GetDocument(); CView2* pView2=pDoc->pView2; pView3.DoIt(); CView3和CView4都是不与文档相关联的。如何实现他们之间的通信呢。 正如我们在上面所说的那样,由于在主框架中我们可以访问任意的视图,因此我们的主要任务还是在程序中获得主框架的指针。例如下面的代码实现在CView3中访问CView4中的方法DoIt()。 CMainFrame* MainFrame=(CMainFrame*)this->GetParent()->GetParent(); CView4* View4=(CView4*)MainFrame->m_wndSplitter1.GetPane(2,0); View4->DoIt(); 为了更好地加深读者朋友对上述内容的理解,本实例通过灵活运用上述拆分窗口的方法,在多文档视图模板的基础上,实现了窗口的任意拆分,例如当用户在左边视图InPutView中输入字符串、选择颜色后,能立即反映到右边的CCorlorView、CtextView窗口中。 二、编程步骤 1、启动Visual C++6.0生成一个多文档应用程序Viewex,并添加支持分割的各个视图类; 2、修改CViewExApp::InitInstance()函数,为应用程序添加多文档视图结构模板的支持; 3、添加代码,编译运行程序。 三、编程步骤 BOOL CViewExApp::InitInstance() { …………………………… // simple text output view AddDocTemplate(new CMultiDocTemplate(IDR_TEXTTYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CMDIChildWnd), RUNTIME_CLASS(CTextView))); // simple color output view AddDocTemplate(new CMultiDocTemplate(IDR_COLORTYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CMDIChildWnd), RUNTIME_CLASS(CColorView))); // form view with input AddDocTemplate(new CMultiDocTemplate(IDR_INPUTTYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CMDIChildWnd), RUNTIME_CLASS(CInputView))); // splitter frame with both simple text output and form input view AddDocTemplate(new CMultiDocTemplate(IDR_SPLIT2TYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CSplitterFrame), RUNTIME_CLASS(CTextView))); // 3-way splitter frame with form input, text output and color output views AddDocTemplate(new CMultiDocTemplate(IDR_SPLIT3TYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(C3WaySplitterFrame), RUNTIME_CLASS(CInputView))); CMDIFrameWnd* pMainFrame = new CMDIFrameWnd; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; // Now finally show the main menu pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); m_pMainWnd = pMainFrame; OnFileNew(); return TRUE; } //CinputView类的头文件 class CInputView : public CFormView { DECLARE_DYNCREATE(CInputView) protected: CInputView(); // protected constructor used by dynamic creation // Form Data public: //{{AFX_DATA(CInputView) enum { IDD = IDD_INPUTFORM }; CString m_strData; int m_iColor; //}}AFX_DATA // Attributes public: CMainDoc* GetDocument() { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMainDoc))); return (CMainDoc*) m_pDocument; } // Operations public: // Implementation protected: virtual ~CInputView(); virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint); // Generated message map functions //{{AFX_MSG(CInputView) afx_msg void OnDataChange(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /// CInputView类实现文件 #include "stdafx.h" #include "viewex.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif IMPLEMENT_DYNCREATE(CInputView, CFormView) CInputView::CInputView() : CFormView(CInputView::IDD) { //{{AFX_DATA_INIT(CInputView) m_strData = ""; m_iColor = -1; //}}AFX_DATA_INIT } CInputView::~CInputView() {} void CInputView::OnUpdate(CView*, LPARAM, CObject*) { CMainDoc* pDoc = GetDocument(); m_strData = pDoc->m_strData; if (pDoc->m_colorData == RGB(255, 0, 0)) m_iColor = 0; else if (pDoc->m_colorData == RGB(0, 255, 0)) m_iColor = 1; else if (pDoc->m_colorData == RGB(0, 0, 255)) m_iColor = 2; else m_iColor = -1; TRACE2("OnUpdate: m_iColor = %d ($%lx)\n", m_iColor, pDoc->m_colorData); UpdateData(FALSE); // set the data into the controls } void CInputView::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); //{{AFX_DATA_MAP(CInputView) DDX_Text(pDX, IDC_EDIT1, m_strData); DDX_Radio(pDX, IDC_RADIO1, m_iColor); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CInputView, CFormView) //{{AFX_MSG_MAP(CInputView) ON_EN_CHANGE(IDC_EDIT1, OnDataChange) ON_BN_CLICKED(IDC_RADIO1, OnDataChange) ON_BN_CLICKED(IDC_RADIO2, OnDataChange) ON_BN_CLICKED(IDC_RADIO3, OnDataChange) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CInputView::OnDataChange()// CInputView message handlers { if (!UpdateData()) return; CMainDoc* pDoc = GetDocument(); COLORREF color = RGB(255 * (m_iColor == 0), 255 * (m_iColor == 1), 255 * (m_iColor == 2)); BOOL bUpdate = FALSE; if (m_strData != pDoc->m_strData) { pDoc->m_strData = m_strData; bUpdate = TRUE; } if (color != pDoc->m_colorData) { pDoc->m_colorData = color; bUpdate = TRUE; } if (bUpdate) { // if the document stored data then we would call SetModifiedFlag here pDoc->UpdateAllViews(this); } } /simpvw.h文件 class CTextView : public CView { protected: // create from serialization only CTextView(); DECLARE_DYNCREATE(CTextView) // Attributes public: CMainDoc* GetDocument() { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMainDoc))); return (CMainDoc*) m_pDocument; } // Operations public: // Implementation public: virtual ~CTextView(); virtual void OnDraw(CDC* pDC); // overridden to draw this view // Generated message map functions protected: //{{AFX_MSG(CTextView) afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; class CColorView : public CView { protected: // create from serialization only CColorView(); DECLARE_DYNCREATE(CColorView) // Attributes public: CMainDoc* GetDocument() { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMainDoc))); return (CMainDoc*) m_pDocument; } // Operations public: // Implementation public: virtual ~CColorView(); virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual void OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView); // Generated message map functions protected: //{{AFX_MSG(CColorView) afx_msg int OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; simpvw.cpp文件; #include "stdafx.h" #include "viewex.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif /CTextView IMPLEMENT_DYNCREATE(CTextView, CView) BEGIN_MESSAGE_MAP(CTextView, CView) //{{AFX_MSG_MAP(CTextView) ON_WM_MOUSEACTIVATE() //}}AFX_MSG_MAP END_MESSAGE_MAP() // CTextView construction/destruction CTextView::CTextView() {} CTextView::~CTextView() {} void CTextView::OnDraw(CDC* pDC) { CMainDoc* pDoc = GetDocument(); CRect rect; GetClientRect(rect); pDC->SetTextAlign(TA_BASELINE | TA_CENTER); pDC->SetTextColor(pDoc->m_colorData); pDC->SetBkMode(TRANSPARENT); // center in the window pDC->TextOut(rect.Width() / 2, rect.Height() / 2, pDoc->m_strData, pDoc->m_strData.GetLength()); } int CTextView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message) { // side-step CView's implementation since we don't want to activate // this view return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message); } // CColorView IMPLEMENT_DYNCREATE(CColorView, CView) BEGIN_MESSAGE_MAP(CColorView, CView) //{{AFX_MSG_MAP(CColorView) ON_WM_MOUSEACTIVATE() //}}AFX_MSG_MAP END_MESSAGE_MAP() // CColorView construction/destruction CColorView::CColorView() {} CColorView::~CColorView() {} void CColorView::OnDraw(CDC* pDC) { CMainDoc* pDoc = GetDocument(); CRect rect; GetClientRect(rect); // fill the view with the specified color CBrush br(pDoc->m_colorData); pDC->FillRect(rect, &br); } int CColorView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message) { // side-step CView's implementation since we don't want to activate // this view return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message); } void CColorView::OnActivateView(BOOL, CView*, CView*) { ASSERT(FALSE); // output only view - should never be active } / splitter.h文件; // CSplitterFrame frame with splitter/wiper class CSplitterFrame : public CMDIChildWnd { DECLARE_DYNCREATE(CSplitterFrame) protected: CSplitterFrame(); // protected constructor used by dynamic creation // Attributes protected: CSplitterWnd m_wndSplitter; // Implementation public: virtual ~CSplitterFrame(); virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext); // Generated message map functions //{{AFX_MSG(CSplitterFrame) //}}AFX_MSG DECLARE_MESSAGE_MAP() }; class CViewExSplitWnd : public CSplitterWnd { DECLARE_DYNAMIC(CViewExSplitWnd) // Implementation public: CViewExSplitWnd(); ~CViewExSplitWnd(); CWnd* GetActivePane(int* pRow = NULL, int* pCol = NULL); }; class C3WaySplitterFrame : public CMDIChildWnd { DECLARE_DYNCREATE(C3WaySplitterFrame) protected: C3WaySplitterFrame(); // protected constructor used by dynamic creation // Attributes protected: CViewExSplitWnd m_wndSplitter; CViewExSplitWnd m_wndSplitter2; // embedded in the first // Implementation public: virtual ~C3WaySplitterFrame(); virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext); // Generated message map functions //{{AFX_MSG(C3WaySplitterFrame) //}}AFX_MSG DECLARE_MESSAGE_MAP() }; /splitter.cpp文件; #include "stdafx.h" #include "viewex.h" #include "splitter.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif // CSplitterFrame // Create a splitter window which splits an output text view and an input view // | // TEXT VIEW (CTextView) | INPUT VIEW (CInputView) // | IMPLEMENT_DYNCREATE(CSplitterFrame, CMDIChildWnd) CSplitterFrame::CSplitterFrame() {} CSplitterFrame::~CSplitterFrame() {} BOOL CSplitterFrame::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext) { // create a splitter with 1 row, 2 columns if (!m_wndSplitter.CreateStatic(this, 1, 2)) { TRACE0("Failed to CreateStaticSplitter\n"); return FALSE; } // add the first splitter pane - the default view in column 0 if (!m_wndSplitter.CreateView(0, 0,pContext->m_pNewViewClass, CSize(130, 50), pContext)) { TRACE0("Failed to create first pane\n"); return FALSE; } // add the second splitter pane - an input view in column 1 if (!m_wndSplitter.CreateView(0, 1,RUNTIME_CLASS(CInputView), CSize(0, 0), pContext)) { TRACE0("Failed to create second pane\n"); return FALSE; } // activate the input view SetActiveView((CView*)m_wndSplitter.GetPane(0,1)); return TRUE; } BEGIN_MESSAGE_MAP(CSplitterFrame, CMDIChildWnd) //{{AFX_MSG_MAP(CSplitterFrame) //}}AFX_MSG_MAP END_MESSAGE_MAP() // C3WaySplitterFrame - just like CSplitterFrame except the input view is // the first pane, and there are two output views // | Text View (CTextView) // INPUT VIEW (CInputView) |------------------------ // | Color View (CColorView) IMPLEMENT_DYNCREATE(C3WaySplitterFrame, CMDIChildWnd) C3WaySplitterFrame::C3WaySplitterFrame() {} C3WaySplitterFrame::~C3WaySplitterFrame() {} BOOL C3WaySplitterFrame::OnCreateClient(LPCREATESTRUCT lpcs,CCreateContext* pContext) { // create a splitter with 1 row, 2 columns if (!m_wndSplitter.CreateStatic(this, 1, 2)) { TRACE0("Failed to CreateStaticSplitter\n"); return FALSE; } // add the first splitter pane - the default view in column 0 if (!m_wndSplitter.CreateView(0, 0,pContext->m_pNewViewClass, CSize(200, 50), pContext)) { TRACE0("Failed to create first pane\n"); return FALSE; } // add the second splitter pane - which is a nested splitter with 2 rows if (!m_wndSplitter2.CreateStatic(&m_wndSplitter, // our parent window is the first splitter 2, 1, // the new splitter is 2 rows, 1 column WS_CHILD | WS_VISIBLE | WS_BORDER, // style, WS_BORDER is needed m_wndSplitter.IdFromRowCol(0, 1) )) // new splitter is in the first row, 2nd column of first splitter { TRACE0("Failed to create nested splitter\n"); return FALSE; } // now create the two views inside the nested splitter int cyText = max(lpcs->cy - 70, 20); // height of text pane if (!m_wndSplitter2.CreateView(0, 0,RUNTIME_CLASS(CTextView), CSize(0, cyText), pContext)) { TRACE0("Failed to create second pane\n"); return FALSE; } if (!m_wndSplitter2.CreateView(1, 0,RUNTIME_CLASS(CColorView), CSize(0, 0), pContext)) { TRACE0("Failed to create third pane\n"); return FALSE; } // it all worked, we now have two splitter windows which contain // three different views return TRUE; } BEGIN_MESSAGE_MAP(C3WaySplitterFrame, CMDIChildWnd) //{{AFX_MSG_MAP(C3WaySplitterFrame) //}}AFX_MSG_MAP END_MESSAGE_MAP() IMPLEMENT_DYNAMIC(CViewExSplitWnd, CSplitterWnd) CViewExSplitWnd::CViewExSplitWnd() {} CViewExSplitWnd::~CViewExSplitWnd() {} CWnd* CViewExSplitWnd::GetActivePane(int* pRow, int* pCol) { ASSERT_VALID(this); // attempt to use active view of frame window CWnd* pView = NULL; CFrameWnd* pFrameWnd = GetParentFrame(); ASSERT_VALID(pFrameWnd); pView = pFrameWnd->GetActiveView(); // failing that, use the current focus if (pView == NULL) pView = GetFocus(); return pView; } 四、小结 本实例通过灵活运用CsplitterWnd类,实现了窗口的任意拆分。另外,需要补充的内容是,在具体应用中可以通过对CSplitterWnd原有方法的覆盖或者增加新的方法来扩展CSplitterWnd。我们在此仅举两个方面的例子,一是锁定切分条;二是定制自己的切分条。对于锁定切分条,不希望用户通过拖动切分条来调节窗口的大小这个问题,最简单的解决方法莫过于不让CSplitterWnd来处理WM_LBUTTONDOWN,WM_MOUSEMOVE,WM_SETCURSOR消息,而是将这些消息交给CWnd窗口进行处理,从而屏蔽掉这些消息。那么如何定制自己的切分条呢?通过重载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); } 收藏(0)