使用CSplitterWnd实现拆分窗口(多视图显示)
MFC支持两种类型的拆分窗口:静态的和动态的。
静态拆分窗口的行列数在拆分窗口被创建时就设置好了,用户不能更改。但是用户可以缩放各行各列。一个静态拆分窗口最多可以包含16行16列。
要找一个使用了静态拆分窗口的应用程序,只要看一下windows管理器即可。
动态拆分窗口最多可以有两行两列,但它们可以相互拆分和合并。Vc就使用了动态拆分窗口使得可以同时编辑源程序文件的两个以上不同的部分。
选择静态或动态拆分的一个准则是是否希望用户能够交互地修改拆分窗口的行列配置。另一个决定因素是计划在拆分窗口中使用的视图种类。
在静态拆分窗口中很容易使用两个以上不同种类的视图,因为您可以在每个窗格中指定所用的视图类型。但是在动态拆分窗口中,MFC管理着视图,
除非从 CsplitterWnd派生一个新类并修改拆分窗口的默认操作性能,否则拆分窗口中的所有视图使用的都是相同的视图类。
静态拆分窗口是用CsplitterWnd::CreateStatic而不是CsplitterWnd::Create创建,并且由于MFC不会自动创建静态拆分窗口中显示的视图,
所以您要亲自在CreateStatic返回之后创建视图。CsplitterWnd为此提供了名为 CreateView的函数。
你应按如下步骤创建一个CSplitterWnd对象:
1. 在父框架中嵌入一个CSplitterWnd成员变量。
2. 重载父框架的CFrameWnd::OnCreateClient成员函数。
3. 从重载的OnCreateClient函数中调用类CSplitterWnd的Create或CreateStatic成员函数,并调用CreateView来创建视图。
使用静态拆分窗口的一个优点是由于您自己给窗格添加视图,所以可以控制放入视图的种类。
关键函数
BOOL CreateStatic( CWnd* pParentWnd, int nRows,int nCols, DWORD dwStyle = WS_CHILD | WS_VISIBLE, UINT nID = AFX_IDW_PANE_FIRST );
函数有5个参数,意义如下:
● pParentWnd:切分窗口的父窗口指针
● nRows:水平方向分隔窗口的数目
● nCols:垂直方向分隔窗口的数目
● dwStyle:切分窗口的风格
● nID:子窗口的ID值,默认为系统定义的AFX_IDW_PANE_FIRST
返回值:如果创建成功,返回非零值(TRUE),否则返回0(FALSE)。
m_wndSplitter.CreateStatic(this, 2,1);// 切分为2行1列
virtual BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );
函数有5个参数,意义如下:
● row:窗格的行标,从0开始
● col:窗格的列标,从0开始
● pViewClass:视图的执行期类CRuntimeClass指针,可以用宏RUNTIME_CLASS获得
● sizeInit:一个SIZE(或者CSize)类型的数据,指定窗格的初始大小
● pContext:一般是由父窗口传递过来,包含窗口的创建信息
返回值:如果创建成功,返回非零值(TRUE),否则返回0(FALSE)。
m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CTest),CSize(190,100),pContext)
实现的关键代码
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // TODO: Add your specialized code here and/or call the base class if(!m_wndSplitter.CreateStatic(this,1,2)) { return FALSE; } CRect rect; GetClientRect(&rect); if(!m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CTest),CSize(rect.Width()/4,rect.Height()),pContext)|| !m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CSplitterSDIView), CSize(rect.Width()/4*3,rect.Height()),pContext)) { return FALSE; } return TRUE; }
重载该函数之前需要做的步骤:
1. 在CMainFrame类中添加protected成员CSplitterWnd m_wndSplitter;
2. 建立对话框资源(IDD_FORMVIEW),并以CFormView类为基类建立相应的视类;
3. 重载OnCreateClient函数(如上述代码)。
4. 如果需要更多的划分,则再添加其他的CSplitterWnd成员变量,但在m_wndSplitter2.CreateStatic()
函数里的第一个参数则采用需要划分的那个子视图,如m_wndSplitter2.CreateStatic(&m_wndSplitter1,
1, 2, WS_CHILD|WS_VISIBLE, m_wndSplitter1.IdFromRowCol(0,0)) ),当然也需要自己创建需要的所
有视图。
多视类之间的交互
在MFC程序中,各个视类之间进行数据交互是通过Doc类来完成的,由CDocument类来处理文档,
而由CView类来显示。即将数据存储到CDocument类中,而用到数据的时候再从该类中读取。
处理按钮事件:
void CTest::OnShowInt() { // TODO: Add your control notification handler code here CSplitterSDIDoc* pDoc =(CSplitterSDIDoc*) GetDocument(); UpdateData(TRUE); pDoc->x=m_int; pDoc->UpdateAllViews(NULL); }
在CSplitterSDIView中显示:
void CSplitterSDIView::OnDraw(CDC* pDC) { CSplitterSDIDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CString str; str.Format("%d", pDoc->x); pDC->TextOut(0,0,str); }
CSplitterWnd类的其他成员信息
有关该类的其他成员函数,可以参考MSDN。
其他信息
当用户创建好分割窗口后,有时并不希望通过拖动切分条来调节窗口的大小。这时就必须锁定切分条。锁定切分条的最简单的
void CXXSplitterWnd::OnLButtonDown(UINT nFlags,CPoint point) { CWnd::OnLButtonDown(nFlags,point);}
切分条的定制
由Window自己生成的切分条总是固定的,没有任何的变化,我们在使用一些软件比如ACDSee的时候却能发现它们的切分条
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);
}
http://www.cnblogs.com/feisky/archive/2010/03/07/1680222.html
另外参考:
用MFC将SDI窗口三叉拆分并初始化各个View
最近做MFC界面,发现《深入浅出MFC》等很多资料里只是教我们如何将窗口三叉拆分,但是拆分后每个View类的初始化和设置很少有资料设计,让我这种初学者郁闷乐半天。这里是我自己的总结,十分简陋……
我需要三个试图,一个ListView,一个TreeView和一个EditView。类似于vc6的界面最下方为list,上部分左边为tree,右边为edit。首先,创建一个工程PaperInfoCrawler,选择SDI窗口,并将CPaperInfoCrawer继承CListview
在MainFram中加入变量 CSplitterWnd m_wndSplitter和CSplitterWnd m_wndTopSplitter并重载函数virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext),下面为函数内容{ m_wndSplitter.CreateStatic(this,2,1);
m_wndSplitter.SetRowInfo(0,450,450);
m_wndTopSplitter.CreateStatic(&m_wndSplitter,1,2,WS_CHILD|WS_VISIBLE,m_wndSplitter.IdFromRowCol(0,0));
m_wndTopSplitter.CreateView(0,0,RUNTIME_CLASS(CMyTreeView),CSize(300,0),pContext);
m_wndTopSplitter.CreateView(0,1,RUNTIME_CLASS(CMyEditView),CSize(0,0),pContext);
m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CPaperInfoCrawerView),CSize(0,0),pContext);
return TRUE;}
这其中CMyTreeView和CMyEditView是自己继承的View类,CPaperInfoCrawerView为自动生成的类,此时需要在MainFram.cpp中include相应的头文件。这时候编译通过应该窗口已经被划分为三部分了,下面开始对各个View进行初始化。
MyTreeView.cpp中增加变量 CTreeCtrl* ptheTree重载virtual void OnInitialUpdate()函数内容为{
CTreeView::OnInitialUpdate();
ptheTree=&GetTreeCtrl();
ptheTree->ModifyStyle(0,TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS|TVS_EDITLABELS);
TVINSERTSTRUCT tvInsert;
HTREEITEM hTreeItem;
tvInsert.hInsertAfter = NULL;//TVI_LAST;
tvInsert.hParent = TVI_ROOT;
tvInsert.item.mask = TVIF_TEXT;
tvInsert.item.pszText = "搜索引擎";
hTreeItem = ptheTree->InsertItem(&tvInsert);
tvInsert.hParent = hTreeItem;
tvInsert.item.pszText = "Google";
ptheTree->InsertItem(&tvInsert);
tvInsert.item.pszText = "Baidu";
ptheTree->InsertItem(&tvInsert);
ptheTree->Expand(hTreeItem,TVE_EXPAND); //默认为合上的TVE_COLLAPSE,打开的TVE_EXPAND
}
PaperInfoCrawler.cpp中增加变量 CListCtrl* ptheList重载virtual void OnInitialUpdate()函数内容为{
CListView::OnInitialUpdate();
ptheList=&GetListCtrl();
ptheList->ModifyStyle(0,LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SORTASCENDING);
ptheList->SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE,0,LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);
ptheList->InsertColumn(0,"标题",LVCFMT_LEFT,200,0);
ptheList->InsertColumn(1,"URL",LVCFMT_LEFT,200,0);
ptheList->InsertColumn(2,"引擎",LVCFMT_LEFT,200,0);
int pos;
pos = ptheList->InsertItem(0,"123");
ptheList->SetItemText(pos,1,"http:\\\\");
ptheList->SetItemText(pos,2,"Google");
}
编译通过就可以发现每个窗口都按我们的要求进行了划分和初始化
//获取主窗口
CMainFrame* pFrame=static_cast<CMainFrame*>(AfxGetMainWnd());
//获取某个view
CMyTreeView* pView=static_cast<CMyTreeView*>(pFrame->m_wndTopSplitter.GetPane(0,0));
//激活View
pFrame->SetActiveView(pView);
pFrame->m_wndTopSplitter.RecalcLayout();
//想干什么就干什么
pView->XXXXXX();
pView->SendMessage(WM_PAINT);
通过以上代码我们就可以在各个view间互相通信了