第7章 框架窗口、文档和视图

来源:百度文库 编辑:神马文学网 时间:2024/04/30 00:32:38
7.1框架窗口
7.2文档及其序列化
7.3视图及视图类
7.4文档视图结构
7.1框架窗口
7.1.1主窗口和文档窗口
主窗口是应用程序直接放置在桌面上的那个窗口,每个应用程序只能有一个主窗口,主窗口的标题栏上往往显示应用程序的名称。
文档窗口对于SDI程序来说,它和主窗口是一致的,即主窗口就是文档窗口;而对于MDI程序,文档窗口是主窗口的子窗口,如图。文档窗口一般都有相应的可见边框,它的客户区是由相应的视图来构成的,因此可以说视图是文档窗口内的子窗口。文档窗口时刻跟踪当前处于活动状态的视图的变化,并将用户或系统产生的命令消息传递给当前活动视图。而主窗口负责管理各个用户交互对象并根据用户操作相应地创建或更新文档窗口及其视图。
7.1框架窗口
7.1.2窗口风格的设置
Ø     窗口风格
窗口风格通常有一般和扩展两种形式。这两种形式的窗口风格可在函数CWnd::Create或CWnd::CreateEx参数中指定,CreateEx函数可同时支持以上两种风格,而CWnd::Create只能指定窗口的一般风格。需要说明的是,对于控件和对话框这样的窗口来说,它们的窗口风格可直接通过其属性对话框来设置。
需要说明的是,除了上述风格外,框架窗口还有以下三个自己的风格。它们都可以在PreCreateWindow重载函数中指定。
u       FWS_ADDTOTITLE
该风格指定相关的信息如文档名添加到框架窗口标题的后面。
u       FWS_PREFIXTITLE
该风格使得框架窗口标题中的文档名显示在应用程序名之前。
u       FWS_SNAPTOBARS
该风格用来调整窗口的大小,使它刚好包含了框架窗口中的控制栏。
7.1框架窗口
Ø     用MFC AppWizard设置
7.1框架窗口
Ø      修改CREATESTRUCT结构
窗口创建之前,系统自动调用PreCreateWindow虚函数。在用MFC AppWizard创建SDI/MDI应用程序结构时,MFC已为主窗口或文档窗口类自动重载了该虚函数。可以在此函数中通过修改CREATESTRUCT结构来设置窗口的绝大多数风格。
例如,在SDI程序中,框架窗口默认的风格是WS_OVERLAPPEDWINDOW和FWS_ADDTOTITLE的组合,更改风格代码:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{        // 新窗口不带有[最大化]按钮
cs.style &= ~WS_MAXIMIZEBOX;
// 将窗口的大小设为1/3屏幕并居中
cs.cy = ::GetSystemMetrics(SM_CYSCREEN) / 3;
cs.cx = ::GetSystemMetrics(SM_CXSCREEN) / 3;
cs.y = ((cs.cy * 3) - cs.cy) / 2;
cs.x = ((cs.cx * 3) - cs.cx) / 2;
// 调用基类的PreCreateWindow函数
return CFrameWnd::PreCreateWindow(cs);
}
对于MDI程序,文档窗口的风格可用下列的代码更改:
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{        // 创建不含有[最大化]按钮的子窗口
cs.style &= ~WS_MAXIMIZEBOX;
// 调用基类的PreCreateWindow函数
return CMDIChildWnd::PreCreateWindow(cs);
}
7.1框架窗口
Ø     使用ModifyStyle和ModifyStyleEx
CWnd类中的成员函数ModifyStyle和ModifyStyleEx也可用来更改窗口的风格,其中ModifyStyleEx还可更改窗口的扩展风格。这两个函数具有相同的参数,其含义如下。
BOOL ModifyXXXX( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );
参数dwRemove用来     指定需要删除的风格,dwAdd用来指定需要增加的风格,nFlags表示SetWindowPos的标志,0(默认)表示更改风格的同时不调用SetWindowPos函数。
由于框架窗口在创建时不能直接设定其扩展风格,因此只能通过调用ModifyStyle函数来进行。
7.1框架窗口
[例Ex_Vscroll] 多文档(MDI)的子文档窗口增加垂直滚动条。
(1)用MFC AppWizard创建一个多文档应用程序。
(2)用ClassWizard为子文档窗口类CChildFrame添加OnCreateClient消息处理,并增加下列代码:
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{        ModifyStyle(0, WS_VSCROLL, 0);
return CMDIChildWnd::OnCreateClient(lpcs, pContext);
}
(3)编译并运行,如图。
7.1框架窗口
Ø    设置TopMost窗口风格
最顶(TopMost)窗口是指那些始终出现在桌面的最前方且不会被其他窗口覆盖的窗口。如果要创建这样的一个最顶窗口,则在运行时必须指定WS_EX_TOPMOST扩展窗口风格。此风格的设置是在CWnd::SetWindowPos函数中进行;作为技巧,我们可以先调用CWnd::GetExStyle函数来确定是否已经设置了WS_EX_TOPMOST风格。例如下面的代码:
void SetWindowTopMost(CWnd* pWnd)
{        ASSERT_VALID(pWnd);
pWnd->SetWindowPos((pWnd->GetExStyle()&WS_EX_TOPMOST)?
&wndNoTopMost:&wndTopMost,0,0,0,0,
SWP_NOSIZE | SWP_NOMOVE);
}
函数SetWindowPos具体的使用方法将在下一节中介绍。
7.1框架窗口
7.1.3窗口状态的改变
Ø     用ShowWindow改变窗口的显示状态
当一个新的应用程序创建之后,InitInstance函数总会调用ShowWindow函数来显示窗口
想要改变改变窗口显示状态,只需根据上表选择所需的参数值,调用ShowWindow函数就可达到目的。当然,也可对CWinApp的公有型(public)成员变量m_nCmdShow进行赋值,同样能达到效果。下面的代码是将窗口的初始状态设置为“最小化”:
BOOL CEx_SdiApp::InitInstance()
{      ...
m_nCmdShow = SW_SHOWMINIMIZED;
// 由于CEx_SdiApp类继承了基类CWinApp的特性,因此也可在派生类
// CEx_SdiApp使用这个公有型成员变量m_nCmdShow。
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
7.1框架窗口
Ø      用SetWindowPos或MoveWindow改变窗口的大小和位置
u       SetWindowPos不仅可以改变窗口的大小、位置,还可以改变所有窗口在堆栈排列的次序(Z次序),这个次序是根据它们在屏幕出现的先后来确定的。
u       函数CWnd::MoveWindow也可用来改变窗口的大小和位置,用户必须在MoveWindow函数中指定窗口的大小。
void MoveWindow( int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE );
void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );
参数x和y表示          窗口新的左上角坐标,nWidth和nHeight表示窗口新的宽度和高度,bRepaint用于指定窗口是否重绘,lpRect表示窗口新的大小和位置。
u       使用上述两个函数把主窗口移动到屏幕的(100,100)处(代码添在CEx_SdiApp::InitInstance中[return TRUE]语句之前)。
// 使用SetWindowPos函数的示例
m_pMainWnd->SetWindowPos(NULL,100,100,0,0,SWP_NOSIZE|SWP_NOZORDER);
CRect rcWindow;                  // 使用MoveWindow函数的示例
m_pMainWnd->GetWindowRect(rcWindow);
m_pMainWnd->MoveWindow(100,100,rcWindow.Width(),rcWindow.Height(),TRUE);
u       改变窗口的大小和位置的CWnd成员函数还不止以上两个。例如CenterWindow函数是使窗口居于父窗口中央,就像下面的代码:
CenterWindow(CWnd::GetDesktopWindow());          // 将窗口置于屏幕中央
AfxGetMainWnd()->CenterWindow(); // 将主框架窗口居中
7.2文档及其序列化
7.2.1单文档和多文档
一个多文档应用程序的示例过程:
(1)用MFC AppWizard创建一个多文档应用程序项目Ex_Mdi。由于向导默认创建的就是这种程序类型,因此只要在向导的Step 1对话框中单击[Finish]按钮即可创建一个默认的多文档应用程序。
(2)打开CEx_MdiApp::InitInstance可看出与默认的单文档应用程序不同的代码是:
BOOL CEx_MdiApp::InitInstance()
{        …
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_EX_MDITYPE,
RUNTIME_CLASS(CEx_MdiDoc),
RUNTIME_CLASS(CChildFrame), // MDI文档子窗口
RUNTIME_CLASS(CEx_MdiView));
AddDocTemplate(pDocTemplate);
// 创建MDI主框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;

}
7.2文档及其序列化
(3)用ClassWizard为该项目添加一个CDocument派生类CAnotherDoc,并在CEx_Mdi- App::InitInstance增加下列代码:
BOOL CEx_MdiApp::InitInstance()
{        …
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_EX_MDITYPE,
RUNTIME_CLASS(CEx_MdiDoc),
RUNTIME_CLASS(CChildFrame), // MDI文档子窗口
RUNTIME_CLASS(CEx_MdiView));
AddDocTemplate(pDocTemplate);
CMultiDocTemplate* pAnother;
pAnother = new CMultiDocTemplate(IDR_EX_MDITYPE,
RUNTIME_CLASS(CAnotherDoc),
RUNTIME_CLASS(CChildFrame), // MDI文档子窗口
RUNTIME_CLASS(CEx_MdiView));
AddDocTemplate(pAnother);
// 创建MDI主框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;

}
7.2文档及其序列化
(4)在Ex_Mdi.cpp文件的开始处,添加包含CAnotherDoc类的头文件:
#include "AnotherDoc.h"
(5)编译并运行后,如图,从中选择一个文档类型。任何时候,选择Ex_Mdi程序的“文件”菜单à“新建”命令或单击“新建”工具按钮都将弹出此对话框。
选定第二个文档类型后,单击[确定]。这时程序出现一个断言错误,这是因为在视图类中有一个GetDocument函数,它的代码如下:
CEx_MdiDoc* CEx_MdiView::GetDocument() // non-debug version is inline
{          ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx_MdiDoc)));
return (CEx_MdiDoc*)m_pDocument;
}
创建的文档类型是CAnotherDoc时,由于两种类型的文档共用一个视图,调用的GetDocument函数是同一个函数,因此断言不成立,程序不能正常运行。
(6)用ClassWizard为该项目添加一个CView类的派生类CAnotherView,并将InitInstance第二模板pAnother中的CEx_MdiView改成CAnotherView,然后在该源文件前面添加CAnotherView的头文件AnotherView.h。
(7)再次运行,上述问题得到解决。但是,图7.4的文档“新建”对话框中显示的文档类型名称应该怎么修改呢?这时就需要使用文档的字串资源。
7.2文档及其序列化
7.2.2文档的字串资源
通过对文档的字串资源修改可以改变文档窗口标题、文档类型以及通用文件对话框中的某些内容。例如下面的示例过程:
(1)打开前面的多文档应用程序项目Ex_Mdi。
(2)打开String Table,将IDR_EX_MDITYPE字串资源复制,复制后的ID为IDR_ANOTHERTYPE,修改这两个字串资源,结果如下表所示。
如果IDR_ANOTHERTYPE的值不等于130,可在其属性对话框中直接给标识赋值,如图。
(3)将InitInstance第二模板pAnother中的资源号IDR_EX_MDITYPE改成IDR_ANOTHERTYPE。
7.2文档及其序列化
(4)重新编译并运行,如图。测试后还发现“打开”或“保存”对话框中的“文件类型”框中显示出文档字串资源定义的类型名,如图。
文档资源字串的内容也可在文档应用程序创建时的Advanced Options对话框中指定,如图,图中数字表示该项的含义与表中对应串号的含义相同。
7.2文档及其序列化
7.2.3 SDI序列化过程
Ø     创建空文档
应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通过CWinApp:: ProcessShellCommand间接调用CWinApp的另一个非常有用的成员函数OnFileNew,并依次完成下列工作:
(1)构造文档对象,但并不从磁盘中读数据。
(2)构造主框架类CMainFrame的对象,并创建该主框架窗口,但不显示。
(3)构造视图对象,并创建视图窗口,也不显示。
(4)通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。AddDocTemplate函数建立的是“类”之间的联系。
(5)调用文档对象的CDocument::OnNewDocument虚成员函数,并调用CDocument:: DeleteContents(清除文档对象的内容)虚函数。
(6)调用视图对象的CView::OnInitialUpdate虚成员函数。
(7)调用框架对象的CFrameWnd::ActiveFrame虚成员函数,以便显示出具有菜单、工具栏、状态栏以及视图窗口的主框架窗口。
MFC AppWizard为用户在文档类中自动产生OnNewDocument虚函数的重载,用户利用此函数框架可以对文档对象进行初始化。
7.2文档及其序列化
Ø     打开文档
MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“打开(Open)”命令(ID号为ID_FILE_OPEN)映射到CWinApp的OnFileOpen成员函数。这一结果可以从应用类(.cpp)的消息入口处得到验证:
BEGIN_MESSAGE_MAP(CEx_SdiApp, CWinApp)
……
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
// Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
OnFileOpen函数还会进一步完成下列工作:
(1)弹出通用“打开”对话框,供用户选择一个文件。
(2)调用文档对象的CDocument:: OnOpenDocument虚成员函数。该函数将打开文件,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive对象用于数据的读取,接着又自动调用Serialize函数。
(3)调用视图对象的CView::OnInitialUpdate虚成员函数。
7.2文档及其序列化
MFC为我们重载了Serialize函数,使得我们不必使用CFile类就可以完成相应的文档操作。例如,在文档类中有这样的代码:
void CEx_SdiDoc::Serialize(CArchive& ar)
{      if (ar.IsStoring())
{       // TODO: add storing code here        }
else
{       // TODO: add loading code here        }
}
只需根据ar.IsStoring()结果的“真”与“假”就可决定向文档写与读数据。例如,在此判断体中,可以增加一些代码来读取文档中的数据:
void CEx_SdiDoc::Serialize(CArchive& ar)
{      if (ar.IsStoring())        {}
else
{        for (int i=0; i>m_ch[i];
CString str;
str.Format(_T(“%s”), m_ch);
AfxMessageBox(str);
}
}
7.2文档及其序列化
Ø     保存文档
MFC AppWizard创建应用程序时,会自动将“文件(File)”菜单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作:
(1)弹出通用“保存”对话框,让用户提供一个文件名。
(2)调用文档对象的CDocument::OnSaveDocument虚成员函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。
需要说明的是:
u       只有在保存文档之前还没有存过盘或读取的文档是“只读”的,OnFileSave函数才会弹出通用“保存”对话框。否则,只执行第二步。
u       “文件(File)”菜单中还有一个“另存为(Save As)”命令,它是与文档类CDocument的OnFileSaveAs函数相关联。不管文档有没有保存过,OnFileSaveAs都有会执行上述两个步骤。
可以用ClassWizard来重载CDocument::OnSaveDocument函数,并可在Serialize函数体的ar.IsStorinr()为“真”的条件语句处添加代码来在文档中保存用户自己的数据,
7.2文档及其序列化
Ø    关闭文档
试图关闭文档时,应用程序会根据文档是否修改进一步完成下列任务:
§       若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。若用户选择“ 是” ,则应用程序执行OnFileSave 过程。
§       调用CDocument::OnCloseDocument 虚成员函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类CDocument 的DeleteContents 清除文档数据。
u       MFC应用程序通过CDocument的数据成员m_bModified的逻辑值来判断用户是否对文档进行修改,如果m_bModified为“真”,则表示文档被修改。
u       protected类型的m_bModified标记是通过CDocument的SetModifiedFlag和IsModified成员函数被访问的。当文档创建、从磁盘中读出以及文档存盘时,文档的这个标记就被置为FALSE(假);而当文档数据被修改时,必须使用SetModifiedFlag函数将该标记置为TRUE。关闭文档时,应用程序才会显示询问消息对话框。
7.2文档及其序列化
7.2.4使用CFile和CArchive类
Ø    使用CFile类
(1)文件的打开和关闭
在MFC中,使用CFile打开一个文件通常使用下列两个步骤:
u       构造一个不带指定任何参数的CFile对象;
u       调用成员函数Open并指定文件路径以及文件标志。
Open函数的原型如下:
BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );
7.2文档及其序列化
(2)文件的读写和定位
CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:
u       UINT Read( void* lpBuf, UINT nCount );
将文件中指定大小的数据读入指定的缓冲区,返回向缓冲区传输的字节数。
u       void Write( const void* lpBuf, UINT nCount );
此函数将缓冲区的数据写到文件中。
u       LONG Seek( LONG lOff, UINT nFrom );
定位文件指针的位置,若要定位的位置是合法的,返回从文件开始的偏移量。否则,返回值是不定的且激活一个CFileException对象。
u       函数void SeekToBegin( )和DWORD SeekToEnd( )分别将文件指针移动到文件开始和结尾位置,对于后者还将返回文件的大小。
7.2文档及其序列化
(3)获取文件的有关信息
BOOL GetStatus( CFileStatus& rStatus ) const;
static BOOL PASCAL GetStatus( LPCTSTR lpszFileName, CFileStatus& rStatus );
若指定文件的状态信息成功获得,该函数返回TRUE,否则返回FALSE。 rStatus用来存放文件状态信息,是一个CFileStatus结构类型,该结构具有下列成员:
CTime m_ctime             文件创建日期和时间
CTime m_mtime            文件最后一次修改日期和时间
CTime m_atime            文件最后一次访问日期和时间
LONG m_size             文件的逻辑大小字节数,就像DOS命令中DIR所显示的大小
BYTE m_attribute           文件属性
char m_szFullName[_MAX_PATH]   文件名
例如下面的代码:
CFile theFile;
char* szFileName = "c:\\test\\myfile.dat";
BOOL bOpenOK;
CFileStatus status;
if( CFile::GetStatus( szFileName, status ) )
{            bOpenOK = theFile.Open( szFileName, CFile::modeWrite );    }
else
{        bOpenOK = theFile.Open( szFileName, CFile::modeCreate | CFile::modeWrite );    }
7.2文档及其序列化
Ø     使用CArchive类
(1)CArchive类对象的创建和关闭
创建CArchive对象,有两种方法:一是通过框架隐式创建CArchive对象,另一是显式创建CArchive 对象。
(2)使用“<<”和“>>”运算符
(3)文件文本的读写
CArchive提供成员函数ReadString和WriteString用来从一个文件对象中读写一行文本,它们的原型如下:
Bool ReadString(CString& rString );
LPTSTR ReadString( LPTSTR lpsz, UINT nMax );
void WriteString( LPCTSTR lpsz );
7.2文档及其序列化
7.2.5使用简单数组集合类
Ø     简单数组集合类的构造及元素的添加
对简单数组集合类构造的方法都是一样的,都是使用各自的构造函数,它们的原型如下:
CByteArray            CByteArray( );
CDWordArray                 CDWordArray( );
CPtrArray              CPtrArray( );
CStringArray         CStringArray( );
CUIntArray            CUIntArray( );
CWordArray           CWordArray( );
为了有效使用内存,在使用简单数组集合类之前最好调用成员函数SetSize 设置此数组的大小,与其对应的函数是GetSize,用来返回数组的大小。它们的原型如下:
void SetSize( int nNewSize, int nGrowBy = -1 );
int GetSize( ) const;
向简单数组集合类添加一个元素,可使用成员函数Add和Append,它们的原型如下:
int Add( CObject* newElement );
int Append( const CObArray& src );
7.2文档及其序列化
Ø     访问简单数组集合类的元素
在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可使用“[]”操作符,例如:
//  CObArray::operator []示例
CObArray array;
CAge* pa;                     // CAge是一个用户类
array.Add( new CAge( 21 ) );         // 添加一个元素
array.Add( new CAge( 40 ) );         // 再添加一个元素
pa = (CAge*)array[0];           // 获取元素0
ASSERT( *pa == CAge( 21 ) );
array[0] = new CAge( 30 );         // 替换元素0;
ASSERT( *(CAge*) array[0] == CAge( 30 ) );
//  CObArray::GetAt示例
CObArray array;
array.Add( new CAge( 21 ) );        // 元素 0
array.Add( new CAge( 40 ) );         // 元素 1
ASSERT( *(CAge*) array.GetAt( 0 ) == CAge( 21 ) );
7.2文档及其序列化
Ø     删除简单数组集合类的元素
删除简单数组集合类中的元素一般需要进行以下几个步骤:
(1)使用函数GetSize()和整数下标值访问简单数组集合类中的元素。
(2)若对象元素是在堆中创建的,则使用delete删除每一个对象元素。
(3)调用函数RemoveAll()删除简单数组集合类中的所有元素。
例如,下面代码过程是一个CObArray的删除示例:
CObArray array;
CAge* pa1;
CAge* pa2;
array.Add( pa1 = new CAge( 21 ) );
array.Add( pa2 = new CAge( 40 ) );
ASSERT( array.GetSize() == 2 );
for (int i=0;iarray.RemoveAll();
函数RemoveAll是删除数组中的所有元素,而函数RemoveAt( int nIndex, int nCount = 1)则表示要删除数组中指定元素开始的指定数目的元素。
7.3视图及视图类
7.3.1一般视图类的使用
7.3视图及视图类
Ø     CFormView类
u       CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像CDiolog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认(DDX和DDV)。
u       CFormView是所有表单视(如CRecordView、CDaoRecordView、CHtmlView等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。
u       创建表单应用程序的基本方法除了在创建SDI/MDI的第六步中选择CFormView作为应用程序视图类的基类外。还可通过相关菜单命令来自动插入一个表单,其步骤如下:
(1)切换到ClassView标签项,在项目名称上右击鼠标按钮。从弹出的快捷菜单中选择“New Form”命令,或者直接在主菜单中选择“Insert”à“New Form...”菜单命令,弹出如图7.10的“New Form”对话框。
(2)在“New Form”对话框中,键入表单名称。如果想要表单支持“自动化”特性,则选择“Automation”单选框。在“Document Template Information”栏中,指定和表单并联的文档内容。如果想要更改文件扩展名或文档模板字串资源,则可按击[Change]按钮。
(3)单击[OK]按钮,这样,一个表单视图派生类的程序框架就被添加到用户程序中;此时,我们就可用对话框编辑器为表单增加一些控件。
7.3视图及视图类
Ø    CEditView类
CEditView类对象是一种视图,提供窗口编辑控制功能,可以执行简单文本操作。由于CEditView类自动封装上述功能的映射函数,因此只要在文档模板中使用CEditView类,那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可自动激活。
但CEditView仍然摆脱不了所有编辑控件的限制,如:
u       CEditView不具有所见即所得编辑功能。
u       CEditView只能将文本作单一字体的显示,不支持特殊格式的字符。
u       CEditView可容纳的文本总数有限,在32位Windows中最多不超过1M字节。
7.3视图及视图类
Ø      CRichEditView类
CRichEditView类使用了复文本编辑控件,因此它支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc和CRichEditCntrItem类一起使用,它们可实现一个完整的ActiveX包容器应用程序。
Ø      CHtmlView 类
CHtmlView 类是在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链接、统一资源定位(URL)导航器并维护历史列表等。
Ø     CScrollView类
CScrollView类不仅能直接支持视图的滚动操作,而且还能管理视口的大小和映射模式,并能响应滚动条消息、键盘消息以及鼠标滚轮消息。
滚动视图第一次被创建时,往往在重载的CView::OnInitialUpdate或CView::OnUpdate中调用CScrollView成员函数SetScrollSizes来设置相关参数,如映射模式、滚动逻辑窗口的大小、水平或垂直方向的滚动量等。如果仅需要视图具有自动缩放功能(而不具有滚动特性),则用CScrollView::SetScaleToFitSize函数代替SetScrollSizes即可。
7.3视图及视图类
7.3.2列表控件和列表视图
Ø     列表控件的风格及其修改
7.3视图及视图类
Ø     列表项的基本操作
u       函数SetImageList用来为列表控件设置一个关联的图像列表,其原型如下:
CImageList* SetImageList( CImageList* pImageList, int nImageList );
u       函数InsertItem用来向列表控件中插入一个列表项。该函数成功时返回新列表项的索引号,否则返回-1。函数原型如下:
int InsertItem( const LVITEM* pItem );
int InsertItem( int nItem, LPCTSTR lpszItem );
int InsertItem( int nItem, LPCTSTR lpszItem, int nImage );
pItem用来指定一个指向LVITEM结构的指针,其结构描述如下:
typedef struct _LVITEM
{        UINT           mask;                // 指明哪些参数有效
int              iItem;                // 列表项索引
int              iSubItem;                     // 子项索引
UINT             state;                // 列表项状态
UINT             stateMask;      // 指明state哪些位是有效的,-1全部有效
LPTSTR            pszText;                     // 列表项文本标签
int              cchTextMax;    // 文本大小
int              iImage;             // 在图像列表中列表项图标的索引号。
LPARAM           lParam;              // 32位值
int          iIndent;          // 项目缩进数量,1个数量等于1个图标的像素宽度
} LVITEM, FAR *LPLVITEM;
7.3视图及视图类
u       函数DeleteItem和DeleteAllItems分别用来删除指定的列表项和全部列表项,函数原型如下:
BOOL DeleteItem( int nItem );
BOOL DeleteAllItems( );
u       函数FindItem用来查寻列表项,函数成功查找时返回列表项的索引号,否则返回-1。其原型如下:
int FindItem( LVFINDINFO* pFindInfo, int nStart = -1 ) const;
pFindInfo表示要查找的信息,其结构描述如下:
typedef struct tagLVFINDINFO
{      UINT           flags;             // 查找方式
LPCTSTR     psz;               // 匹配的文本
LPARAM         lParam;          // 匹配的值
POINT           pt;                // 查找开始的位置坐标。
UINT             vkDirection;// 查找方向,用虚拟方向健值表示。
} LVFINDINFO, FAR* LPFINDINFO;
u       函数Arrange用来按指定方式重新排列列表项,其原型如下:
BOOL Arrange( UINT nCode );
7.3视图及视图类
u       函数InsertColumn用来向列表控件插入新的一列,函数成功调用后返回新的列的索引,否则返回-1。其原型如下:
int InsertColumn( int nCol, const LVCOLUMN* pColumn );
int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat = LVCFMT_LEFT,
int nWidth = -1, int nSubItem = -1 );
pColumn表示包含新列信息的LVCOLUMN结构地址,其结构描述如下:
typedef struct _LVCOLUMN
{        UINT           mask;                // 指明哪些参数有效
int          fmt;                   // 列的标题或子项文本格式
int           cx;                    // 列的像素宽度
LPTSTR           pszText;                     // 列的标题文本
int           cchTextMax;    // 列的标题文本大小
int           iSubItem;           // 和列相关的子项索引
int           iImage;              // 图像列表中的图像索引
int           iOrder;               // 列的序号,最左边的列为0
} LVCOLUMN, FAR *LPLVCOLUMN;
u       函数DeleteColumn用来从列表控件中删除一个指定的列,其原型如下:
BOOL DeleteColumn( int nCol );
u       GetItemCount用来返回列表控件中的列表项个数等。它们的原型如下:
BOOL SetColumnWidth( int nCol, int cx );
int GetItemCount( );
7.3视图及视图类
Ø     列表控件的消息
常用的列表控件消息有:
LVN_BEGINDRAG                  用户按左鼠拖动列表列表项
LVN_BEGINLABELEDIT           用户对某列表项标签进行编辑
LVN_COLUMNCLICK              某列被按击
LVN_ENDLABELEDIT            用户对某列表项标签结束编辑
LVN_ITEMACTIVATE              用户激活某列表项
LVN_ITEMCHANGED             当前列表项已被改变
LVN_ITEMCHANGING            当前列表项即将改变
LVN_KEYDOWN                     某键被按下
在用ClassWizard处理上述这些消息时,其消息处理函数参数中往往会出现NM_LISTVIEW结构,其定义如下:
typedef struct tagNMLISTVIEW
{        NMHDR             hdr;                   // 包含通知消息的结构
int               iItem;                 // 列表项索引,没有为-1
int               iSubItem;           // 子项索引,没有为0
UINT              uNewState;       // 新的项目状态
UINT             uOldState;          // 原来的项目状态
UINT              uChanged;       // 项目属性更改标志
POINT             ptAction;           // 事件发生的地点
LPARAM            lParam;              // 用户定义的32位值
} NMLISTVIEW, FAR *LPNMLISTVIEW;
7.3视图及视图类
[例Ex_Lview] 将当前文件夹中的文件用“大图标”、“小图标”、“列表视图”以及“报表视图”四种不同方式在列表视图中显示出来。当双击某个列表项时,还将该项的文本标签内容用消息对话框的形式显示出来。
(1)用MFC AppWziard创建一个单文档应用程序Ex_List,在创建的第六步将视图的基类选择为CListView。
(2)为CEx_ListView类添加下列成员函数和成员函数:
public:
CImageList            m_ImageList;
CImageList              m_ImageListSmall;
CStringArray m_strArray;
void SetCtrlStyle(HWND hWnd, DWORD dwNewStyle);
(3)将项目工作区窗口切换到ResourceView页面,打开Accelerator节点下的IDR_MAINFRAME,为其添加一个键盘加速键Ctrl+Shift+X,其ID号为ID_VIEW_CHANGE。
(4)用ClassWizard为CEx_ListView类添加ID_VIEW_CHANGE的COMMAND消息映射函数,并增加下列代码:
void CEx_ListView::OnViewChange()
{        static int nStyleIndex = 1;
DWORD style[4] = {LVS_REPORT, LVS_ICON, LVS_SMALLICON, LVS_LIST };
CListCtrl& m_ListCtrl = GetListCtrl();
SetCtrlStyle(m_ListCtrl.GetSafeHwnd(), style[nStyleIndex]);
nStyleIndex++;
if (nStyleIndex>3) nStyleIndex = 0;
}
7.3视图及视图类
(5)用ClassWizard为CEx_ListView类添加NM_DBLCLK消息映射函数,增加代码:
void CEx_ListView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult)
{          LPNMITEMACTIVATE lpItem = (LPNMITEMACTIVATE)pNMHDR;
int nIndex = lpItem->iItem;
if (nIndex >= 0)
{          CListCtrl& m_ListCtrl = GetListCtrl();
CString str = m_ListCtrl.GetItemText(nIndex, 0);
MessageBox(str);                        }
*pResult = 0;
}
(6)在CEx_ListView::OnInitialUpdate中添加下列代码:
void CEx_ListView::OnInitialUpdate()
{          CListView::OnInitialUpdate();
// 创建图像列表
m_ImageList.Create(32,32,ILC_COLOR8|ILC_MASK,1,200);
m_ImageListSmall.Create(16,16,ILC_COLOR8|ILC_MASK,1,200);
CListCtrl& m_ListCtrl = GetListCtrl();
m_ListCtrl.SetImageList(&m_ImageList,LVSIL_NORMAL);
m_ListCtrl.SetImageList(&m_ImageListSmall,LVSIL_SMALL);
LV_COLUMN listCol;
char* arCols[4]={"文件名", "大小", "类型", "修改日期"};
listCol.mask = LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;
7.3视图及视图类
for (int nCol=0; nCol<4; nCol++)
{          listCol.iSubItem = nCol;
listCol.pszText = arCols[nCol];
if (nCol == 1)
listCol.fmt = LVCFMT_RIGHT;
else
listCol.fmt = LVCFMT_LEFT;
m_ListCtrl.InsertColumn(nCol,&listCol);               }
// 查找当前目录下的文件
CFileFind finder;
BOOL bWorking = finder.FindFile("*.*");
int nItem = 0, nIndex, nImage;
CTime m_time;
CString str, strTypeName;
while (bWorking)
{      bWorking = finder.FindNextFile();
if (finder.IsArchived())
{          str = finder.GetFilePath();
SHFILEINFO fi;
SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO),
SHGFI_ICON|SHGFI_LARGEICON|SHGFI_TYPENAME);
strTypeName = fi.szTypeName;
nImage = -1;
7.3视图及视图类
for (int i=0; i{          if (m_strArray[i] == strTypeName)
{            nImage = i;        break;          }
}
if (nImage<0)
{          nImage = m_ImageList.Add(fi.hIcon);
SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO),
SHGFI_ICON|SHGFI_SMALLICON );
m_ImageListSmall.Add(fi.hIcon);
m_strArray.Add(strTypeName);
}
nIndex = m_ListCtrl.InsertItem(nItem,finder.GetFileName(),nImage);
DWORD dwSize = finder.GetLength();
if (dwSize> 1024)          str.Format("%dK", dwSize/1024);
else          str.Format("%d", dwSize);
m_ListCtrl.SetItemText(nIndex,1,str);
m_ListCtrl.SetItemText(nIndex,2,strTypeName);
finder.GetLastWriteTime(m_time) ;
m_ListCtrl.SetItemText(nIndex,3,m_time.Format("%Y-%m-%d"));
nItem++;
}
}
7.3视图及视图类
SetCtrlStyle(m_ListCtrl.GetSafeHwnd(), LVS_REPORT);
m_ListCtrl.SetColumnWidth(0, LVSCW_AUTOSIZE);// 设置列宽
m_ListCtrl.SetColumnWidth(1, 100);
m_ListCtrl.SetColumnWidth(2, LVSCW_AUTOSIZE);
m_ListCtrl.SetColumnWidth(3, 200);
}
(7)编译并运行,如图。
7.3视图及视图类
7.3.3树控件和树视图
Ø     树形视图的风格
7.3视图及视图类
Ø     树控件的常用操作
u       函数InsertItem用来向树控件插入一个新项,操作成功后,函数返回新项的句柄,否则返回NULL。函数原型如下:
HTREEITEM InsertItem( UINT nMask, LPCTSTR lpszItem,int nImage, int nSelectedImage,
UINT nState, UINT nStateMask, LPARAM lParam,
HTREEITEM hParent,  HTREEITEM hInsertAfter );
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT,
HTREEITEM hInsertAfter = TVI_LAST );
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage,
HTREEITEM hParent = TVI_ROOT,
HTREEITEM hInsertAfter = TVI_LAST );
u       函数DeleteItem和DeleteAllItems分别用来删除指定的项和全部的项。它们的原型如下:
BOOL DeleteAllItems( );
BOOL DeleteItem( HTREEITEM hItem );
u       函数Expand用来用来展开或收缩指定父项的所有子项,其原型如下:
BOOL Expand( HTREEETEM hItem, UINT nCode );
7.3视图及视图类
Ø     树形视图控件的通知消息
树视图可以用ClassWizard映射公共控件消息和树控件消息。常用的树控件消息有:
TVN_BEGINDRAG                     开始拖放操作
TVN_BEGINLABELEDIT         开始编辑标签
TVN_BEGINRDRAG                   鼠标右按钮开始拖放操作
TVN_ENDLABELEDIT                 标签编辑结束
TVN_ITEMEXPANDED               含有子项的父项已展开或收缩
TVN_ITEMEXPANDING          含有子项的父项将要展开或收缩
TVN_SELCHANGED                   当前选择项发生改变
TVN_SELCHANGING                  当前选择项将要发生改变
用ClassWizard处理上述这些消息时,其消息处理函数参数中往往会出现NM_TREEVIEW结构,定义如下:
typedef struct tagNMTREEVIEW
{      NMHDR         hdr;               // 含有通知代码的信息结构
UINT         action;         // 通知方式标志
TVITEM         itemOld;         // 原有项的信息
TVITEM         itemNew;    // 现在项的信息
POINT   ptDrag;                 // 事件产生时,鼠标的位置
} NMTREEVIEW, FAR *LPNMTREEVIEW;
7.3视图及视图类
[例Ex_Tree] 遍历本地磁盘所有的目录。
(1)用MFC AppWziard创建一个单文档应用程序Ex_Tree,在创建的第六步将视图的基类选择为CTreeView。
(2)按快捷键Ctrl+R,选定资源类型Icon,单击Import按钮,从外部调入六个图标,分别表示“我的电脑”、“软驱”、“硬盘”、“光驱”、“文件夹”以及“打开的文件夹”,相应的ID号设为IDI_MYCOMP、IDI_35FLOPPY、IDI_DRIVE、IDI_CDDRIVE、IDI_CLSDFOLD和IDI_OPENFOLD,如图。
7.3视图及视图类
(3)为CEx_TreeView类添加下列成员函数和成员函数:
public:
CImageList            m_ImageList;
CString           m_strPath;                 // 文件夹路径
void InsertFoldItem(HTREEITEM m_hItem);
void SetCtrlStyle(HWND hWnd, DWORD dwNewStyle);
InsertFoldItem函数的代码如下:
void CEx_TreeView::InsertFoldItem(HTREEITEM m_hItem)
{        CFileFind finder;
BOOL bWorking = finder.FindFile(m_strPath);
CString m_str;
while (bWorking)
{          bWorking = finder.FindNextFile();
if (finder.IsDirectory() && !finder.IsHidden())
{          m_str = finder.GetFileName();
m_str.TrimRight();
m_str.TrimLeft();
if ((m_str!=_T("."))&&(m_str!=_T("..")))
{          CTreeCtrl& m_TreeCtrl = GetTreeCtrl();
m_TreeCtrl.InsertItem(m_str,4,5,m_hItem);}
}
}
}
7.3视图及视图类
(4)为CEx_TreeView类添加TVN_ITEMSELECTED消息处理,增加代码:
void CEx_TreeView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)
{          NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
HTREEITEM m_hSelected = pNMTreeView->itemNew.hItem;
CTreeCtrl& m_TreeCtrl = GetTreeCtrl();
if ((!m_TreeCtrl.ItemHasChildren(m_hSelected))&&(m_hSelected)){
BOOL m_bIsTop = FALSE;
CString m_str;
m_strPath.Empty();
HTREEITEM m_hTemp = m_hSelected;
while (!m_bIsTop){
m_str = m_TreeCtrl.GetItemText(m_hTemp);
m_str.TrimLeft();
m_str.TrimRight();
m_strPath = m_str+"\\"+m_strPath;
m_TreeCtrl.GetParentItem(m_hTemp);
if(m_TreeCtrl.GetParentItem(m_TreeCtrl.GetParentItem(m_hTemp))){
m_hTemp = m_TreeCtrl.GetParentItem(m_hTemp);
}else m_bIsTop=TRUE;       }
m_strPath += _T("*.");
InsertFoldItem(m_hSelected);
m_TreeCtrl.Expand(m_hSelected,TVE_EXPAND);          }
*pResult = 0;
}
7.3视图及视图类
(5)在CEx_TreeView::OnInitialUpdate函数中添加下列代码:
void CEx_TreeView::OnInitialUpdate()
{        CTreeView::OnInitialUpdate();
CTreeCtrl& m_TreeCtrl = GetTreeCtrl();
m_ImageList.Create(16, 16, ILC_COLOR|ILC_MASK, 6, 0);
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_MYCOMP));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_35FLOPPY));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_DRIVE));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_CDDRIVE));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_CLSDFOLD));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_OPENFOLD));
m_TreeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL);
HTREEITEM m_hRootItem, m_hTempItem;
m_hRootItem = m_TreeCtrl.InsertItem("我的电脑",0,0);
// 查找并检测已有的驱动器
DWORD dwDrives = GetLogicalDrives();
UINT uType;
CString m_strDrive, m_strTemp;
7.3视图及视图类
for( int i = 0; i < 32; i++ )
{        DWORD dwTemp = dwDrives;
if( dwTemp & 1 )
{          m_strTemp.Format(_T("%c:\\."),'A'+(char)i);
uType = GetDriveType(m_strTemp);
m_strTemp.Format(_T(" %c: "),'A'+(char)i);
switch(uType){
case DRIVE_REMOVABLE:
m_TreeCtrl.InsertItem(m_strTemp,1,1,m_hRootItem);
break;
case DRIVE_FIXED:m_hTempItem = m_TreeCtrl.InsertItem(m_strTemp,2,2,m_hRootItem);
break;
case DRIVE_CDROM:
m_TreeCtrl.InsertItem(m_strTemp,3,3,m_hRootItem);
break;
}
}
dwDrives = dwDrives >> 1;
}
SetCtrlStyle(m_TreeCtrl.GetSafeHwnd(),
TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS);
m_TreeCtrl.Expand(m_hRootItem,TVE_EXPAND);
}
7.3视图及视图类
(6)编译并运行,如图。
7.4文档视图结构
7.4.1文档与视图的相互作用
Ø     CView::GetDocument函数
MFC AppWizard产生CView的用户派生类时,同时创建一个安全类型的GetDocument函数,返回的是指向派生文档类的指针。该函数是一个内联(inline)函数,类似于下面的形式:
CMyDoc* CMyView::GetDocument() // non-debug version is inline
{        ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMyDoc)));
// “断言”m_pDocument指针可以指向的CMyDoc类是一个RUNTIME_CLASS类型
return (CMyDoc*)m_pDocument;
}
Ø      CDocument::UpdateAllViews函数
UpdateAllViews函数的原型如下。
void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL );
Ø      CView::OnUpdate函数
应用程序调用了CDocument::UpdateAllViews函数时,应用程序框架就会相应地调用该函数。
virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint );
7.4文档视图结构
Ø      CView::OnInitialUpdate函数
应用程序被启动时,或从“文件”菜单中选择了“新建”或“打开”时,CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint = 0, pHint = NULL)的OnUpdate函数之外,没做其他任何事情。
可以重载此函数对文档所需信息进行初始化操作。如果应用程序中的文档大小是动态的,那么就可在文档每次改变时调用OnUpdate来更新视图的滚动范围。
Ø     CDocument::OnNewDocument函数
在SDI应用程序中,从“文件”菜单中选择“新建”命令时,框架将先构造一个文档对象,然后调用该虚函数。
MFC AppWizard为用户的派生文档类自动产生了重载的OnNewDocument函数,如下面的代码:
BOOL CMyDoc::OnNewDocument()
{     if (!CDocument::OnNewDocument())        // 注意一定要保证对基类函数的调用,
return FALSE;
// Do initialization of new document here.
return TRUE;
}
7.4文档视图结构
7.4.2应用程序对象指针的互调
Ø     从文档类中获取视图对象指针
在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数GetFirstViewPosition和GetNextView来定位相应的视图对象。
GetFirstViewPosition函数用来获得与文档类相关联的视图列表中第一个可见视图的位置,GetNextView函数用来获取指定视图位置的视图类指针,并将此视图位置移动下一个位置,若没有下一个视图,则视图位置为NULL。原型如下:
virtual POSITION GetFirstViewPosition( ) const;
virtual CView* GetNextView( POSITION& rPosition ) const;
例如,使用CDocument::GetFirstViewPosition和GetNextView重绘每个视图
void CMyDoc::OnRepaintAllViews()
{        POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{        CView* pView = GetNextView(pos);
pView->UpdateWindow();
}
}
//  实现上述功能也可直接调用UpdateAllViews(NULL);
7.4文档视图结构
Ø     从视图类中获取文档对象和主框架对象指针
函数CWnd::GetParentFrame可实现从视图类中获取主框架指针,原型:
CFrameWnd* GetParentFrame( ) const;
Ø     在主框架类中获取视图对象指针
CFrameWnd类的GetActiveView函数原型如下:
CView* GetActiveView( ) const;
在框架类中可直接调用CFrameWnd::GetActiveDocument函数获得当前活动的文档对象指针。
在同一个应用程序的任何对象中,可通过全局函数AfxGetApp()来获得指向应用程序对象的指针。
7.4文档视图结构
7.4.3切分窗口
Ø      静态切分和动态切分
对“静态切分”窗口,窗口第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,但可以移动切分条来调整窗格的大小。
对“动态切分”窗口,允许在任何时候对窗口进行切分,既可以通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分框对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类。切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新增加的视图对象被动态创建;当视图沿着两个方向被切分时,新增加的三个视图对象则被动态创建。取消切分时,所有新增加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失。
Ø      切分窗口的CSplitterWnd类
成员函数Create和CreateStatic用来创建“动态切分”和“静态切分”的文档窗口,原型:
BOOL Create( CWnd* pParentWnd, int nMaxRows, int nMaxCols, SIZE sizeMin,
CCreateContext* pContext,
DWORD dwStyle = WS_CHILD | WS_VISIBLE |WS_HSCROLL | WS_VSCROLL | SPLS_DYNAMIC_SPLIT,
UINT nID = AFX_IDW_PANE_FIRST );
BOOL CreateStatic( CWnd* pParentWnd, int nRows, int nCols,
DWORD dwStyle = WS_CHILD | WS_VISIBLE,
UINT nID = AFX_IDW_PANE_FIRST );
7.4文档视图结构
[例Ex_ SplitSDI] 将SDI文档窗口静态分成3 x 2个窗格:
(1)用MFC AppWizard创建一个单文档项目Ex_SplitSDI。
(2)打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义:
protected:  // control bar embedded members
CStatusBar          m_wndStatusBar;
CToolBar            m_wndToolBar;
CSplitterWnd         m_wndSplitter;
(3)用ClassWizard创建一个新的视图类CDemoView(基类为CView)用于与静态切分的窗格相关联。
7.4文档视图结构
(5) 为CMainFrame类添加OnCreateClient消息函数,增加代码:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs,  CCreateContext* pContext)
{      CRect rc;
GetClientRect(rc);
CSize paneSize(rc.Width()/2-16,rc.Height()/3-16);                      m_wndSplitter.CreateStatic(this,3,2);                         m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CDemoView),
paneSize,pContext);                                m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDemoView),
paneSize,pContext);
m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CDemoView),
paneSize,pContext);
m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CDemoView),
paneSize,pContext);
m_wndSplitter.CreateView(2,0,RUNTIME_CLASS(CDemoView),
paneSize,pContext);
m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView),
paneSize,pContext);
return TRUE;
}
7.4文档视图结构
(5)在MainFrm.cpp源文件的开始处,添加视图类CDemoView的包含文件:
#include "DemoView.h"
(6)编译并运行,如图。
7.4文档视图结构
值得注意的是:
u       调用CreateStatic函数创建静态切分窗口后,必须将每个窗格用CreateView函数指定相关联的视图类。
u       由于切分功能只应用于文档窗口,因而在MDI添加该功能时应在文档子窗口类CChildFrame中进行操作。
u       动态切分功能的添加过程比静态切分要简单得多,它不需要重新为窗格指定其它视图类,因为动态切分窗口的所有窗格共享同一个视图。若在文档窗口添加动态切分的功能可在MFC AppWizard创建SDI(或MDI)应用程序过程中进行设置。在“Step 4”对话框中单击[Advanced]按钮,再单击Window Styles标签,然后选择Use Split Window即可。应用程序编译运行之后,还在“查看”菜单中包括一个“分隔”菜单命令,如图。
7.4文档视图结构
7.4.4一档多视
MFC对于“一档多视”提供下列三个模式:
(1)在各自MDI文档窗口中包含同一个视图类的多个视图对象。
有时,想要应用程序能为同一个文档打开另一个文档窗口,以便同时使用两个文档窗口来查看文档的不同部分内容。用MFC AppWizard创建的MDI应用程序支持这种模式,选择“窗口”菜单的“新建窗口”命令时,系统就会为第一个文档窗口创建一个副本。
(2)在同一个文档窗口中包含同一个视图类的多个视图对象。
这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。
(3)在单独一个文档窗口中包含不同视图类的多个视图对象。
实验13的示例(项目名为Ex_Rect)就是在MDI中为同一个文档数据提供两种不同的显示和编辑方式,如图。
7.4文档视图结构
u       几个视图之间的数据传输是通过CDocument::UpdateAllViews和CView::OnUpdate的相互作用来实现的。例如:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{      ...
GetDocument()->UpdateAllViews(NULL, 1, (CObject*)&m_ptDraw);
...
}

void CEx_RectView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{      if (lHint == 1)
{        ...
}
}
是一一对应的。
u       在为文档创建另一个视图时,该视图的CView::OnInitialUpdate将被调用,因此该函数是放置初始化的最好地方。
u       为了能及时更新并保存文档数据,相应的数据成员应在用户文档类中定义。这样,由于所有的视图类都可与文档类进行交互,因而可以共享这些数据。