第 7章 框架窗口、文档和视图
7.1框架窗口
7.2文档及其序列化作业 1
7.3视图及视图类
7.4文档视图结构作业 2
7.1框架窗口
7.1.1主窗口和文档窗口主窗口是应用程序直接放置在桌面上的那个窗口,每个应用程序只能有一个主窗口,主窗口的标题栏上往往显示应用程序的名称。
文档窗口对于 SDI程序来说,它和主窗口是一致的,即主窗口就是文档窗口;
而对于 MDI程序,文档窗口是主窗口的子窗口,如图。文档窗口一般都有相应的可见边框,它的客户区是由相应的视图来构成的,因此可以说视图是文档窗口内的子窗口。文档窗口时刻跟踪当前处于活动状态的视图的变化,并将用户或系统产生的命令消息传递给当前活动视图。而主窗口负责管理各个用户交互对象并根据用户操作相应地创建或更新文档窗口及其视图。
图 7.1 MDI应用程序的框架窗口文档窗口 主窗口
7.1框架窗口
7.1.2窗口风格的设置
窗口风格窗口风格通常有一般和扩展两种形式。这两种形式的窗口风格可在函数
CWnd::Create或 CWnd::CreateEx参数中指定,CreateEx函数可同时支持以上两种风格,而 CWnd::Create只能指定窗口的一般风格。需要说明的是,
对于控件和对话框这样的窗口来说,它们的窗口风格可直接通过其属性对话框来设置。
需要说明的是,除了上述风格外,框架窗口还有以下三个自己的风格。它们都可以在 PreCreateWindow重载函数中指定。
FWS_ADDTOTITLE
该风格指定相关的信息如文档名添加到框架窗口标题的后面。
FWS_PREFIXTITLE
该风格使得框架窗口标题中的文档名显示在应用程序名之前。
FWS_SNAPTOBARS
该风格用来调整窗口的大小,使它刚好包含了框架窗口中的控制栏。
7.1框架窗口
用 MFC AppWizard设置图 7.2 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.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改变窗口的大小和位置
SetWindowPos不仅可以改变窗口的大小、位置,还可以改变所有窗口在堆栈排列的次序 (Z次序 ),这个次序是根据它们在屏幕出现的先后来确定的。
函数 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表示窗口新的大小和位置。
使用上述两个函数把主窗口移动到屏幕的 (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);
改变窗口的大小和位置的 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.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.5 指定标识值
7.2文档及其序列化
(4)重新编译并运行,如图。测试后还发现,打开,或,保存,对话框中的
,文件类型,框中显示出文档字串资源定义的类型名,如图 。
文档资源字串的内容也可在文档应用程序创建时的 Advanced Options对话框中指定,如图,图中数字表示该项的含义与表中对应串号的含义相同。
图 7.7 Ex_Mdi运行结果图 7.8 Advanced Options对话框
0
4 5
62
1 3
图 7.6,新建”对话框
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<sizeof(m_ch); i++) ar>>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对象的内容保存在文档中。
需要说明的是:
只有在保存文档之前还没有存过盘或读取的文档是,只读,的,
OnFileSave函数才会弹出通用,保存,对话框。否则,只执行第二步。
,文件 (File)”菜单中还有一个,另存为 (Save As)”命令,它是与文档类
CDocument的 OnFileSaveAs函数相关联。不管文档有没有保存过,
OnFileSaveAs都有会执行上述两个步骤。
可以用 ClassWizard来重载 CDocument::OnSaveDocument函数,并可在
Serialize函数体的 ar.IsStorinr()为,真,的条件语句处添加代码来在文档中保存用户自己的数据,
7.2文档及其序列化
关闭文档试图关闭文档时,应用程序会根据文档是否修改进一步完成下列任务:
若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。若用户选择,是,,则应用程序执行 OnFileSave过程。
调用 CDocument::OnCloseDocument虚成员函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类 CDocument的
DeleteContents清除文档数据。
MFC应用程序通过 CDocument的数据成员 m_bModified的逻辑值来判断用户是否对文档进行修改,如果 m_bModified为,真,,则表示文档被修改。
protected类型的 m_bModified标记是通过 CDocument的 SetModifiedFlag和
IsModified成员函数被访问的。当文档创建、从磁盘中读出以及文档存盘时,
文档的这个标记就被置为 FALSE(假 );而当文档数据被修改时,必须使用
SetModifiedFlag函数将该标记置为 TRUE。关闭文档时,应用程序才会显示询问消息对话框。
7.2文档及其序列化
7.2.4使用 CFile和 CArchive类
使用 CFile类
(1)文件的打开和关闭在 MFC中,使用 CFile打开一个文件通常使用下列两个步骤:
构造一个不带指定任何参数的 CFile对象;
调用成员函数 Open并指定文件路径以及文件标志。
Open函数的原型如下:
BOOL Open( LPCTSTR lpszFileName,UINT nOpenFlags,CFileException*
pError = NULL );
7.2文档及其序列化
(2)文件的读写和定位
CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:
UINT Read( void* lpBuf,UINT nCount );
将文件中指定大小的数据读入指定的缓冲区,返回向缓冲区传输的字节数。
void Write( const void* lpBuf,UINT nCount );
此函数将缓冲区的数据写到文件中。
LONG Seek( LONG lOff,UINT nFrom );
定位文件指针的位置,若要定位的位置是合法的,返回从文件开始的偏移量。
否则,返回值是不定的且激活一个 CFileException对象。
函数 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;i<array.GetSize();i++) delete array.GetAt(i);
array.RemoveAll();
函数 RemoveAll是删除数组中的所有元素,而函数 RemoveAt( int nIndex,
int nCount = 1)则表示要删除数组中指定元素开始的指定数目的元素。
作业
1。 P376:1,6,10;
2.p434:实验 12
7.3视图及视图类
7.3.1一般视图类的使用图 7.9,Step 6”对话框
7.3视图及视图类
CFormView类
CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。
像 CDiolog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认 (DDX和 DDV)。
CFormView是所有表单视 (如 CRecordView,CDaoRecordView,CHtmlView
等 )的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。
创建表单应用程序的基本方法除了在创建 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仍然摆脱不了所有编辑控件的限制,如:
CEditView不具有所见即所得编辑功能。
CEditView只能将文本作单一字体的显示,不支持特殊格式的字符。
CEditView可容纳的文本总数有限,在 32位 Windows中最多不超过 1M字节。
图 7.10,New Form”对话框
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视图及视图类
列表项的基本操作
函数 SetImageList用来为列表控件设置一个关联的图像列表,其原型如下:
CImageList* SetImageList( CImageList* pImageList,int nImageList );
函数 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视图及视图类
函数 DeleteItem和 DeleteAllItems分别用来删除指定的列表项和全部列表项,
函数原型如下:
BOOL DeleteItem( int nItem );
BOOL DeleteAllItems( );
函数 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;
函数 Arrange用来按指定方式重新排列列表项,其原型如下:
BOOL Arrange( UINT nCode );
7.3视图及视图类
函数 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;
函数 DeleteColumn用来从列表控件中删除一个指定的列,其原型如下:
BOOL DeleteColumn( int nCol );
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<m_strArray.GetSize(); 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.11 Ex_List运行结果
7.3视图及视图类
7.3.3树控件和树视图
树形视图的风格
7.3视图及视图类
树控件的常用操作
函数 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 );
函数 DeleteItem和 DeleteAllItems分别用来删除指定的项和全部的项。它们的原型如下:
BOOL DeleteAllItems( );
BOOL DeleteItem( HTREEITEM hItem );
函数 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.12 从外部调入的图标
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.13 Ex_Tree运行结果
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.14 单文档的静态切分第 0,0窗格第 1,0窗格第 2,0窗格第 0,1窗格第 1,1窗格第 2,1窗格
7.4文档视图结构值得注意的是:
调用 CreateStatic函数创建静态切分窗口后,必须将每个窗格用 CreateView
函数指定相关联的视图类。
由于切分功能只应用于文档窗口,因而在 MDI添加该功能时应在文档子窗口类 CChildFrame中进行操作。
动态切分功能的添加过程比静态切分要简单得多,它不需要重新为窗格指定其它视图类,因为动态切分窗口的所有窗格共享同一个视图。若在文档窗口添加动态切分的功能可在 MFC AppWizard创建 SDI(或 MDI)应用程序过程中进行设置。在,Step 4”对话框中单击 [Advanced]按钮,再单击 Window
Styles标签,然后选择 Use Split Window即可。应用程序编译运行之后,还在,查看,菜单中包括一个,分隔,菜单命令,如图。
图 7.15 SDI动态切分窗 口水平切分框垂直切分框切分菜单命令
7.4文档视图结构
7.4.4一档多视
MFC对于,一档多视,提供下列三个模式:
(1)在各自 MDI文档窗口中包含同一个视图类的多个视图对象。
有时,想要应用程序能为同一个文档打开另一个文档窗口,以便同时使用两个文档窗口来查看文档的不同部分内容。用 MFC AppWizard创建的 MDI应用程序支持这种模式,选择,窗口,菜单的,新建窗口,命令时,系统就会为第一个文档窗口创建一个副本。
(2)在同一个文档窗口中包含同一个视图类的多个视图对象。
这种模式实际上是使用,切分窗口,机制使 SDI应用程序具有多视的特征。
(3)在单独一个文档窗口中包含不同视图类的多个视图对象。
实验 13的示例 (项目名为 Ex_Rect)就是在 MDI中为同一个文档数据提供两种不同的显示和编辑方式,如图。
图 7.16 Ex_Rect运行结果
7.4文档视图结构
几个视图之间的数据传输是通过 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)
{,..
}
}
是一一对应的。
在为文档创建另一个视图时,该视图的 CView::OnInitialUpdate将被调用,
因此该函数是放置初始化的最好地方。
为了能及时更新并保存文档数据,相应的数据成员应在用户文档类中定义。
这样,由于所有的视图类都可与文档类进行交互,因而可以共享这些数据。
作业
1。 P377:14
7.1框架窗口
7.2文档及其序列化作业 1
7.3视图及视图类
7.4文档视图结构作业 2
7.1框架窗口
7.1.1主窗口和文档窗口主窗口是应用程序直接放置在桌面上的那个窗口,每个应用程序只能有一个主窗口,主窗口的标题栏上往往显示应用程序的名称。
文档窗口对于 SDI程序来说,它和主窗口是一致的,即主窗口就是文档窗口;
而对于 MDI程序,文档窗口是主窗口的子窗口,如图。文档窗口一般都有相应的可见边框,它的客户区是由相应的视图来构成的,因此可以说视图是文档窗口内的子窗口。文档窗口时刻跟踪当前处于活动状态的视图的变化,并将用户或系统产生的命令消息传递给当前活动视图。而主窗口负责管理各个用户交互对象并根据用户操作相应地创建或更新文档窗口及其视图。
图 7.1 MDI应用程序的框架窗口文档窗口 主窗口
7.1框架窗口
7.1.2窗口风格的设置
窗口风格窗口风格通常有一般和扩展两种形式。这两种形式的窗口风格可在函数
CWnd::Create或 CWnd::CreateEx参数中指定,CreateEx函数可同时支持以上两种风格,而 CWnd::Create只能指定窗口的一般风格。需要说明的是,
对于控件和对话框这样的窗口来说,它们的窗口风格可直接通过其属性对话框来设置。
需要说明的是,除了上述风格外,框架窗口还有以下三个自己的风格。它们都可以在 PreCreateWindow重载函数中指定。
FWS_ADDTOTITLE
该风格指定相关的信息如文档名添加到框架窗口标题的后面。
FWS_PREFIXTITLE
该风格使得框架窗口标题中的文档名显示在应用程序名之前。
FWS_SNAPTOBARS
该风格用来调整窗口的大小,使它刚好包含了框架窗口中的控制栏。
7.1框架窗口
用 MFC AppWizard设置图 7.2 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.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改变窗口的大小和位置
SetWindowPos不仅可以改变窗口的大小、位置,还可以改变所有窗口在堆栈排列的次序 (Z次序 ),这个次序是根据它们在屏幕出现的先后来确定的。
函数 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表示窗口新的大小和位置。
使用上述两个函数把主窗口移动到屏幕的 (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);
改变窗口的大小和位置的 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.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.5 指定标识值
7.2文档及其序列化
(4)重新编译并运行,如图。测试后还发现,打开,或,保存,对话框中的
,文件类型,框中显示出文档字串资源定义的类型名,如图 。
文档资源字串的内容也可在文档应用程序创建时的 Advanced Options对话框中指定,如图,图中数字表示该项的含义与表中对应串号的含义相同。
图 7.7 Ex_Mdi运行结果图 7.8 Advanced Options对话框
0
4 5
62
1 3
图 7.6,新建”对话框
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<sizeof(m_ch); i++) ar>>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对象的内容保存在文档中。
需要说明的是:
只有在保存文档之前还没有存过盘或读取的文档是,只读,的,
OnFileSave函数才会弹出通用,保存,对话框。否则,只执行第二步。
,文件 (File)”菜单中还有一个,另存为 (Save As)”命令,它是与文档类
CDocument的 OnFileSaveAs函数相关联。不管文档有没有保存过,
OnFileSaveAs都有会执行上述两个步骤。
可以用 ClassWizard来重载 CDocument::OnSaveDocument函数,并可在
Serialize函数体的 ar.IsStorinr()为,真,的条件语句处添加代码来在文档中保存用户自己的数据,
7.2文档及其序列化
关闭文档试图关闭文档时,应用程序会根据文档是否修改进一步完成下列任务:
若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保存。若用户选择,是,,则应用程序执行 OnFileSave过程。
调用 CDocument::OnCloseDocument虚成员函数,关闭所有与该文档相关联的文档窗口及相应的视图,调用文档类 CDocument的
DeleteContents清除文档数据。
MFC应用程序通过 CDocument的数据成员 m_bModified的逻辑值来判断用户是否对文档进行修改,如果 m_bModified为,真,,则表示文档被修改。
protected类型的 m_bModified标记是通过 CDocument的 SetModifiedFlag和
IsModified成员函数被访问的。当文档创建、从磁盘中读出以及文档存盘时,
文档的这个标记就被置为 FALSE(假 );而当文档数据被修改时,必须使用
SetModifiedFlag函数将该标记置为 TRUE。关闭文档时,应用程序才会显示询问消息对话框。
7.2文档及其序列化
7.2.4使用 CFile和 CArchive类
使用 CFile类
(1)文件的打开和关闭在 MFC中,使用 CFile打开一个文件通常使用下列两个步骤:
构造一个不带指定任何参数的 CFile对象;
调用成员函数 Open并指定文件路径以及文件标志。
Open函数的原型如下:
BOOL Open( LPCTSTR lpszFileName,UINT nOpenFlags,CFileException*
pError = NULL );
7.2文档及其序列化
(2)文件的读写和定位
CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:
UINT Read( void* lpBuf,UINT nCount );
将文件中指定大小的数据读入指定的缓冲区,返回向缓冲区传输的字节数。
void Write( const void* lpBuf,UINT nCount );
此函数将缓冲区的数据写到文件中。
LONG Seek( LONG lOff,UINT nFrom );
定位文件指针的位置,若要定位的位置是合法的,返回从文件开始的偏移量。
否则,返回值是不定的且激活一个 CFileException对象。
函数 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;i<array.GetSize();i++) delete array.GetAt(i);
array.RemoveAll();
函数 RemoveAll是删除数组中的所有元素,而函数 RemoveAt( int nIndex,
int nCount = 1)则表示要删除数组中指定元素开始的指定数目的元素。
作业
1。 P376:1,6,10;
2.p434:实验 12
7.3视图及视图类
7.3.1一般视图类的使用图 7.9,Step 6”对话框
7.3视图及视图类
CFormView类
CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。
像 CDiolog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和对话框数据确认 (DDX和 DDV)。
CFormView是所有表单视 (如 CRecordView,CDaoRecordView,CHtmlView
等 )的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。
创建表单应用程序的基本方法除了在创建 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仍然摆脱不了所有编辑控件的限制,如:
CEditView不具有所见即所得编辑功能。
CEditView只能将文本作单一字体的显示,不支持特殊格式的字符。
CEditView可容纳的文本总数有限,在 32位 Windows中最多不超过 1M字节。
图 7.10,New Form”对话框
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视图及视图类
列表项的基本操作
函数 SetImageList用来为列表控件设置一个关联的图像列表,其原型如下:
CImageList* SetImageList( CImageList* pImageList,int nImageList );
函数 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视图及视图类
函数 DeleteItem和 DeleteAllItems分别用来删除指定的列表项和全部列表项,
函数原型如下:
BOOL DeleteItem( int nItem );
BOOL DeleteAllItems( );
函数 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;
函数 Arrange用来按指定方式重新排列列表项,其原型如下:
BOOL Arrange( UINT nCode );
7.3视图及视图类
函数 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;
函数 DeleteColumn用来从列表控件中删除一个指定的列,其原型如下:
BOOL DeleteColumn( int nCol );
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<m_strArray.GetSize(); 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.11 Ex_List运行结果
7.3视图及视图类
7.3.3树控件和树视图
树形视图的风格
7.3视图及视图类
树控件的常用操作
函数 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 );
函数 DeleteItem和 DeleteAllItems分别用来删除指定的项和全部的项。它们的原型如下:
BOOL DeleteAllItems( );
BOOL DeleteItem( HTREEITEM hItem );
函数 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.12 从外部调入的图标
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.13 Ex_Tree运行结果
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.14 单文档的静态切分第 0,0窗格第 1,0窗格第 2,0窗格第 0,1窗格第 1,1窗格第 2,1窗格
7.4文档视图结构值得注意的是:
调用 CreateStatic函数创建静态切分窗口后,必须将每个窗格用 CreateView
函数指定相关联的视图类。
由于切分功能只应用于文档窗口,因而在 MDI添加该功能时应在文档子窗口类 CChildFrame中进行操作。
动态切分功能的添加过程比静态切分要简单得多,它不需要重新为窗格指定其它视图类,因为动态切分窗口的所有窗格共享同一个视图。若在文档窗口添加动态切分的功能可在 MFC AppWizard创建 SDI(或 MDI)应用程序过程中进行设置。在,Step 4”对话框中单击 [Advanced]按钮,再单击 Window
Styles标签,然后选择 Use Split Window即可。应用程序编译运行之后,还在,查看,菜单中包括一个,分隔,菜单命令,如图。
图 7.15 SDI动态切分窗 口水平切分框垂直切分框切分菜单命令
7.4文档视图结构
7.4.4一档多视
MFC对于,一档多视,提供下列三个模式:
(1)在各自 MDI文档窗口中包含同一个视图类的多个视图对象。
有时,想要应用程序能为同一个文档打开另一个文档窗口,以便同时使用两个文档窗口来查看文档的不同部分内容。用 MFC AppWizard创建的 MDI应用程序支持这种模式,选择,窗口,菜单的,新建窗口,命令时,系统就会为第一个文档窗口创建一个副本。
(2)在同一个文档窗口中包含同一个视图类的多个视图对象。
这种模式实际上是使用,切分窗口,机制使 SDI应用程序具有多视的特征。
(3)在单独一个文档窗口中包含不同视图类的多个视图对象。
实验 13的示例 (项目名为 Ex_Rect)就是在 MDI中为同一个文档数据提供两种不同的显示和编辑方式,如图。
图 7.16 Ex_Rect运行结果
7.4文档视图结构
几个视图之间的数据传输是通过 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)
{,..
}
}
是一一对应的。
在为文档创建另一个视图时,该视图的 CView::OnInitialUpdate将被调用,
因此该函数是放置初始化的最好地方。
为了能及时更新并保存文档数据,相应的数据成员应在用户文档类中定义。
这样,由于所有的视图类都可与文档类进行交互,因而可以共享这些数据。
作业
1。 P377:14