第8章视图应用框架 8.1文档与视图的相互作用 8.2一般视图框架 8.3列表视图框架 8.4树视图框架 8.5切分视图框架 8.6综合应用
8.1文档与视图的相互作用 1. CView::GetDocument函数 视图对象只有一个与之相联系的文档对象,它所包含的GetDocument函数允许应用程序由视图得到与之相联系的文档。假设视图对象接收到了一条消息,表示用户输入了新的数据,此时,视图就必须通知文档对象对其内部数据进行相应的更新。GetDocument函数返回的是指向文档的指针,利用它用户就可以对文档类成员函数及公共数据成员进行访问。 当MFC AppWizard产生应用程序CView类时,它同时也创建一个安全类型的GetDocument函数,它返回的是指向用户派生文档类的指针。该函数是一个内联(inline)函数,如下面的代码: CEx_SDIDoc* CEx_SDIView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx_SDIDoc))); // “断言”m_pDocument指针所指向的CEx_SDIDoc类是一个RUNTIME_CLASS类型 return (CEx_SDIDoc*)m_pDocument; }
8.1文档与视图的相互作用 2. CDocument::UpdateAllViews函数 void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ); 其中,参数pSender表示视图指针,若在应用程序文档类的成员函数中调用该函数,则此参数应为NULL,若该函数被应用程序视图类中的成员函数调用,则此参数应为this。lHint通常表示更新视图时发送信息的提示标识值,pHint表示存贮信息的对象指针。 当UpdateAllViews函数被调用时,如果参数pSender指向某个特定的视图对象,那么除了该指定的视图之外,文档的所有其他视图的OnUpdate函数都会被调用。 CView::OnUpdate函数 这是一个虚函数。当应用程序调用了CDocument::UpdateAllViews函数时,视图应用框架就会相应地调用该函数。 virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint );
8.1文档与视图的相互作用 其中,参数pSender表示文档被更改的所在视图类指针,当为NULL时表示所有的视图需要更新。 默认的OnUpdate函数(lHint = 0, pHint = NULL)使得整个窗口矩形无效。如果用户想要视图的某部分无效,那么用户就要定义相关的提示(Hint)参数给出准确的无效区域;lHint和pHint含义同UpdateAllViews。 事实上,hint机制主要用来在视图中根据提示标识值来获取文档或其他视图传递来的数据,例如将文档的CPoint数据传给所有的视图类,则有下列语句: GetDocument()->UpdateAllViews(NULL, 1, (CObject *)&m_ptDraw); 4. CView::OnInitialUpdate函数 当应用程序被启动时,或当用户从“文件”菜单中选择了“新建”或“打开”时,该CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint = 0, pHint = NULL)的OnUpdate函数之外,没做其他任何事情。 但用户可以在视图类中重载此函数,然后对文档所需信息进行初始化操作。例如,如果用户应用程序中的文档大小是固定的,那么用户就可以在此重载函数中根据文档大小设置视图滚动范围;如果应用程序中的文档大小是动态的,那么用户就可在文档每次改变时调用OnUpdate来更新视图的滚动范围。
8.1文档与视图的相互作用 5. CDocument::OnNewDocument函数 在文档应用程序中,当用户从“文件”菜单中选择“新建”命令时,框架将首先构造一个文档对象,然后调用该虚函数,这里是设置文档数据成员初始值的好地方。当然,文档数据成员初始化处理还有其他的一些方法。例如,对于文档应用程序来说,用户还可在文档构造函数中添加初始化代码。 MFC AppWizard为用户的派生文档类自动产生了重载的OnNewDocument函数,如下面的代码: BOOL CMyDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) //注意一定要保证对基类函数的调用, return FALSE; // Do initialization of new document here. return TRUE; }
8.2一般视图框架 MFC中的CView类及其它的派生类封装了视图的各种不同的应用功能,它们为用户实现最新的Windows应用程序特性提供了很大的便利。这些视图类如表8.1所示。它们都可以作为文档应用程序中视图类的基类,其设置的方法是在MFC AppWizard(.exe)创建SDI/MDI的第6步中进行用户视图类的基类的选择。 表8.1 CView的派生类及其功能描述 类 名 功能描述 CScrollView 提供自动滚动或缩放功能 CFormView 提供可滚动的视图应用框架,它由对话框模板创建,并具有和对话框一样的设计方法。 CRecordView 提供表单视图直接与ODBC记录集对象关联;和所有的表单视图一样,CRecordView也是基于对话框模板设计的。 CDaoRecordView 提供表单视图直接与DAO记录集对象关联;其它同CRecordView。 CCtrlView 是CEditView、CListView、CTreeView和CRichEditView的基类,它们提供的文档视图结构也适用于Windows中的新控件。 CEditView 提供包含编辑控件的视图应用框架;支持文本的编辑、查找、替换以及滚动功能。 CRichEditView 提供包含复合编辑控件的视图应用框架;它除了CEditView功能外还支持字体、颜色、图表及OLE对象的嵌入等。 CListView 提供包含列表控件的视图应用框架;它类似于Windows 资源管理器的右侧窗口。 CTreeView 提供包含树状控件的视图应用框架;它类似于Windows 资源管理器的左侧窗口。
8.2一般视图框架 8.2.1 CEditView和CRichEditView [例Ex_Edit] 创建CEditView视图应用程序 (1)选择“文件”→“新建”菜单,在弹出的“新建”对话框中选择“工程”标签,选择MFC AppWizard(exe)的项目类型,指定项目工作文件夹位置,输入项目名Ex_Edit,单击[确定]按钮。 (2)在向导的第一步中,将应用程序类型选为“单文档”(SDI)。 (3)保留默认选项,单击[下一步]按钮,直到出现向导的第六步,将CEx_EditView的基类选为CEditView,如图8.1所示。 图8.1 更改CEx_EditView的基类
8.2一般视图框架 (4)单击[完成]按钮,编译运行,打开一个文档,结果如图8.2所示。 图8.2 Ex_Edit运行结果 需要说明的是,尽管CEditView类具有编辑框控件的功能,但它却不具有所见即所得编辑功能,而且只能将文本作单一字体的显示,不支持特殊格式的字符。
8.2一般视图框架 8.2.2 CFormView CFormView是一个非常有用的视图应用框架,它具有许多无模式对话框的特点。像 CDialog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它也支持对话框数据交换和数据校验(DDX和DDV)。CFormView还是所有表单视图类(如CRecordView、CDaoRecordView、CHtmlView等)的基类。 下面来看一个示例,它在一个单文档应用程序Ex_Form中添加表单后,将文档内容显示在表单视图的编辑框控件中。 [例Ex_Form] 添加表单视图应用框架 用MFC AppWizard创建一个默认的的单文档应用程序Ex_Form。 将项目工作区切换到ClassView页面,在顶层项名称上右击鼠标按钮。从弹出的快捷菜单中选择“New Form”命令,或者直接在主菜单中选择“插入”→“窗体”菜单命令,弹出 “新建窗体”(New Form)对话框,在名称(Name)框中输入CTextView,结果如图8.3所示。
8.2一般视图框架 图8.3 “新建窗体”对话框
8.2一般视图框架 (3)单击[确定]按钮,这样,一个表单视图派生类的程序框架就被添加到用户程序中。此时的界面如图8.4所示。右边是表单资源编辑器,它与对话框编辑器是一样的。 表单模板 图8.4 添加表单后的开发环境
8.2一般视图框架 (4)右击表单模板,从弹出的快捷菜单中选择“属性”命令,在表单属性对话框将其字体设置为“宋体,9号”。 (5)删除原来的静态文本控件,添加一个编辑框(用于文档内容的显示),在其“样式”属性对话框中,选中“多行”(Multiline)、“水平滚动条”(Horizontal scroll)、“垂直滚动条”(Vertical scroll)和“自动垂直滚动”(Auto VScroll)属性。保留默认编辑框的标识不变(IDC_EDIT1)。 (6)用MFC ClassWizard为IDC_EDIT1创建一个CString控件变量m_strText。 (7)为CEx_FormDoc类添加一个成员变量CString m_strContent。 (8)在CEx_FormDoc::Serialize函数中添加下列代码: void CEx_FormDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {} else CString str; m_strContent.Empty(); // 清空字符串变量内容; while (ar.ReadString(str)) m_strContent = m_strContent + str; m_strContent = m_strContent + "\r\n";// 在每行文本未尾添加回车换行 }
8.2一般视图框架 (9)用MFC ClassWizard为CTextView类添加OnUpdate函数的重载映射,当文档更新后,会自动通知其关联的视图类,并自动调用OnUpdate函数。在OnUpdate函数中添加下列代码: void CTextView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { CEx_FormDoc* pDoc = (CEx_FormDoc*)GetDocument(); m_strText = pDoc->m_strContent; UpdateData(FALSE); } (10)在TextView.cpp文件前面添加CEx_FormDoc类头文件包含: #include "Ex_Form.h" #include "TextView.h" #include "Ex_FormDoc.h"
8.2一般视图框架 (11)由于表单添加后,MFC会自动在CEx_FormApp::InitInstance函数中添加一个单文档模板代码,这样该单文档应用程序就有两个文档类型。事实上,在本例中只需要一个文档模板类型,故将InitInstance函数修改如下: BOOL CEx_FormApp::InitInstance() { { // BLOCK: doc template registration // Register the document template. Document templates serve // as the connection between documents, frame windows and views. // Attach this form to another document or frame window by changing … AddDocTemplate(pNewDocTemplate); } // 前面的这段文档模板代码删除 AfxEnableControlContainer(); pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CEx_FormDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CTextView)); // 修改成添加的表单视图类 AddDocTemplate(pDocTemplate); return TRUE;
8.2一般视图框架 (12)编译运行并测试,结果如图8.5所示。 图8.5 Ex_Form第一次运行结果
8.2一般视图框架 (13)在实际应用中,上图结果是有缺陷的,因为总希望显示文档内容的编辑框控件大小能和表单视图大小一样大。为此需要用MFC ClassWizard为CTextView类添加WM_SIZE(当窗口大小发生改变时产生)的消息映射,并添加下列代码: void CTextView::OnSize(UINT nType, int cx, int cy) { CFormView::OnSize(nType, cx, cy); CWnd* pWnd = GetDlgItem(IDC_EDIT1); // 获取编辑框窗口指针 if (pWnd) // 若窗口指针有效 pWnd->SetWindowPos( NULL,0,0,cx,cy,SWP_NOZORDER); } (14)再次编译运行并测试,结果如图8.6所示。 图8.6 Ex_Form运行结果
8.2一般视图框架 在Ex_Form创建的第6步,也可直接将CEx_FormView类的基础由CView改为CFormView,则上述过程更为简单些。需要注意的是,添加一个表单,就是添加一个视图框架,它包括新的文档模板资源、菜单栏以及新的单文档模板的创建等。 8.2.3 CHtmlView CHtmlView框架是将WebBrowser控件嵌入到文档视图结构中所形成的视图框架。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链接、统一资源定位(URL)导航器并维护历史列表等。 例如,用MFC AppWizard创建一个单文档应用程序Ex_Html,在向导的第6步将CEx_HtmlView的基类由CView改为CHtmlView。编译运行后,程序会自动打开网址http://www.microsoft.com/visualc/,结果如图8.7所示。 图8.7 Ex_Html运行结果
8.2一般视图框架 上述网址自动打开的代码是写在CEx_HtmlView::OnInitialUpdate函数中的: void CEx_HtmlView::OnInitialUpdate() { CHtmlView::OnInitialUpdate(); // TODO: This code navigates to a popular spot on the web. // change the code to go where you'd like. Navigate2(_T("http://www.microsoft.com/visualc/"),NULL,NULL); } 其中,函数CHtmlView:: Navigate2用来浏览指定的文件、网页或网址。 8.2.4 CScrollView CScrollView框架不仅能直接支持视图的滚动操作,而且还能管理视口的大小和映射模式,并能响应滚动条消息、键盘消息以及鼠标滚轮消息。 需要说明的是,当滚动视图应用程序框架创建(即在向导的第6步将视图基类改为CScrollView)后,MFC AppWizard会自动重载CView:: OnInitialUpdate,并在该函数调用CScrollView成员函数SetScrollSizes来设置相关参数,如映射模式、滚动逻辑窗口的大小、水平或垂直方向的滚动量等。如果仅需要视图具有自动缩放功能(而不具有滚动特性),则调用CScrollView::SetScaleToFitSize函数代替MFC AppWizar添加的SetScrollSizes函数调用代码。它们的原型如下: void SetScaleToFitSize( SIZE sizeTotal ); void SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault );
8.3列表视图框架 8.3.1 列表视图类型和样式 由于CListView框架是以列表控件CListCtrl为内建对象,因而它的类型和样式也就是列表控件的类型和样式。 1. 列表控件的类型 列表控件是一种极为有用的控件之一,它可以用 “大图标”、“小图标”、“列表视图”或“报表视图”等四种不同的方式来显示一组信息,如图8.8所示。 图8.8 列表控件样式
8.3列表视图框架 2. 列表控件的样式及其修改 列表控件的样式有两类,一类是一般样式,如表8.2所示。 2. 列表控件的样式及其修改 列表控件的样式有两类,一类是一般样式,如表8.2所示。 另一类是Visual C++在原有的基础上添加的扩展样式,如LVS_EX_FULLROWSELECT,表示整行选择,但它仅用于“报表视图”显示方式中。类似的常用的还有: LVS_EX_BORDERSELECT 用边框选择方式代替高亮显示列表项 LVS_EX_GRIDLINES 列表项各行显示线条(仅用于“报表视图”) 对于列表控件的一般风格的修改,可先调用GetWindowLong来获取当前风格,然后调用SetWindowLong重新设置新的风格。对于列表控件的扩展风格,可直接调用成员函数CListCtrl::SetExtendedStyle加以设置。 8.3.2 列表项的基本操作 (1)函数SetImageList用来为列表控件设置一个关联的图像列表,其原型如下: CImageList* SetImageList( CImageList* pImageList, int nImageList ); 其中,nImageList用来指定图像列表的类型,它可以是LVSIL_NORMAL(大图标)、LVSIL_SMALL(小图标)和LVSIL_STATE(表示状态的图像列表)。
8.3列表视图框架 需要说明的是,CImageList类来创建、显示或管理图像的,其最常见的操作有创建和添加,相应的函数原型如下: BOOL CImageList::Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow ); 其中,cx和cy用来指定图像的像素大小;nFlags表示要创建的图像类型,一般取其ILC_COLOR和ILC_MASK(指定屏蔽图像)的组合,默认的ILC_COLOR为ILC_COLOR4(16色),当然也可以是ILC_COLOR8(256色)、ILC_COLOR16(16位色)等;nInitial用来指定图像列表中最初的图像数目;nGrow表示当图像列表的大小发生改变时图像可以增加的数目。 int CImageList::Add( CBitmap* pbmImage, CBitmap* pbmMask ); int CImageList::Add( CBitmap* pbmImage, COLORREF crMask ); int CImageList::Add( HICON hIcon ); 此函数用来向一个图像列表添加一个图标或多个位图。成功时返回第一个新图像的索引号,否则返回-1。参数pbmImage表示包含图像的位图指针,pbmMask表示包含屏蔽的位图指针,crMask表示屏蔽色,hIcon表示图标句柄。
8.3列表视图框架 (2)函数InsertItem用来向列表控件中插入一个列表项。该函数成功时返回新列表项的索引号,否则返回-1。函数原型如下: int InsertItem( const LVITEM* pItem ); int InsertItem( int nItem, LPCTSTR lpszItem ); int InsertItem( int nItem, LPCTSTR lpszItem, int nImage ); 其中,nItem用来指定要插入的列表项的索引号,lpszItem表示列表项的文本标签,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;
8.3列表视图框架 结构中,mask最常用的值可以是: LVIF_TEXT pszText有效或必须赋值。 LVIF_IMAGE iImage有效或必须赋值。 LVIF_INDENT iIndent有效或必须赋值。 (3)函数DeleteItem和DeleteAllItems分别用来删除指定的列表项和全部列表项,函数原型如下: BOOL DeleteItem( int nItem ); BOOL DeleteAllItems( ); (4)函数FindItem用来查寻列表项,函数成功查找时返回列表项的索引号,否则返回-1。其原型如下: int FindItem( LVFINDINFO* pFindInfo, int nStart = -1 ) const; 其中,nStart表示开始查找的索引号,-1表示从头开始。pFindInfo表示要查找的信息,其结构描述如下: typedef struct tagLVFINDINFO { UINT flags; // 查找方式 LPCTSTR psz; // 匹配的文本 LPARAM lParam; // 匹配的值 POINT pt; // 查找开始的位置坐标。 UINT vkDirection; // 查找方向,用虚拟方向健值表示。 } LVFINDINFO, FAR* LPFINDINFO;
8.3列表视图框架 结构中,flags可以是下列值之一或组合: (5)函数Arrange用来按指定方式重新排列列表项,其原型如下: LVFI_PARAM 查找内容由lParam指定。 LVFI_PARTIAL 查找内容由psz指定,不精确查找。 LVFI_STRING 查找内容由psz指定,精确查找。 LVFI_WRAP 若没有匹配,再从头开始。 LVFI_NEARESTXY 靠近pt位置查找,查找方向由vkDirection 确定。 (5)函数Arrange用来按指定方式重新排列列表项,其原型如下: BOOL Arrange( UINT nCode ); 其中,nCode用来指定排列方式,它可以是下列值之一: LVA_ALIGNLEFT 左对齐 LVA_ALIGNTOP 上对齐 LVA_DEFAULT 默认方式 LVA_SNAPTOGRID 使所有的图标安排在最接近的网格位置处 (6)函数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 );
8.3列表视图框架 其中,nCol用来指定新列的索引,lpszColumnHeading用来指定列的标题文本,nFormat用来指定列排列的方式,它可以是LVCFMT_LEFT(左对齐)、LVCFMT_RIGHT(右对齐)和LVCFMT_CENTER(居中对齐);nWidth用来指定列的像素宽度,-1时表示宽度没有设置;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;
8.3列表视图框架 结构中,mask 可以是0或下列值之一或组合: fmt可以是下列值之一: LVCF_FMT fmt参数有效 LVCF_IMAGE iImage参数有效 LVCF_ORDER iOrder参数有效 LVCF_SUBITEM iSubItem参数有效 LVCF_TEXT pszText参数有效 LVCF_WIDTH cx参数有效 fmt可以是下列值之一: LVCFMT_BITMAP_ON_RIGHT 位图出现在文本的右边,对于从图像列表中选取的图像无效 LVCFMT_CENTER 文本居中 LVCFMT_COL_HAS_IMAGES 列表头的图像是在图像列表中 LVCFMT_IMAGE 从图像列表中显示一个图像 LVCFMT_LEFT 文本左对齐 LVCFMT_RIGHT 文本右对齐
8.3列表视图框架 函数DeleteColumn用来从列表控件中删除一个指定的列,其原型如下: BOOL DeleteColumn( int nCol ); 除了上述操作外,还有一些函数是用来设置或获取列表控件的相关属性的。例如SetColumnWidth用来设置指定列的像素宽度,GetItemCount用来返回列表控件中的列表项个数等。它们的原型如下: BOOL SetColumnWidth( int nCol, int cx ); int GetItemCount( ); 其中,nCol用来指定要设置的列的索引号,cx用来指定列的像素宽度,它可以是LVSCW_AUTOSIZE,表示自动调整宽度。 8.3.3 列表控件的消息 在列表视图中,可以用MFC ClassWizard映射的控件消息有公共控件消息(如NM_DBLCLK)、标题头控件消息以及列表控件消息。常用的列表控件消息有: LVN_BEGINDRAG 用户按左鼠拖动列表列表项 LVN_BEGINLABELEDIT 用户对某列表项标签进行编辑 LVN_COLUMNCLICK 某列被按击 LVN_ENDLABELEDIT 用户对某列表项标签结束编辑 LVN_ITEMACTIVATE 用户激活某列表项 LVN_ITEMCHANGED 当前列表项已被改变 LVN_ITEMCHANGING 当前列表项即将改变 LVN_KEYDOWN 某键被按下
8.3列表视图框架 需要说明的是,在用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; 但对于LVN_ITEMACTIVATE来说,上述结构变成了NMITEMACTIVATE,它在结构NM_LISTVIEW基础上增加了一个成员“UINT uKeyFlags”,用来表示ALT、CTRL和SHIFT键的按下状态,它的值可以是LVKF_ALT、LVKF_CONTROL和LVKF_SHIFT。
8.3列表视图框架 8.3.4 列表视图应用示例 实现这个示例有两个关键问题,一个是如何获取当前文件夹中的所有文件,另一个是如何获取各个文件的图标以便添加到与列表控件相关联的图像列表中。第一个问题可能通过MFC类CFileFind来解决,而对于第二问题,则是需要使用API函数SHGetFileInfo。需要说明的是,为了使添加到图像列表中的图标不重复,本例还使用了一个字符串数组集合类对象来保存图标的类型,每次添加图标时都先来验证该图标是否已经添加过。 [例Ex_List] 列表显示当前的文件 (1)用MFC AppWizard创建一个默认的单文档应用程序Ex_List,在创建的第6步将视图的基类选择为CListView。 (2)为CEx_ListView类添加下列成员函数和成员函数:
8.3列表视图框架 class CEx_ListView : public CListView { public: CImageList m_ImageList; CImageList m_ImageListSmall; CStringArray m_strArray; void SetCtrlStyle(HWND hWnd, DWORD dwNewStyle) DWORD dwOldStyle; dwOldStyle = GetWindowLong(hWnd, GWL_STYLE); // 获取当前风格 if ((dwOldStyle&LVS_TYPEMASK) != dwNewStyle) dwOldStyle &= ~LVS_TYPEMASK; dwNewStyle |= dwOldStyle; SetWindowLong(hWnd, GWL_STYLE, dwNewStyle); // 设置新风格 } …
8.3列表视图框架 其中,成员函数SetCtrlStyle用来设置列表控件的一般风格。 (3)在工作区窗口的ResourceView页面中,将Accelerator节点下的IDR_MAINFRAME资源打开,为其添加一个键盘加速键Ctrl+G,其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; } 这样,当程序运行后同时按下Ctrl和G键就会切换列表控件的显示方式。
8.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中添加下列代码:
8.3列表视图框架 (7)编译并运行,结果如图8.9所示。 图8.9 Ex_List运行结果
8.4树视图框架 8.4.1 树视图的样式和操作 由于CTreeView框架是以树控件CTreeCtrl为内建对象,因而它的样式也就是控件的样式。表8.3所示,其修改方法与列表控件同的一般样式修改方法相同。 表8.3 树控件的一般样式 风 格 含义 TVS_HASLINES 子节点与它们的父节点之间用线连接 TVS_LINESATROOT 用线连接子节点和根节点 TVS_HASBUTTONS 在每一个父节点的左边添加一个按钮“+”和“-” TVS_EDITLABELS 允许用户编辑节点的标签文本内容 TVS_SHOWSELALWAYS 当控件失去焦点时,被选择的节点仍然保持被选择 TVS_DISABLEDRAGDROP 该控件被禁止发送TVN_BEGINDRAG通知消息 TVS_NOTOOLTIPS 控件禁用工具提示 TVS_SINGLEEXPAND 当使用这个风格时,节点可展开收缩 TVS_CHECKBOXES 在每一节点的最左边有一个复选框 TVS_FULLROWSELECT 多行选择,不能用于TVS_HASLINES 风格 TVS_INFOTIP 控件得到工具提示时发送TVN_GETINFOTIP通知消息 TVS_NONEVENHEIGHT 节点的高度值不一样。默认节点高度是一样 TVS_NOSCROLL 不使用水平或垂直滚动条 TVS_TRACKSELECT 使用热点跟踪
8.4树视图框架 树控件类CTreeCtrl类提供了许多关于树控件操作的成员函数,如节点的添加和删除等。下面分别说明。 (1)函数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 );
8.4树视图框架 其中,nMask用来指定要设置的属性,lpszItem用来指定节点的文本标签内容,nImage用来指定该节点图标在图像列表中的索引号,nSelectedImage表示该节点被选定时,其图标图像列表中的索引号,nState表示该节点的当前状态,它可以是TVIS_BOLD(加粗)、TVIS_EXPANDED(展开)和TVIS_SELECTED(选中)等,nStateMask用来指定哪些状态参数有效或必须设置,lParam表示 与该节点关联的一个32位值,hParent用来指定要插入节点的父节点的句柄,hInsertAfter用来指定新节点添加的位置,它可以是: TVI_FIRST 插到开始位置 TVI_LAST 插到最后 TVI_SORT 插入后按字母重新排序 (2)函数DeleteItem和DeleteAllItems分别用来删除指定的节点和全部的节点。它们的原型如下: BOOL DeleteAllItems( ); BOOL DeleteItem( HTREEITEM hItem );
8.4树视图框架 其中,hItem用来指定要删除的节点的句柄。如果hItem的值是TVI_ROOT,则所有的节点都被从此控件中删除。 (4)函数Expand用来用来展开或收缩指定父节点的所有子节点,其原型如下: BOOL Expand( HTREEETEM hItem, UINT nCode ); 其中,hItem指定要被展开或收缩的节点的句柄,nCode用来指定动作标志,它可以是: TVE_COLLAPSE 收缩所有子节点 TVE_COLLAPSERESET 收缩并删除所有子节点 TVE_EXPAND 展开所有子节点 TVE_TOGGLE 如果当前是展开的则收缩,反之则展开 (5)函数GetNextItem用来获取下一个节点的句柄。它的原型如下: HTREEITEM GetNextItem( HTREEITEM hItem, UINT nCode ); 其中,hItem指定参考节点的句柄,nCode用来指定与hItem的关系标志,常见的标志有: TVGN_CARET 返回当前选择节点的句柄 TVGN_CHILD 返回第一个子节点句柄,hItem必须为NULL TVGN_NEXT 返回下一个兄弟节点(同一个树支上的节点)句柄 TVGN_PARENT 返回指定节点的父节点句柄 TVGN_PREVIOUS 返回上一个兄弟节点句柄 TVGN_ROOT 返回hItem父节点的第一个子节点句柄
8.4树视图框架 (6)函数HitTest用来测试鼠标当前操作的位置位于哪一个节点中,并返回该节点句柄。它的原型如下: HTREEITEM HitTest( CPoint pt, UINT* pFlags ); 其中pFlags包含当前鼠标所在的位置标志,如下列常用定义: TVHT_ONITEM 在节点上 TVHT_ONITEMBUTTON 在节点前面的按钮上 TVHT_ONITEMICON 在节点文本前面的图标上 TVHT_ONITEMLABEL 在节点文本上 除了上述操作外,还有其他常见操作,如表8.4所示。
8.4树视图框架 表8.4 CTreeCtrl类其他常见操作 成员函数 说 明 UINT GetCount( ); 说 明 UINT GetCount( ); 获取树中节点的数目,若没有返回-1 BOOL ItemHasChildren( HTREEITEM hItem ); 判断一个节点是否有子节点 HTREEITEM GetChildItem( HTREEITEM hItem ); 获取由hItem指定的节点的子节点句柄 HTREEITEM GetParentItem( HTREEITEM hItem ); 获取由hItem指定的节点的父节点句柄 HTREEITEM GetSelectedItem( ); 获取当前被选择的节点 HTREEITEM GetRootItem( ); 获取根节点句柄 CString GetItemText( HTREEITEM hItem ) const; 返回由hItem指定的节点的文本 BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem ); 设置由hItem指定的节点的文本 DWORD GetItemData( HTREEITEM hItem ) const; 返回与指定节点关联的32位值 BOOL SetItemData( HTREEITEM hItem, DWORD dwData ); 设置与指定节点关联的32位值 COLORREF SetBkColor( COLORREF clr ); 设置控件的背景颜色 COLORREF SetTextColor ( COLORREF clr ); 设置控件的文本颜色 BOOL SelectItem( HTREEITEM hItem ); 选中指定节点 BOOL SortChildren( HTREEITEM hItem ); 用来将指定节点的所有子节点排序
8.4树视图框架 8.4.2 树视图控件的消息 同列表视图相类似,树视图也可以用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;
8.4树视图框架 8.4.3 树视图应用示例 [例Ex_Tree] 遍历本地磁盘所有的文件夹 8.4.3 树视图应用示例 [例Ex_Tree] 遍历本地磁盘所有的文件夹 (1)用MFC AppWizard创建一个默认的单文档应用程序Ex_Tree,在创建的第6步将视图的基类选择为CTreeView。 为CEx_TreeView类添加下列成员变量: class CEx_TreeView : public CTreeView { public: CImageList m_ImageList; CString m_strPath; // 文件夹路径
8.4树视图框架 (2)为CEx_TreeView类添加成员函数InsertFoldItem,其代码如下: void CEx_TreeView::InsertFoldItem(HTREEITEM hItem, CString strPath) { CTreeCtrl& treeCtrl = GetTreeCtrl(); if (treeCtrl.ItemHasChildren(hItem)) return; CFileFind finder; BOOL bWorking = finder.FindFile(strPath); while (bWorking) bWorking = finder.FindNextFile(); if (finder.IsDirectory() && !finder.IsHidden() && !finder.IsDots()) treeCtrl.InsertItem(finder.GetFileTitle(), 0, 1, hItem, TVI_SORT); }
8.4树视图框架 (4)为CEx_TreeView类添加成员函数GetFoldItemPath,其代码如下: CString CEx_TreeView::GetFoldItemPath(HTREEITEM hItem) { CString strPath, str; strPath.Empty(); CTreeCtrl& treeCtrl = GetTreeCtrl(); HTREEITEM folderItem = hItem; while (folderItem) int data = (int)treeCtrl.GetItemData( folderItem ); if (data == 0) str = treeCtrl.GetItemText( folderItem ); else str.Format( "%c:\\", data ); strPath = str + "\\" + strPath; folderItem = treeCtrl.GetParentItem( folderItem ); } strPath = strPath + "*.*"; return strPath;
8.4树视图框架 (5)用ClassWizard为CEx_TreeView类添加TVN_SELCHANGED(当前选择的节点改变后)消息处理,并增加下列代码: void CEx_TreeView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; HTREEITEM hSelItem = pNMTreeView->itemNew.hItem; // 获取当前选择的节点 CTreeCtrl& treeCtrl = GetTreeCtrl(); CString strPath = GetFoldItemPath( hSelItem ); if (!strPath.IsEmpty()) InsertFoldItem(hSelItem, strPath); treeCtrl.Expand(hSelItem,TVE_EXPAND); } *pResult = 0; (6)在CEx_TreeView::PreCreateWindow函数中添加设置树控件风格代码: BOOL CEx_TreeView::PreCreateWindow(CREATESTRUCT& cs) cs.style |= TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS; return CTreeView::PreCreateWindow(cs); (7)在CEx_TreeView::OnInitialUpdate函数中添加下列代码:
8.4树视图框架 (8)编译并运行,结果如图8.10所示。 图8.10 Ex_Tree运行结果
8.5切分视图框架 8.5.1 切分类型 切分视图可分为静态切分和动态切分两种类型。 8.5.1 切分类型 切分视图可分为静态切分和动态切分两种类型。 对于“静态切分”来说,当文档窗口(视图)第一次被创建时,窗格就已经被切分好了,窗格的次序和数目不能再被改变,但用户可以移动切分条来调整窗格的大小。每个窗格通常代表着不同视图类的对象。 对于“动态切分”来说,它允许用户在任何时候对文档窗口(视图)进行切分,用户既可以通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分块对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类对象。当切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新添加的视图对象被动态创建;当视图沿着两个方向被切分时,新添加的三个视图对象则被动态创建。当用户取消切分时,所有新添加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失为止。 无论是静态切分还是动态切分,在创建时都要指定切分窗口中行和列的窗格最大数目。对于静态切分,窗格在初始时就按用户指定的最大数目划分好了;而对于动态切分窗口,当窗口构造时,第一个窗格就被自动创建。动态切分窗口允许的最大窗格数目是2 x 2,而静态切分允许的最大窗格数目为16 x 16。
8.5切分视图框架 在MFC中,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, 其中,参数pParentWnd表示切分窗口的父框架窗口。nMaxRows表示窗口动态切分的最大行数(不能超过2)。nMaxCols表示窗口动态切分的最大列数(不能超过2)。nRows表示窗口静态切分的行数(不能超过16)。nCols表示窗口静态切分的列数(不能超过16)。sizeMin表示动态切分时允许的窗格最小尺寸。 CSplitterWnd类成员函数CreateView用来为静态窗格指定一个视图类,并创建视图窗口,其函数原型如下: BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );
8.5切分视图框架 其中,row和col用来指定具体的静态窗格,pViewClass用来指定与静态窗格相关联的视图类,sizeInit表示视图窗口初始大小,pContext用来指定一个“创建上下文”指针。“创建上下文”结构CCreateContext包含当前文档视图框架结构。 8.5.2 静态切分实现 利用CSplitterWnd成员函数,用户可以在文档应用程序的文档窗口(视图)中添加动态或静态切分功能。例如,下面的示例是将单文档应用程序中的文档窗口静态分成3x2个窗格。 [例Ex_SplitSDI] 静态切分 (1)创建一个默认的单文档应用程序Ex_SplitSDI。 (2)打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义: protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CSplitterWnd m_wndSplitter; (3)用MFC ClassWizard添加并创建一个新的视图类CDemoView(基类为CView)用于与静态切分的窗格相关联。 (4)用MFC ClassWizard为CMainFrame类添加OnCreateClient(当主框架窗口客户区创建的时候自动调用该函数)函数重载,并添加下列代码:
8.5切分视图框架 (5)在MainFrm.cpp源文件的开始处,添加视图类CDemoView的包含文件: #include "MainFrm.h" #include "DemoView.h" (6)编译并运行,结果如图8.11所示。 第0,0窗格 第1,0窗格 第2,0窗格 第0,1窗格 第1,1窗格 第2,1窗格 切分条 图8.11 单文档的静态切分
8.5切分视图框架 需要说明的是: (1)在调用CreateStatic函数创建静态切分窗口后,必须将每个窗格用CreateView函数指定相关联的视图类。各窗格的视图类可以相同,也可以不同。 (2)切分功能只应用于文档窗口,对于单文档应用程序切分的创建是在CMainFrame类进行的,而对于多文档应用程序,添加切分功能时应在子框架窗口类CChildFrame中进行。 8.5.3 动态切分窗口实现 动态切分功能的创建过程要比静态切分简单得多,它不需要重新为窗格指定其它视图类,因为动态切分窗口的所有窗格共享同一个视图。若在文档窗口中添加动态切分功能,除了上述方法外,还可在MFC AppWizard创建文档应用程序的“第4步”对话框中单击[高级]按钮,通过选中“高级选项”(Advanced Options)对话框“窗口样式”(Window Styles)页面中的“应用切分窗体”(Use Split Window)来创建,或是通过 添加切分窗口组件来创建。 例如,下面的示例是通过添加切分窗口组件来创建动态切分的。 [例Ex_DySplit] 动态切分 (1)创建一个默认的单文档应用程序Ex_DySplit。
图8.12 “Components and Controls Gallery”对话框 8.5切分视图框架 (2)选择“工程”→“添加工程”→“Components and Controls”,弹出如图8.12所示的对话框。 图8.12 “Components and Controls Gallery”对话框
8.5切分视图框架 (3)双击“Visual C++ Components”,出现Visual C++支持的组件,选中Splitter Bar,结果如图8.13所示。 图8.13 Visual C++支持的组件
8.5切分视图框架 (4)单击[Insert]按钮,出现一个消息对话框,询问是否要插入Splitter Bar组件,单击[确定]按钮,弹出如图8.14所示的对话框。从中可选择切分类型:Horizontal(水平切分)、Vertical(垂直切分)和Both(水平垂直切分)。 垂直切分块 水平切分块 图8.15 Ex_DySplit运行结果 图8.14 Splitter Bar组件选项对话框 (5)选中Both选项,单击[OK]按钮,回到图8.13中的对话框,单击[关闭]按钮,动态切分 就被添加到单文档应用程序的主框架窗口类CMainFrame中。 (6)编译运行,结果如图8.15所示。 需要说明的是,上述方法还可向应用程序添加许多类似组件,如Splash screen(程序启动画面)、Tip of the day(今日一贴)和Windows Multimedia library(Windows多媒体库)等。
8.6综合应用 如图8.16所示是一个带有左右两个窗格的静态切分视图框架。 图8.16 Ex_A8运行结果
8.6综合应用 下面就创建切分视图框架、添加CEx_A8Doc和CEx_A8View类代码、完善CDrawView类代码等3部分来阐述其实现过程。 [例Ex_A8] 一档多视 1)创建切分视图框架 (1)用MFC AppWizard创建一个单文档应用程序Ex_A8。在第6步中将视图的基类选择为CFormView。 (2)此时会自动打开表单模板资源IDD_EX_A8_FORM,删除“TODO…”静态文本控件,参看图8.16,调整表单模板大小,并依次添加如表8.5所示的控件。 表8.5 在表单中添加的控件 添加的控件 ID号 标 题 其他属性 编辑框 IDC_EDIT1 —— 默认 旋转按钮 IDC_SPIN1 自动伙伴(Auto buddy)、自动结伴整数(Set buddy integer)、靠右对齐(Alignment Right),其它默认 IDC_EDIT2 IDC_SPIN2
8.6综合应用 (3)打开MFC ClassWizard的Member Variables标签,在Class name中选择CEx_A8View,选中所需的控件ID号,双击鼠标或单击Add Variables按钮。依次为表8.6中的控件添加成员变量。 表8.6 添加的控件变量 控件ID号 变量类别 变量类型 变量名 IDC_EDIT1 Value int m_nCoorX IDC_EDIT2 m_nCoorY IDC_SPIN1 Control CSpinButtonCtrl m_wndSpinX IDC_SPIN2 m_wndSpinY (4)用MFC ClassWizard添加并创建一个新的视图类CDrawView(基类为CView)用于与右边窗格相关联。 打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分窗口的数据成员,如下面的定义: protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CSplitterWnd m_wndSplitter;
8.6综合应用 (6)用MFC ClassWizard为CMainFrame类添加OnCreateClient(当主框架窗口客户区创建的时候自动调用该函数)函数重载,并添加下列代码: BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { BOOL bRes = m_wndSplitter.CreateStatic(this, 1, 2);// 创建2个水平静态窗格 m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CEx_A8View), CSize(0,0), pContext); m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDrawView), CSize(0,0), pContext); m_wndSplitter.SetColumnInfo(0, 200, 10); // 设置左边窗格列宽 m_wndSplitter.RecalcLayout(); // 重新布局 return bRes; //CFrameWnd::OnCreateClient(lpcs, pContext); } (7)在MainFrm.cpp的前面添加用户视图类的头文件包含语句: #include "MainFrm.h" #include "Ex_A8View.h" #include "DrawView.h"
8.6综合应用 (8)编译。此时程序会有一个错误,这个错误的出现是基于这样的一些事实:在用标准C/C++设计程序时,有一个原则即两个代码文件不能相互包含,而且多次包含还会造成重复定义的错误。为了解决这个难题,Visual C++使用#pragma once来通知编译器在生成时只包含(打开)一次,也就是说,在第一次#include之后,编译器重新生成时不会再对这些包含文件进行包含(打开)和读取,因此在用向导创建的所有类的头文件中都有#pragma once这样的语句。然而正是由于这个语句而造成了在第二次#include后编译器无法正确识别所引用的类,从而发生错误。解决的办法是在相互包含时加入类的声明来通知编译器这个类是一个实际的调用,如下一步操作。 (9)打开Ex_A8View.h文件,在class CEx_A8View : public CFormView语句前面添加下列代码: class CEx_A8Doc; // 声明CEx_A8Doc类需要再次使用 class CEx_A8View : public CFormView {…} (10)再次编译运行,然后测试:打开表单模板资源IDD_EX_A8_FORM,调整其大小,编译运行后看看跟前面运行的结果有何区别?多试几次!
8.6综合应用 2)添加CEx_A8Doc和CEx_A8View类代码 (1)在CEx_A8Doc类中添加一个公有型的CPoint数据成员m_ptCoor,用来记录小方块的位置。 public: CPoint m_ptCoor; (2)在CEx_A8Doc类的构造函数处添加下列代码,使初始的位置为(0,0): CEx_A8Doc::CEx_RectDoc() { m_ptCoor = CPoint( 0, 0 ); } (3)在CEx_A8View::OnInitialUpdate中添加一些初始化代码: void CEx_A8View::OnInitialUpdate() CFormView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); m_wndSpinX.SetRange(0, 1000); // 设置旋转按钮控件的范围 m_wndSpinY.SetRange(0, 1000); CEx_A8Doc* pDoc = GetDocument(); m_nCoorX = pDoc->m_ptCoor.x; m_nCoorY = pDoc->m_ptCoor.y; UpdateData(FALSE);
8.6综合应用 (4)打开MFC ClassWizard对话框,并要换到Messsage Maps标签页,在CEx_A8View类中为编辑框IDC_EDIT1和IDC_EDIT2添加EN_CHANGE的消息映射,使它们的映射函数名都设为OnChangeEdit,并添加下列代码: void CEx_A8View::OnChangeEdit() { if ( this->IsWindowVisible( ) ) // 只有表单视图可见时,控件消息才是有用的 UpdateData(TRUE); CEx_A8Doc* pDoc = GetDocument(); pDoc->m_ptCoor = CPoint( m_nCoorX, m_nCoorY ); pDoc->UpdateAllViews(NULL, 2 ); }
8.6综合应用 (5)用MFC ClassWizard为CEx_A8View添加OnUpdate的消息函数,并添加下列代码: void CEx_A8View::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == 1) CEx_A8Doc* pDoc = GetDocument(); m_nCoorX = pDoc->m_ptCoor.x; m_nCoorY = pDoc->m_ptCoor.y; UpdateData(FALSE); // 在控件中显示 } 3)完善CDrawView类代码 (1)在DrawView.cpp文件的前面添加CEx_A8Doc类的包含语句: #include "Ex_A8.h" #include "DrawView.h" #include "Ex_A8Doc.h"
8.6综合应用 (2)在CDrawView::OnDraw函数中添加下列代码: void CDrawView::OnDraw(CDC* pDC) { CEx_A8Doc* pDoc = (CEx_A8Doc*)m_pDocument; CRect rc( pDoc->m_ptCoor.x - 5, pDoc->m_ptCoor.y - 5, pDoc->m_ptCoor.x + 5, pDoc->m_ptCoor.y + 5 ); pDC->FillSolidRect( rc, RGB( 0, 128, 0 ) ); } (3)用MFC ClassWizard为CDrawView类添加OnUpdate的消息函数,并添加下列代码: void CDrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) if (lHint == 2) Invalidate();
8.6综合应用 (4)用MFC ClassWizard为CDrawView类添加WM_LBUTTONDOWN的消息映射,并添加下列代码: void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) { CEx_A8Doc* pDoc = (CEx_A8Doc*)m_pDocument; pDoc->m_ptCoor = point; pDoc->UpdateAllViews( NULL, 1 ); Invalidate(); // 强迫调用CDrawView::OnDraw CView::OnLButtonDown(nFlags, point); } (5)编译运行并测试,结果如前图8.16所示。
习题 1. 视图类CView的派生类(视图应用框架)有哪些?如何创建这些框架应用程序? 2. 列表视图有哪些显示模式?如何通过程序实现切换? 3. 如何向树视图中添加一个新项(节点)?从一个节点向上和向下遍历时各要调用哪些成员函数? 4. 什么是静态切分和动态切分?它们有何异同?如何在文档窗口(视图)中添加动态和静态切分功能? 5. 什么是“一档多视”?说说Ex_A8中,文档数据改变后是怎样通知视图的?与同一个文档相联系的多个视图又是怎样获得数据的?