当前位置: 首页 > news >正文

宁波网站设计首选荣盛网络建设网站目标

宁波网站设计首选荣盛网络,建设网站目标,大理公司网站建设,制作一个手机app需要多少钱尽管窗口、文档和视图是MFC的基础#xff0c;但可能也是最不容易理解的部分#xff0c;因为其概念比传统编程所需要的Windows函数更强一些#xff0c;因此#xff0c;须在本章做进一步详细讨论框架窗口、文档和视图的方法和技巧。 6.1框架窗口 分两类#xff1a;一是应用…尽管窗口、文档和视图是MFC的基础但可能也是最不容易理解的部分因为其概念比传统编程所需要的Windows函数更强一些因此须在本章做进一步详细讨论框架窗口、文档和视图的方法和技巧。 6.1框架窗口 分两类一是应用程序主窗口另一类是文档窗口。 6.1.1主窗口和文档窗口 主窗口或称主框架窗口是应用程序直接放在桌面DeskTop上的那个窗 口每个应用程序只能有一个窗口主窗口的标题栏上往往显示应用程序的名称。 主窗口类的源文件名是MainFrm.h和MainFrm.cpp其类名是CMainFrame。 单文档SDI程序主窗口类是从CFrameWnd派生来的。 多文档MDI程序主窗口类是从CMDIFrameWnd派生的。 如果应用程序中还有工具栏CToolBar状态栏CStatusBar那么在CMainFrame类还含有表示工具栏和状态栏的成员变量m_wndToolBar和m_wndStatusBar并在CMainFrame的OnCreate函数中进行初始化。 文档窗口对于SDI程序来说它和主窗口是一致的即主窗口就是文档窗口对于MDI程序文档窗口是主窗口的子窗口。见书244页图6.1所示。文档窗口一般都有相应的可见边框他的客户区初了窗口标题栏、边框外的区域是由相应的视图来构成的可以说视图是文档窗口内的子窗口。文档窗口时刻跟踪当前处于活动状态的视图的变化并将用户或系统产生的命令消息传递给当前活动视图。而主窗口负责管理各个用户交互对象包括菜单、工具栏、状态栏以及加速键并根据用户操作相应地创建或更新文档窗口及其视图。在MDI应用程序中MFC AppWizard创建的文档子窗口类的源代码文件是ChildFrm.h和ChildFrm.cpp其类名是CChildFrame它是从CMDIChildWnd派生的。 6.1.2窗口风格的设置 窗口的风格决定了窗口的外观及功能用户通过风格的设置增加或减少窗口中所包含的功能这些功能一般都是由系统内部定义的不需要用户去编程实现。 窗口风格可以通过MFC AppWizard来设置也可以在主窗口或文档窗口类的 PreCreateWindow函数中修改CREATESTRUCT结构或是可以调用CWnd类的 成员函数ModifyStyle和ModifyStyleEx来更改。 1、窗口风格 通常以WS_为前缀和扩展以WS_EX_为前缀两种形式这两种形式的窗口风格可在函数CWnd::Create只能指定窗口的一般风格或CWnd::CreateEx可同时支持以上两种风格对于控件和对话框这样的窗口来说它们的窗口风格可直接通过其属性对话框来设置。常见的一般窗口风格如下所示书245页表6.1 WS_BORDER 窗口含有边框 WS_CAPTION 窗口含有标题栏它意味着还具有WS_BORDER风格 但它不能和WS_DLGFRAME组合 WS_CHILD 创建子窗口它不能和WS_POPUP组合 WS_CLIPCHILDREN 在父窗口范围内裁剪子窗口它通常在父窗口创建时指定 WS_CLIPSIBLINGS 裁剪相邻子窗口也就是说具有此风格的子窗口和其他 子窗口重叠的部分被裁剪它只和WS_CHILD组合 WS_DISABLED 窗口最初时是禁用的 WS_DLGFRAME 窗口含有双边框但没有标题 WS_GROUP 此风格被控件组中第1个控件窗口指定。用户可在控件组 的第1个和最后1个控件中用方向键来选择 WS_HSCROLL 窗口最初时处于最大化 WS_MAXIMIZEBOX 在窗口的标题栏上含有”最大化”按钮 WS_MINIMIZE 窗口最初处于最小化他只和WS_OVERLAPPED组和 WS_MINIMIZEBOX 在窗口的标题栏上含有”最小化”按钮 WS_OVERLAPPED 创建覆盖窗口一个覆盖窗口通常有一个标题和边框 WS_OVERLAPPEDWINDOW 创建一含有WS_OVERLAPPED、WS_CAPTION、 WS_SYSMENU、WS_THICKFRAME、 WS_MINIMIZEBOX和WS_MAXIMIZEBOX 风格的覆盖窗口 WS_POPUP 创建一弹出窗口它不能和WS_CHILD组合只能用 CreateWx函数指定 WS_POPUPWINDOW 创建一含有WS_BORDER、WS_POPUP和WS_SYSMENU 风格的弹出窗口。当WS_CAPTION和WS_POPUPWINDOW 风格组合时才能使系统菜单可见。 WS_SYSMENU 窗口的标题栏上含有系统菜单框它仅用于含有标题的窗口 WS_TABSTOP 用户可以用于TAB键 选择控件组中的下一个控件 WS_THICKFRAME 窗口含有边框并可调整窗口的大小 WS_VISIBLE 窗口最初是可见的 WS_VSCROLL 窗口含有垂直滚动条 除了这些风格外框架窗口还有以下3个自己的风格。他们都可以在PreCreateWindow重载函数中指定。 1FWS_ADDTOTITLE风格指定一个文档名添加到框架窗口标题中如书244页图6.1的“Ex_MDI---Ex_MDI1”的Ex_MDI1是文档名。若单文档应用程序默认的文档名是”无标题”。 2FWS_PREFIXTITLE风格使得框架窗口标题中的文档名显示在应用程序名之前。例如若未指定该风格时的窗口标题为”Ex_MDI_Ex_MDI1”,当指定该风格后就变成了”Ex_MDI1_Ex_MDI”。 3FWS_SNAPTOBARS风格用于调整窗口的大小使它刚好包含了框架窗口中的控制栏如工具栏 2、用MFC AppWizard设置 MFC AppWizard有一个Advanced高级按钮在创建单或多文档程序时的第4步中允许用户指定有关SDI和MDI框架窗口的属性。见书246页图6.2所示为”Advanced Options”对话框的Window Styles页面其中的含义如下246页 但在该对话框中用户只能设定少数几种窗口风格 Use split window(应用拆分窗口) 选中时将程序的文档窗口创建成”切分”*(或称 拆分)窗口 Thick frame厚框 选中时设置窗口风格WS_THICKFRAME Minimize box(最小化框符) 选中时设置窗口风格WS_MINIMIZEBOX Maximize box(最大化框符) 选中时设置窗口风格WS_MAXIMIZEBOX System menu(系统菜单) 选中时设置窗口风格WS_SYSMENU Minimized(最小化) 选中时设置窗口风格WS_MINIMIZE Maximized(最大化) 选中时设置窗口风格WS_MAXIMIZE 3、修改CREATESTRUCT结构 在窗口创建之前系统自动调用PreCreateWindow虚函数。在MFC AppWizard 创建SDI/MDI应用程序结构时MFC已为主窗口或文档窗口类自动重载了该虚函数。我们可以在此函数中通过修改CREATESTRUCT结构来设置窗口的绝大多数风格。 例在SDI程序中框架窗口默认的风格是WS_OVERLAPPEDWINDOW和FWS_ADDTOTITLE的组合更改其风格。 1建一个单文档的应用程序 2在主框架程序MainFrm.cpp中找到PreCreateWindow虚函数并加代码 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT cs) { cs.style~WS_MAXIMIZEBOX;//新窗口不带有[最大化]按钮 cs.cy::GetSystemMetrics(SM_CYSCREEN)/3; cs.cx::GetSystemMetrics(SM_CYSCREEN)/3;//将窗口大小设置为1/3屏幕大 //小并居中。 cs.y((cs.cy*3)-cs.cy)/2; cs.x((cs.cx*3)-cs.cx)/2; //调用基类的PreCreateWindow(cs)函数 return CFrameWnd::PreCreateWindow(cs); // if( !CFrameWnd::PreCreateWindow(cs) ) // return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs // return TRUE; } 3编译运行见出现的窗口是原来的1/3。 4建一个多文档MDI应用程序先运行一下试一试窗口最大化按钮 5在子文档窗口ChildFrm程序中找到PreCreateWindow虚函数并加代码 创建不含有[最大化]按钮的子窗口 BOOL CChildFrame::PreCreateWindow(CREATESTRUCT cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs cs.style~WS_MAXIMIZEBOX;//创建不含有[最大化]按钮的子窗口 return CFrameWnd::PreCreateWindow(cs); // if( !CMDIChildWnd::PreCreateWindow(cs) ) // return FALSE; // return TRUE; } 结果是不含有[最大化]按钮的子窗口 代码中前面有“”作用域符号的函数是指全局函数。 代码“cs.style ~WS_MAXIMIZEBOX;”中的“~”是按位取“反”运算符它将WS_MAXIMIZEBOX的值按位取反后再和cs.style值按位“与”其结果是将cs.style值中的WS_MAXIMIZEBOX标志位清零。 4、使用ModifyStyle和ModifyStyleEx CWnd类中的成员函数ModifyStyle和ModifyStyleEx也可用来更改窗口的风格其中ModifyStyleEx还可更改窗口的扩展风格。这两个函数具有相同的参数 BOOL ModifyXXXX(DWORD dwRemove,DWORD dwAdd,UINT nFlags0); 参数dwRemove用来指定需要删除的风格 dwAdd 用来指定需要增加的风格 nFlags 表示SetWindowPos的标志0默认表示更改风格的同时不 调用SetWindowPos函数 由于框架窗口在创建时不能直接设定其扩展风格因此只能通过调用ModifyStyle函数来进行。 例在多文档MDI的子文档窗口增加水平和垂直滚动条书247页例248 页图6.3) 1用MFC AppWizard创建一个多文档应用程序或用上个多文档应用程序 名为“多文档” 2用ClassWizard为子文档窗口类CChildFrame添加OnCreateClient处理消息: ViewàClassWizardàCChildFrameàOnCreateClientàAdd FunctionàEdit Code 并加如下代码 BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs,CCreateContext* pContext) { // TODO: Add your specialized code here and/or call the base class ModifyStyle(0,WS_VSCROLL,0);//垂直滚动轴 ModifyStyle(0,WS_HSCROLL,0);//水平滚动轴 return CMDIChildWnd::OnCreateClient(lpcs, pContext); } 3编译并运行书上只有一个滚动轴 6.1.3窗口状态的改变 MFC AppWizard为每个窗口设置了相应的大小和位置但总有的默认窗口 不另人满意这时就需要进行适当的调整。 1、用ShowWindow改变窗口的显示状态 当应用程序运行时Windows会自动调用应用程序框架内部的WinMain函数并自动查找该应用程序类的全局变量theApp,然后自动调用用户应用程序类的虚函数InitInstance该函数会进一步调用相应的函数来完成主窗口的构造和显示工作如一个单文档应用程序的应用程序文件中的程序名.cpp中的InitInstance()函数中的代码 m_pMainWnd-ShowWindow(SW_SHOW); m_pMainWnd-UpdateWindow(); 参数SW_SHOW是用当前的大小和位置激活并显示窗口。 m_pMainWnd是主框架窗口指针变量ShowWindow是CWnd类的成员函 数用于按指定的参数显示窗口该参数的值如下 SW_HIDE 隐藏此窗口并将激活状态移交给其他窗口 SW_MINIMIZE 将窗口最小化并激活系统中的顶层窗口 SW_RESTORE 激活并显示窗口。若窗口是最小或最大状态时则恢复到原来的 大小和位置 SW_SHOW 用当前的大小和位置激活并显示窗口 SW_SHOWMAXIMIZED 激活窗口并使之最大化 SW_SHOWMINIMIZED 激活窗口并使之最小化 SW_SHOWMINNOACTIVE窗口显示成为一个图标并保留其激活状态即原来是 激活的仍然是激活 SW_SHOWNA 用当前状态显示窗口 SW_SHOWNOACTIVATE 用最近的大小和位置状态显示窗口并保留其激活状态 SW_SHOWNORMAL 激活并显示窗口 通过指定ShowWindow函数的参数值可以改变窗口显示状态例如将窗口的初始状态设置为”最小化”可以这样写 m_pMainWnd-ShowWindow(SW_SHOWMINIMIZED); m_pMainWnd-UpdateWindow(); 由于用户应用程序类继承了基类CWinApp的特性因此也可在用户应用程序类 CWinApp中使用公有型public成员变量m_nCmdShow通过对其进行赋值同样能达到效果。 例如在上个“多文档”项目的应用程序的InitInstance()函数中这样写 m_nCmdShowSW_SHOWMAXIMIZED;//激活窗口并使之最大化书是最小 pMainFrame-ShowWindow(m_nCmdShow); pMainFrame-UpdateWindow(); return TRUE; 2、用SetWindowPos或MoveWindow改变窗口的大小和位置 CWnd中的SetWindowPos是一个非常有用的函数它不仅可以改变窗口的大小、位置而且还可以改变所有窗口在堆栈排列的次序Z次序这个次序是根据它们在屏幕出现的先后来确定的。 BOOL SetWindowPos(const CWnd *pWndInsertAfter,int x,int y,int cx,int cy,UINT nFlags); 参数pWndInsertAfter表示窗口对象指针它可以预定义下列窗口对象的地址 wndBottom将窗口放置在Z次序中的底层 wndTop将窗口放置在Z次序中的顶层 wndTopMost设置最顶层窗口 wndNoTopMost将窗口放置在所有最顶层的后面若此窗口不是最顶窗 口则此标志无效。 x和y表示窗口新的左上角坐标cx和cy分别表示窗口的宽度和高度 nFlags表示窗口新的大小和位置方式如下说明书250页表6.4 常用nFlags值及其含义 SWP_HIDEWINDOW隐藏窗口 SWP_NOACTIVATE 不激活窗口。如该标志没有被指定则依赖pWndInsertAfter参数 SWP_NOMOVE 不改变当前的窗口位置忽略x和y参数 SWP_NOOWNERZORDER不改变父窗口的Z次序 SWP_NOREDRAW 不重新绘制窗口 SWP_NOSIZE 不改变当前的窗口大小忽略cx和cy参数 SWP_NOZORDER 不改变当前的窗口Z次序忽略pWndInsertAfter参数 SWP_SHOWWINDOW 显示窗口 函数CWnd::MoveWindow也可用来改变窗口的大小和位置与SetWindowPos函数不同的是用户必须在MoveWindow函数中指定窗口的大小。 void MoveWindow(int x,int y,int nWidth,int nHeight,BOOL bRepaintTRUE); void MoveWindow(LPCRECT lpRect,BOOL bRepaintTRUE); 参数x,y表示窗口新的左上角坐标 nWidth,nHeight表示窗口新的宽度和高度 bRepaint用于指定窗口是否重绘 lpRect表示窗口新的大小和位置 例用上面两个函数把主窗口移到屏幕的100100处。 1建一个单文档的应用程序 2打开文件名.cpp应用程序找到BOOL CMyApp::InitInstance()函数并添下列代码 m_pMainWnd-SetWindowPos(NULL,100,100,0,0,SWP_NOSIZE| SWP_NOZORDER);//不改变当前的窗口大小和窗口Z次序 CRect rcWindow; //见上面说明书250页表6.4 m_pMainWnd-GetWindowRect(rcWindow); m_pMainWnd-MoveWindow(100,100,rcWindow.Width(), rcWindow.Height(),TRUE);//改变窗口位置 //AfxGetMainWnd()-CenterWindow();//将主框架窗口居中. //AfxGetMainWnd()-CenterWindow(CWnd::GetDesktopWindow()); //将窗口置于屏幕中央 //return TRUE; 当然改变窗口的大小和位置的CWnd成员函数还不止以上两个例如 CenterWindowCWND::GetDesktopWindow()函数是使窗口居于父窗口屏幕中央。 AfxGetMainWnd()-CenterWindow();//将主框架窗口居中。见上面已加上的两个函数。 6. 2文档模板 用MFC AppWizard创建的单文档SDI或多文档MDI应用程序均包含应用程序类、文档类、视图类和框架窗口类这些类是通过文档模板有机地联系在一起的。 6.2.1文档模板类 文档应用程序框架结构是在程序运行一开始构造的。 1、在单文档应用程序的应用程序类InitInstance()函数中可以看到这样的代码 BOOL CMyApp::InitInstance() { CSingleDocTemplate *pDocTemplate; pDocTemplate new CSingleDocTemplate ( IDR_MAINFRAME, //资源ID RUNTIME_CLASS(CMyDoc), //以中文名字命名的项目名的项目文档类 RUNTIME_CLASS(CMainFrame),// 主框架窗口类 RUNTIME_CLASS(CMyView) //以中文名字命名的项目名的项目视图类 ); AddDocTemplate(pDocTemplate); ….. return TRUE; } 代码中 pDocTemplate 是类CSingleDocTemplate的指针对象。 CSingleDocTemplate是一个单文档模板类他的构造函数中有4个参数分别表示 菜单和加速键等的资源ID号以及3个由宏RUNTIME_CLASS 指定的运行时类对象。 AddDocTemplate 是类CWinApp的一个成员函数当调用了该函数后就建立 了应用程序类、文档类、视图类和主框架类之间的相互联系。 2、在多文档应用程序的应用程序类InitInstance()函数中同样可以看到这样的代码 BOOL CMyApp::InitInstance() { CMultiDocTemplate* pDocTemplate; pDocTemplate new CMultiDocTemplate ( IDR_MYTYPE, //以中文名字命名的项目名的项目资源ID号 RUNTIME_CLASS(CMyDoc),//以中文名字命名的项目名的项目文档类 RUNTIME_CLASS(CChildFrame),// MDI文档窗口类 RUNTIME_CLASS(CMyView)//以中文名字命名的项目名的项目视图类 ); AddDocTemplate(pDocTemplate); // create main MDI Frame window创建主框架窗口 CMainFrame* pMainFrame new CMainFrame; if (!pMainFrame-LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd pMainFrame; …… Return TRUE; } 由于多文档模板是用于建立资源、文档类、视图类和子框架窗口文档窗口类之间的联系的因而多文档的主框架窗口需要额外的代码来创建。代码中 LoadFrame()是CFrameWnd类成员函数用于加载与主框架窗口相关的菜单、加速键、图标等资源。 说明多文档主框架窗口的创建应在多文档模板创建后进行以便MFC程序框架将多文档模板和多文档主框架窗口建立联系。 应用程序类对象在模板被创建之前就已经存在但此时文档、视图及框架对象 还没有被创建。程序运行后程序框架会动态地创建这些对象。这一动态创建的 过程包含了对C语言的非常复杂的运用。 通过在用户文档类、视图类以及主框架类的定义.h及实现.cpp过程中 使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏MFC库可以动 态地创建相应的类对象。 这种称为运行时类的动态创建机制简化了程序员编程工作拓展了“类”的 功能是MFC程序框架结构的重要特性。 6.2.2 文档模板字串资源 在MFC AppWizard创建的应用程序资源中许多资源标识符都是IDR_MAINFRAME 意味着这些具有同名标识的资源将被框架自动加载到应用程序中。其中String Table (字符串)资源列表中也有一个IDR_MAINFRAME项他是用于标识文档类型、标题等内容的称为“文档模板字串资源”。其内容如下以单文档“模板”为例 模板\n\nMy\n\n\nMy.Document\nMy Document 可以看出IDR_MAINFRAME所标识的字符串被分成了一些以“\n”结尾的子串这些子串共有7段每段都有特定的用途如252页表6.5 文档模板字符串的含义 IDR_MAINFRAME的子串 串号 用途 模板\n 0 应用程序窗口标题即窗口标题栏:无标题--模板 \n 1 文档根名。对多文档应用程序来说若在文档 窗口标题上显示“Sheet1”则其中的Sheet就 是文档根名若该子串为空则文档名为默认 的“无标题” My\n 2 新建文档的类型名。若有多个文档类型则这 个名称将出现在“新建”对话框中 \n 3 通用对话框的文件过滤器正文 \n 4 通用对话框的文件扩展名 My.Document\n 5 在注册表中登记的文档类型标识 My Document 6 在注册表中登记的文档类型名称 但对于MDI多文档来说上述的字串分别由IDR_MAINFRAME和IDR_MYTYPE (项目名为中文名“字串资源“)2项组成其中IDR_MAINFRAME表示窗口标题而IDR_MYTYPE表示后6项内容。它们的内容如下 IDR_MAINFRAME My中文项目名 IDR_MYTYPE \nMy\n\n\nMy.Document\nMy Document 实际上文档模板字串资源内容既可直接通过字串资源编辑器进行修改也可以在文档应用程序中创建向导的第4步中通过“Advanced Options”对话框中的 “Document Template String”页面来指定如书235页图6.4所示名字是Ex_SDI。 图中的数字表示该项的含义与上表中对应串号的含义相同。 6.2.3使用多个文档类型 在MFC AppWizard创建的应用程序中通常只有一种文档类型。但有时用户也需要另一种文档类型。例如Visual C6.0本身既要处理文本文件也要处理MFC资源至少要有2种文档类型。 多种文档类型的应用程序中一般有多个文档模板、多个文档类以及与之紧密相联的多个视图类。 当用户选择“文件”菜单的“新建”命令时应用框架将弹出含有文档类型列表的对话框允许用户自己选择所需的文档类型。下面是实现在多文档应用应用程序中使用多种文档类型。 例使用多个文档 1用MFC AppWizard创建一个多文档应用程序Step1第一步就单击[Finish] 即可 23“多个文档类型实例” 4\nPicture\nMyDenmo图片\n图片文件(*.bmp)\n.bmp\nMyDemo.Document\n My Document 5在“字符串属性”对话框中将ID设为IDR_OTHERTYPE130标题内容设 为\nTxt\nMyDemo 文本\n 文本文件*.txt,*.cpp,*.h\n.txt;*.cpp,*.h\n MyDemo.Document\n MyDocument 6COtherDoc 7COtherDoc 8pDocTemplatenew CMultiDocTemplate( IDR_OTHERTYPE, RUNTIME_CLASS(COtherDoc),//指定新的文档类 RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(COtherView));//指定新的视图类 AddDocTemplate(pDocTemplate); 9 #include OtherDoc.h #include OtherView.h 10运行结果见书 说明A、如果在该例程序中再添加图标和菜单资源并使资源标识设为IDR_OTHERTYPE 则当创建“MyDemo文本”文档类型后程序会自动使用新的图标和菜单资源。 B、单文档应用程序也可以有多个文档类型它的实现方法与多文档类似也是通过添 加文档模板来实现的只不过每次只能在文档窗口视图中显示一个文档。 6.3文档序列化 用户处理的数据往往需要存盘作为永久备份。将文档类中的数据成员变量的值保存在磁盘文件中或者将存储的文档文件中的数据读取到相应的成员变量中这个过程称为序列化Serialize。 6.3.1文档序列化过程 在使用MFC程序结构进行文档序列化之前我们先了解对文档不同操作的具体程序运行过程。 1创建空文档256页有说明 应用程序类的InitInstance函数在调用了AddDocTemplate函数之后会通 过CWinApp::ProcessShellCommand间接调用CWinApp的另一个非常有用的成员函数OnFileNew并依次完成文档各个步骤的创建。….. 2打开文档 当MFC AppWizard创建应用程序时它会自动将“文件File”选单中的“打开Open”命令ID号为ID_FILE_OPEN映射到CWinApp的OnFileOpen 成员函数。这一结果可以从应用类(.cpp)的消息入口处得到验证见书257页 除了使用“文件File”à“打开Open”选项外用户也可以选择最近使用过的文件列表来打开相应的文档。在应用程序的运行过程中系统会记录下4个默认最近使用过的文件并将文件名保存在Windows的注册表中。当每次启动应用程序时应用程序都会在“文件File”选单中显示最近使用过的文件名称。 需要说明的是MFC为我们重载了Serialize函数不必使用CFile类就可以完成相应的文档操作。写磁盘和串口访问这样这样的任务 例使用Serialize函数读取文档中的数据附加 1打开前面的“多文档”应用程序或重新建一个单或多文档应用程序。 2加成员变量 工作区ClassViewà右键对准CMyDocà单击àAdd Member Variableà右键 单击à出现对话框àVariable Type处写char àVariable Name处写m_ch[300] 在文档类中加了一个成员变量。 3在文档类即多文档Doc.cpp中找到void CMyDoc::Serialize(CArchive ar) 函数并加代码 void CMyDoc::Serialize(CArchive ar)//CArchive的对象是ar引用 { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here for(int i0;isizeof(m_ch);i) arm_ch[i]; CString str; str.Format(_T(%s),m_ch); AfxMessageBox(str); } } 这样当我们通过选单的“打开Open”成功打开用*.*一个文件时将弹出一个对话框显示出该文件的前300个字符的内容。 3保存文档257页 当MFC AppWizard创建应用程序时它会自动将“文件File”选单中的“保存Save”命令与文档类CDocument的OnFileSave函数在内部关联起来但用户在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作。 1 弹出通用文件“保存”对话框让用户提供一个文件名 2 调用文档对象的CDocument::OnSaveDocument虚函数接着又自动调用Serialize函数将CArchive对象的内容保存在文档中。 说明 A、只有在保存文档之前还没有存过盘即没有文件名或读取的文档是“只读” 的OnFileSave函数才会弹出通用“保存”对话框否则只执行第2步。 B、“文件File”菜单中还有一个“另存为Save As”命令它是与文档类 CDocument的OnFileSaveAs函数相关联。不管文档有没有保存过OnFileSaveAs 都会执行上述2个步骤。 C、上述文档存盘的必要操作都是由系统自动完成的。 除了系统自动将文档存盘外用户可以用ClassWizard来重载CDocument::OnSaveDocument函数并可在Serialize()函数体的ar.IsStorinr()为“真”的条件语句处添加代码从而在文档中保存用户自己的数据。 例使用Serialize函数保存文档中的数据附加 1打开前面的“多文档”应用程序或重新建一个单或多文档应用程序。 2如果是重建的应用程序也按前面第二步在文档类CMyDoc中加一个成员变量char m_ch[300]。 3在文档类即多文档Doc.cpp中找到void CMyDoc::Serialize(CArchive ar) 函数并加代码 void CMyDoc::Serialize(CArchive ar)// CArchive的对象是ar引用 { if (ar.IsStoring()) { // TODO: add storing code here for(int i0;isizeof(m_ch);i) arm_ch[i]; } else { // TODO: add loading code here for(int i0;isizeof(m_ch);i) arm_ch[i]; CString str; str.Format(_T(%s),m_ch); AfxMessageBox(str); } } 上述代码的结果是当保存的文件名成功指定后该文件保存m_ch中的300字符。若文件中有其它数据则数据被自动清除。 可用Word随便建个文件名“小山”先打开一个文件成功后再用保存将刚才打开的文件另存为“小山”退出再运行之后打开小山就是刚才打开的文件内容 4、关闭文档 当用户试图关闭文档或退出应用程序时应用程序会根据用户对文档的修改与否来进一步完成下列任务。 1若文档内容已被修改则弹出一个“消息”对话框询问用户是否需要将文档保存。当用户选择“是”则应用程序执行OnFileSave过程。 2调用CDocument::OnCloseDocument虚函数关闭所有与该文档相关联的文档窗口及相应的试图调用文档类CDocument的DeleteContents清除文档数据。 MFC应用程序通过CDocument的protected类型成员变量m_bModified的逻辑值来判断用户是否对文档进行修改如果m_bModified为“真”则表示文档被修改。对于用户来说可以通过CDocument的SetModifiedFlag成员函数来设置或通过IsModified成员函数来访问m_bModified的逻辑值。当文档创建、从磁盘中读出以及文档存盘时文档的这个标记就被置为FLASE假而当文档数据被修改时用户必须使用SetModifiedFlag函数将该标记置为TRUE。这样当用户关闭文档时应用程序才会显示询问消息对话框。多文档与单文档类似。 6.3.2文档序列化操作 打开和保存文档系统都会自动调用Serialize函数。而MFC AppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数通过在该函数中加代码可达到实现数据序列化的目的。例如上面的“打开”“保存”都在Serialize里加了代码。 void CMyDoc::Serialize(CArchive ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } } 代码中Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring 的结果是“真”还是“假”就可决定向文档写或读数据。 CArchive归档类提供对文件数据进行缓存同时还保存一个内部标记用于标识文档是存入写盘还是载入读盘。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作提供“”和“”运算符用于向文件写入简单的数据类型以及从文件中读取它们。下面是CArchive支持的常用数据类型 类 型 描 述 类 型 描 述 BYTE 8位无符号整型 WORD 16位无符号整型 LONG 32位带符号整型 DWORD 32位无符号整型 float 单精度浮点 double 双精度浮点 int 带符号整型 short 带符号短整型 char 字符型 unsigned 无符号整型 除了“”和“”运算符外CArchive类还提供成员函数ReadString和 WriteString用于从一个文件对象中读、写一行文本它们的原型如下 BOOL ReadString(CString rString); LPTSTR ReadString(LPTSTR lpsz,UINT nMax); void WriteString(LPCTSTR lpsz); 其中lpsz用于指定读或写的文本内容nMax用于指定可以读出的最大字符个数。 注意当向一个文件写一行字符串时字符’\0’和’\n’都不会写到文件中。 CArchive归档类CArchive类没有基类它提供了串行化对象从文件中读 写的类型安全缓冲机制可以把CArchive对象想象成一种二进制流就象输入/输出流一样可以顺序高效的处理二进制对象数据。使用CArchive对象之前必须先创建一个CFile对象同时保证CArchive对象的读写标志设置和文件打开方式相一致。对于一个CArchive对象可以进行存储操作也可以读取但不能2者同时进行。 1CArchive类操作符””和”” CArchive对象的引用类似于流的输入/输出cin/cout可以使用一系列的 操作符以简化程序例如 int x; CString y; …… arxy; ar是CArchive类的对象,见Serialize(CArchive ar)函数 2、CArchive类的成员函数 Read()、Write() 读写指定字节数的缓冲区内容 ReadString()、WriteString()读写一行文本 ReadObject()、WriteObject()调用一个对象的Serialize函数来读或写 ReadClass()、WriteClass()读写一个CRuntimeClass指明对象 IsLoading()、IsStoring()判断当前读写状态 3、Serialize()函数 在”MFC AppWizard”自动生成的程序框架中文件的串行化操作都是由CDocument派生类成员函数Serialize()完成的。它的结构如下 void CMySdiDoc::Serialize(CArchive ar) { if(ar.IsStoring()) { //TODO:…. } else { //TODO…… } } 在这个成员函数中使用CArchive对象完成具体的操作。参数中传递进来的CArchive引用对象ar是由MFC程序框架根据用户输入需要执行串行化操作时创建的它包含了所需要的文件的信息使用它可以对各种CArchive类支持的数据格式进行读写、调用其它CObject派生类对象的串行化函数等。 “MFC AppWizard”自动生成的代码已经完成的工作是用户选择”打开”、”保存”或”另存为”等命令时程序框架创建这个文件的CFile对象将它关联到新创建的CArchive对象上并设置CArchive对象的”Store”或”Load”标志用这个对象来调用CDocument派生类的Serialize()成员函数。在Serialize()函数完成读写操作返回后自动删除Serialize()函数和CFile对象。祥见最后的例题《学生档案管理程序》 例一个简单的文档序列化示例259页 12 3 char m_chArchive[100];//读、写数据时使用 CString m_strArchive; //读、写数据时使用 BOOL m_bIsMyDoc; //用于判断文档 4m_bIsMyDocFALSE; 5 strcpy(m_chArchive,这是一个用于测试文档的内容!); m_strArchive这是一行文本!; m_bIsMyDocTRUE; 6 if(m_bIsMyDoc)//是自己的文档 { for(int i0;isizeof(m_chArchive);i) arm_chArchive[i]; ar.WriteString(m_strArchive); } else AfxMessageBox(数据无法保存!); } else //此条原程序上有的 { arm_chArchive[0];//读取文档首字符 if(m_chArchive[0])//是自己的文档 { for(int i1;isizeof(m_chArchive);i) arm_chArchive[i]; ar.ReadString(m_strArchive); CString str; str.Format(%s%s,m_chArchive,m_strArchive); AfxMessageBox(str); m_bIsMyDocTRUE; } else//不是自己的文档 { m_bIsMyDocFALSE; AfxMessageBox(打开的文档无效!); } }//原有 }//原有 7不做与第二步重复了 8结果见260页图6.12 6.3.3使用简单数据组集合类 上述文档的读、写是通过变量来存取文档数据的实际上还可以使用MFC提供的集合类来进行操作。这样不仅可以有利于优化数据结构简化数据的序列化而且保证数据类型的安全性。 MFC提供的集合类可分为3类 (1)链表集合类List (2)数组集合类(Array) (3)映射集合类(Map) 在这讨论的是简单数组类它包括 CObArray对象数组集合类 CByteArrayBYTE数组集合类8位无符号整型 CDWordArray(DWORD数组集合类)32位无符号整型 CPtrArray指针数组集合类 CStringArray字符串数组集合类 CUIntArrayUINT数组集合类Windows所用的数据类型unsigned int 32位无符号整数 CWordArrayWORD数组集合类16位无符号整型 简单数组集合类是一个大小动态可变的数组数组中的元素可用下标运算符“[]” 来访问从0开始设置或获取元素数据。若要设置超过数组当前个数的元素的值可以指定是否使数组自动扩展。当数组不需扩展时访问数组集合类的速度与访问标准C中的数组的速度同样快以下的基本操作对所有的简单数组集合类都适用。 1、简单数组集合类的构造及元素的添加 对简单数组集合类构造的方法都是一样的均是使用各自的构造函数它们的原型如下 CByteArray CByteArray() // BYTE数组集合类 CDWordArray CDWordArray() // DWORD数组集合类 CObArray CObArray() // 对象数组集合类 CPtrArray CPtrArray() // 指针数组集合类 CStringArray CStringArray() // 字符串数组集合类 CUIntArray CUIntArray() // UINT数组集合类 CWordArray CWordArray() // WORD数组集合类 下面的代码说明了简单数组集合类的2种构造方法 CObArray array; //使用默认的内存块大小 CObArray *pArraynew CObArray; //使用堆内存中的默认的内存块大小 为了有效使用内存在使用简单数组集合类之前最好调用成员函数SetSize设置此数组的大小与其对应的函数是GetSize用于返回数组的大小。原型如下 void SetSize(int nNewSize,int nGrowBy-1); int GetSize()const; 其中参数nNewSize用于指定新的元素的数目必须大小或等于0。nGrowBy表示当数组需要扩展时允许可添加的最少元素数目默认时为自动扩展。 向简单数组集合类添加一个元素可使用成员函数Add和Append它们的原型如下 int Add(CObject *newElement); int Append(const CObArraysrc); 其中Add函数是向数组的末尾添加一个新元素且数组自动增1。如果调用的函数SetSize的参数nGrowBy的值大于1那么扩展内存将被分配。此函数返回被添加的元素序号元素序号就是数组下标。参数newElement表示要添加的相应类型的数组元素。而Append函数是向数组的末尾添加由src指定的另一个数组的内容。函数返回加入的第1个元素的序号。 2、访问简单数组集合类的元素 在MFC中一个简单数组集合类元素的访问既可以使用GetAt函数也可以使用“[]”运算符例如 //CObArray::operator[]示例 CObArray array; //CObArray是对象数组集合类 CAge *pa; //CAge是一个用户类 array.Add(new CAge(21));//添加一个元素 array.Add(new CAge(40));//再添加一个元素 pa(CAge*)array[0];//获取元素0 Array[0]new CAge(30);//替换元素0 //CObArray::GetAt示例 CObArray array; array.Add(new CAge(21));//元素0 array.Add(new CAge(40));//元素1 3、删除简单数组集合类的元素 1使用函数GetSize和整数下标值访问简单数组集合类中的元素 2若对象元素是在堆内存中创建的则使用delete操作符删除每一个对象元素 3调用函数RrmoveAll删除简单数组集合类中的所有元素 例如以下代码是一个CObArray的删除示例 CObArray array; CAge *pa1; CAge *pa2; array.Add(pa1new CAge(21)); array.Add(pa2new CAge(40)); ASSERT(array.GetSize()2); for(int i0;iarray.GetSize();i) delete array.GetAt(i); array.RemoveAll(); 函数RemoveAll是删除数组中的所有元素而函数RemoveAt(int nIndex,int nCount1) 则表示要删除数组中从序号为nIndex元素开始的数目为nCount的元素。 6.3.4文档序列化示例262页 见书263页图6.13示例首先通过对话框来输入一个学生记录记录包括学生的姓名、学号和3门成绩。然后将记录内容保存到一个对象数组集合类对象中最后通过文档序列化将记录保存到一个文件中。当添加记录或打开一个记录时还会将数据显示在文档窗口视图中。 1、添加用于学生记录输入的对话框 1创建一个单文档的应用程序名为学生记录 2345 2、添加一个CStudent类并使该类可序列化(在CMyDoc.h和CMyDoc.cpp中) //一个可序列化的类必须是CObject的一个派生类且在类声明中需要包含 //264页说明 //在CMyDoc.h中加 #endif // _MSC_VER 1000 class CStudent:public CObject { CString strName; //姓名 CString strID; //学号 float fScore1,fScore2,fScore3;//3门课程 float fAverage;//平均成绩 DECLARE_SERIAL(CStudent) //宏调用派生类本身 public: CStudent(){};//构造函数 与类同名 CStudent(CString name,CString id,float f1,float f2,float f3);//构造函数 void Serialize(CArchive ar);//声明序列化函数 void Display(int y,CDC *pDC);//在坐标为0,y处显示数据 }; //在CMyDoc.cpp中加 CStudent::CStudent(CString name,CString id,float f1,float f2,float f3)//构造函数 { strNamename; strIDid; fScore1f1; fScore2f2; fScore3f3; fAverage(float)((f1f2f3)/3.0); } void CStudent::Display(int y,CDC *pDC) //在坐标为0,y处显示数据 { CString str; str.Format(%s %s %f %f %f %f,strName,strID,fScore1, fScore2,fScore3,fAverage);//打印姓名、学号、三门成绩、平均成绩 pDC-TextOut(0,y,str); } IMPLEMENT_SERIAL(CStudent,CObject,1)//类名基类名应用程序版本号 void CStudent::Serialize(CArchive ar)//使该类的数据成员进行相关序列化操作 { if(ar.IsStoring())//判断真或假决定向文档写或读数据 arstrNamestrIDfScore1fScore2fScore3fAverage;//向文件写入 else arstrNamestrIDfScore1fScore2fScore3fAverage;//从文件读取 } 3、添加并处理菜单 1 2 CAddDlg dlg; if(IDOKdlg.DoModal()) { //添加记录 CStudent *pStudentnew CStudent(dlg.m_strName, dlg.m_strID,dlg.m_fScore1,dlg.m_fScore2, dlg.m_fScore3); m_stuObArray.Add(pStudent);//对象数组集合类对象加到数组中 SetModifiedFlag();//设置文档更改标志 UpdateAllViews(NULL);//更新视图 } 3#include 序列化Doc.h #include AddDlg.h 4、完善代码 1在CMyDoc.h中添加下列成员函数和变量用右键加 CStudent * GetStudentAt(int nIndex); int GetAllRecNum(void); CObArray m_stuObArray;//对象数组集合类CObArray的对象 2在CMyDoc.cpp中添加函数实现代码 CStudent * CMyDoc::GetStudentAt(int nIndex) { if((nIndex0)||nIndexm_stuObArray.GetUpperBound()) return 0; //超界处理 return(CStudent *)m_stuObArray.GetAt(nIndex); } int CMyDoc::GetAllRecNum() { return m_stuObArray.GetSize();//返回数组的大小 } 3在CMyDoc.cpp的析构函数里加 CMyDoc::~CMyDoc() { int nIndexGetAllRecNum(); while(nIndex--) delete m_stuObArray.GetAt(nIndex); m_stuObArray.RemoveAll(); } 4在Serialize函数中添加下列代码 if(ar.IsStoring()) {m_stuObArray.Serialize(ar);} else {m_stuObArray.Serialize(ar);} 这里m_stuObArray是一个对象数组集合类CObArray的对象当读取数据、调用Serialize成员函数时它实际上是调用集合类对象中的元素的Serialize成员函数并将对象添加到m_stuObArray中。那么它又是怎么知道元素是调用CStudent类的Serialize成员函数呢这是因为当添加学生成绩记录后一旦保存到文件中就会将CStudent类名同时存到文件中当读取时就会自动使用CStudent类。这是CObArray序列化的一个内部机制。 5在CMyView.cpp的OnDrawe函数中加 int y0; for(int nIndex0;nIndexpDoc-GetAllRecNum();nIndex) { pDoc-GetStudentAt(nIndex)-Display(y,pDC); y16; } 6打开文档的字串资源IDR_MAINFRAME将其内容修改为 文件名\nStudentRec\nEx_Stu\n记录文件(.rec)\n.rec\n 文件名.Document\nEx_Stu Document 7运行结果见263页图6.13 6.3.5使用CFile类 在MFC中CFile类是一个文件I/O的基类它直接支持非缓冲、二进制的磁盘文件的输入、输出也可以使用其派生类处理文本文件CStdioFile和内存文件CMemFile。CFile类的读、写功能类似于C语言中的fread和fwrite而CStdioFile类的读、写功能类似于C语言中的fgets和fputs。 使用CFile类可以打开或关闭一个磁盘文件、向一个文件读或写数据等。 1、文件的打开和关闭 在MFC中使用CFile打开一个文件通常使用以下2个步骤 1构造一个不带任何参数的CFile对象 2调用成员函数Open并指定文件路径以及文件标志 CFile类的Open函数原型如下 BOOL Open(LPCTSTR lpszFileName,UINT nOpenFlags,CFileException *pError NULL); 其中lpszFileName用于指定一个要打开的文件路径该路径可以是相对的、绝对的或是一个网络文件名UNC。 nOpenFlags用于指定文件打开的标志它的值如下表或267页表6.9所示 pError用于表示操作失败产生的CFileException指针。 CFileException是一个与文件操作有关的异常处理类。 函数Open操作成功时返回TRUE否则为FALSE CFile类的文件访问方式如下 CFile::modeCreate表示创建一个新文件若该文件已存在则将文件原有内容清除 CFile::modeNoTruncate 与CFile::modeCreate组合。若文件已存在不会将文件原 有内容清除 CFile::modeRead 打开文件只读 CFile::modeReadWrite打开文件读与写 CFile::modeWrite打开文件只写 CFile::modeNoInherit防止子线程继承该文件 CFile::shareDenyNone共享文件的读和写若其他线程用相关方式打开过此文件 则创建失败 CFile::shareDenyRead禁止其他线程读此共享文件若其他线程用相关方式打开过 此文件则创建失败 CFile::shareDenyWrite禁止其他线程写此共享文件若其他线程用相关方式打开过 此文件则创建失败 CFile::shareExclusive禁止其他线程读、写此共享文件若其他线程用相关方式打 开过此文件即使是当前线程也会使创建失败 例如以下代码将显示如何用读、写方式创建一个新文件 Char *pszFileName “c:\\test\\myfile.dat”; CFile myFile; CFileException fileException; if(!myFile.Open(pszFileName,CFile::modeCreate|CFile::modeReadWrite),fileException) { TRACE(“Cant open file %s,error%u\n”,pszFileName,fileException.m_cause); } 代码中若文件创建打开有任何问题Open函数将在他的最后一个参数中返回 CFileException(文件异常类)对象TRACE宏将显示出文件名和表示失败原因的代码。使用AfxThrowFileException函数将获得更详细的有关错误的报告。 文件“关闭”使用Close函数若该对象是在堆内存中创建的还需要调用delete来删除它不是删除物理文件。 例使用CFile类完成从磁盘中读取数据的应用程序为267页附加举例 1用AppWizard创建一个单文档应用程序 2将ID_FILE_OPEN的消息映射加到View中。 ViewàClassWizardàClass name处置CMyView视图类里à在Object IDs 里找到ID_FILE_OPEN点黑àCOMMANDàAdd FunctionàEdit Code 3添加代码 void CMyView::OnFileOpen() { // TODO: Add your command handler code here CMyDoc* pDoc GetDocument(); ASSERT_VALID(pDoc); CString FilePathname; CString FileName; CDC *pDCGetDC(); CFile MyFile;//使用不带参数的CFile对象 CFileDialog dlg(TRUE,_T(TXT),_T(*.TXT),OFN_HIDEREADONLY| OFN_OVERWRITEPROMPT,_T(文本文件(*.TXT)|*.TXT|)); //利用CFileDialog类创建打开对话框设置打开文件的类型 if(IDOKdlg.DoModal()) { FilePathname.Format(%s %s,filepath:,dlg.GetPathName()); FileName.Format(%s %s,Old file name:,dlg.GetFileName()); MyFile.Open(dlg.GetFileName(),CFile::modeRead); } pDC-TextOut(0,0,FileName); pDC-TextOut(0,20,FilePathname); pDC-TextOut(0,40,文件已被打开); } 4编译运行出现空白对话框 文件à打开à在一个一般的单文档应用程序中找到如ReadMe文件点黑是.TXT类型的à打开à就见有一些文字说明了。 2、文件的读、写和定位 CFile类支持文件的读、写和定位操作。他们相关函数的原型如下 UINT Read(void *lpBuf,UINT nCount); 此函数将文件中指定大小的数据读入指定的缓冲区并返回向缓冲区传输的字节数。这个返回值可能小于nCount这是因为可能到达了文件的结尾。 void Write(const void *lpBuf,UINT nCount); 此函数将缓冲区的数据写到文件中。参数lpBuf用于指定要写到文件中的数据缓冲区的指针nCount表示从数据缓冲区传送的字节数。对于文本文件每行的换行符也被计算在内。 LONG Seek(LONG lOff,UINT nFrom); 此函数用于定位文件指针的位置若要使定位的位置是合法的此函数将返回从文件开始的偏移量。否则返回值是不定的且激活一个CFileException对象。参数lOff用于指定文件指针移动的字节数nFrom表示指针移动方式他可以是CFile::begin从文件的开始位置、CFile::current从文件的当前位置或CFile::end (从文件的最后位置但lOff必须为负值才能在文件中定位否则将超出文件)等。 文件刚刚打开时默认的文件指针位置为0即文件的开始位置。 void SeekToBegin() 将文件指针移到文件开始位置 DWORD SeekToEnd() 将文件指针移到文件结尾位置并将返回文件大小 上述文件操作与C的fstream操作相类似。 3、获取文件的有关信息 CFile类还支持获取文件状态包括文件是否存在、创建与修改的日期和时间、逻辑大小和路径等。 BOOL GetStatus(CFileStatus rStatus)const; static BOOL PASCAL GetStatus(LPCTSTR lpszFileName,CFileStatus rStatus); 若成功获得指定文件的状态信息该函数返回TRUE否则返回FALSE。 参数lpszFileName用于指定一个文件路径这个路径可以是相对的或是绝对的 但不可以是网络文件名 rStatus用于存放文件状态信息它是一个CFileStatus结构类型。 该结构具有下列成员 CTime m_ctime 文件创建日期和时间 CTime m_mtime文件最后一次修改日期和时间 CTime m_atime 文件最后一次访问日期和时间 LONG m_size 文件大小的字节数32位带符号整数 BYTE m_attribute文件属性8位无符号整数 Char m_szFullName[_MAX_PATH]文件名 static形式的GetStatus函数将获得指定文件名的文件状态并将文件名复制至m_szFullName中。该函数仅获取文件状态并没有真正打开文件这对于测试一个文件的存在性是非常有用的。例如 CFile theFile; char *szFileName”c::\\test\\myfile.dat”; BOOL bOpenOK; CFileStatus status; if(CFile::GetStatus(szFileName,status))//该文件已存在直接打开 { bOpenOKtheFile.Open(szFileName,CFile::modeWrite);} else {bOpenOKtheFile.Open(szFileName,CFile::modeCreate|CFile::modeWrite);} 4、CFile和CArchive归档从磁盘中读写可看成是二进制流类之间的关联 CFile theFile; theFile.Open(…,CFile::modeWrite); CArchive archive(theFile,CArchive::store); CArchive构造函数的原型如下 CArchive(CFile *pFile,UINT nMode,int nBufSize4096,void *lpBufNULL); 参数pFile用于指定与之关联的文件指针。 nBufSize表示内部文件的缓冲区大小默认值为4096字节。 lpBuf表示自定义的缓冲区指针若为NULL则表示缓冲区在堆内存中 当对象清除时缓冲区内存也被释放若指明用户缓冲区对象消除 时缓冲区内存不会释放。 nMode用于指定文档是用于存入还是读取它可以是CArchive::load读取 数据、CArchive::store存入数据或CArchive::bNoFlushOnDelete 当析构函数被调用时避免文档自动调用Flush.若设置这个标志 则必须在析构函数被调用之前调用Close否则文件数据将被破坏。 也可将一个CArchive对象与CFile类指针相关联如下ar是CArchive对象 Const CFile *fpar.GetFile(); 6.4视图及视图类 视图是框架窗口的子窗口它与文档紧密相联是用户与文档之间的交互接口。视图不仅可以响应各种类型的输入例如键盘输入、鼠标输入或拖放输入、菜单、工具栏和滚动条产生的命令输入等而且能实现文档的打印和打印预览。 6.4.1一般视图类的使用 MFC中的CView类及其他的派生类封装了视图的各种不同的功能他们为用户实现最新的Windows特性提供了很大的便利。这些视图类如下所示他们都可以作为文档应用程序中视图类的基类其设置方法是在MFC AppWizard创建SDI/MDI的第六步中进行基类的选择。 CView的派生类及其功能描述 CScrollView 具有滚动或缩放功能 CFormView 提供可滚动的视图它由对话框模板创建并具有和对话框一 样的设计方法 CRecordView 提供表单视图直接与ODBC记录集对象关联和所有的表单视 图一样CRecoraView也是基于对话框模板设计的 CDaoRecordView 提供表单视图直接与DAO记录集对象关联其他同CRecordView CCtrlView 是CEditView、CListView、CTreeView和CRichEditView的基类他们提供的文档/视图结构也适用于Windows98(NT)中的新控件 CEditView 提供包含编辑控件的视图支持文本的编辑、查找、替换以及滚动功能 CRichEditView 提供包含复合编辑控件的视图它除了CEditView功能外还支持字体、颜色、图表及OLE对象的嵌入等。 CLisView 提供包含列表控件的视图他类似于Windows98资源管理器的右侧窗口 CTreeView 提供包含树状控件的视图他类似于Windows98资源管理的左侧窗口 1、CEditView类 CEditView类对象是一种视图像CEdit类一样他是提供窗口编辑控制功能可以用于执行简单文本操作如打印、查找、替换、剪贴板的剪切、复制和粘贴等。由于CEditView类自动封装上述功能的映射函数因此只要在文档模板中使用CEditView类那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可自动激活。 例如创建一个基于CEditView类的单文档应用程序 1建一个单文档基于CEditView的应用程序 2编译运行打开一个文档见书271页图6.15 需要说明的是尽管CEditView类具有编辑框控件的功能但他却不具有所见即所得编辑的功能而且只能将文本单一字体的显示不支持特殊格式的字符。 2、CRichEditView类 CRichEditView类使用了复合文本编辑控件他支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc、CRichEditCntrItem类一起使用他们可实现一个完整的ActiveX包容器应用程序。 3、使用CFormView类有用的位图显示 CFormView类是一个非常有用的视图类它具有许多无模式对话框的特点。像CDiolog的派生类一样CFormView的派生类也和相应的对话框资源相联系 它也支持对话框数据交换和对话框数据确认CDX和DDV CFormView是所有表单视图如CRecordView,CDaoRecordView,CHtmlView 等的基类一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。CFormView类的基类是CView类使用CFormView类后可以使对话框显示在窗口中这大大地方便了程序开发者。 创建表单应用程序的基本方法除了在MFC AppWizard创建的第六步中选择 CFormView作为文档应用程序视图类的基类外还可以通过选择“插入”à “新建形式New Form”菜单命令在文档应用程序中自动插入一个表单。 例如制作一个使用CFormView类的应用程序当点击窗口中名为“显示背景图” 的按钮时窗口会显示一幅位图。为271页附加 1建一个单文档应用程序“背景图形”在Step6第6步将视图的基类选择为 CFormView对程序进行编译并运行可以看到一个对话框和窗口结合的 应用程序在对话框上添加各种控件和普通对话框模板完全一样。 2将对话框上原有东西删掉加一个按钮ID等不动名字为显示背景图 3在视图的头文件的public中添加如下代码 CBitmap m_bitmap; 4将按钮消息IDC_BUTTON1映射到View中去。 5在如上按钮消息函数中加代码 void CMyView::OnButton1() { // TODO: Add your control notification handler code here CDC *pDCGetDC(); HBITMAP hBitmap(HBITMAP)LoadImage(NULL,_T(Bkground.bmp), IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE| LR_LOADFROMFILE); m_bitmap.Attach(hBitmap); BITMAP bm; m_bitmap.GetBitmap(bm);//注意此处好出错与你加的图形有关。 CDC dcImage; if(!dcImage.CreateCompatibleDC(pDC)) return; CBitmap *pOldBitmapdcImage.SelectObject(m_bitmap); pDC-BitBlt(0,0,bm.bmWidth,bm.bmHeight,dcImage,0,0,SRCCOPY); //调用BitBlt()函数填充窗口背景 dcImage.SelectObject(pOldBitmap); DeleteObject(m_bitmap.Detach());//释放位图存储空间 } 注意一定将准备好的.bmp图形拷贝到此程序的文件夹中并重新命名为 Bkground.bmp在我的文档里有此图形玩具2 4、CHtmlView类的应用 CHtmlView类具有在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址也可以作为本地文件和网络文件系统的窗口它支持超级连接、统一资源定位URL导航器并维护历史列表等。 例1将我们经常浏览的网站分成6类新闻、聊天、影视、体育、音乐、购物。 点击到哪个浏览器会自动连接到相应的网站。此处是使用CHtmlView类 创建一个浏览器而窗口上的工具条是通过DialogBar类来实现的。为271页附加 1运行AppWizard创建一单文档应用程序第6步将视图基类置为CHtmlView 2创建工具条 * 拷贝6个.bmp图形即新闻、聊天、影视、购物、体育、音乐到本程序的文 件夹中U盘有这6个图形 * 将如上6个.bmp图形加到项目中 InsertàResourceà点黑BitmapàImportà出现对话框à选文件类型为*.*à 逐个或一起把这6个图形加进去。并将这6个.bmp图形的IDB改成 “IDB_BITMAP1U” “IDB_BITMAP2U” “IDB_BITMAP3U” “IDB_BITMAP4U” “IDB_BITMAP5U” “IDB_BITMAP6U” 1建个对话框 ID改为IDD_MYDIALOG InsertàResourceà点黑DialogàNewà出现对话框将ok和cancel删去à把 按钮Button拖过来做适当大小比原来稍大些能放下图形这样再拷贝同样大小5个按钮并在属性栏里写 ID处写IDC_BUTTON1 Caption处写IDB_BITMAP1 ID处写IDC_BUTTON2 Caption处写IDB_BITMAP2 ID处写IDC_BUTTON3 Caption处写IDB_BITMAP3 ID处写IDC_BUTTON4 Caption处写IDB_BITMAP4 ID处写IDC_BUTTON5 Caption处写IDB_BITMAP5 ID处写IDC_BUTTON6 Caption处写IDB_BITMAP6 在Styles风格下置好Owner draw和Bitmap右键对准按钮击出 在此对话框本身IDD_MYDIALOG的Styles风格里选Child 及 Dialog Frame 一定把Title bar的对号去掉所有都没对号。右键对准这个对话框击出 2手工添加工具条按钮的消息映射 1在view.h里加 //{{AFX_MSG(CMyView) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! afx_msg void OnMyAddress(UINT nID); //}}AFX_MSG 2在view.cpp里加 BEGIN_MESSAGE_MAP(CMyView, CHtmlView) //{{AFX_MSG_MAP(CMyView) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! ON_COMMAND_RANGE(IDC_BUTTON1,IDC_BUTTON6,OnMyAddress) //}}AFX_MSG_MAP // Standard printing commands 3添加代码 1 在MainFrame.h的public里加用CDialogBar类完成工具条显示 CDialogBar m_WndDlgBar; CBitmapButton w_MyBitmapButton1, w_MyBitmapButton2, w_MyBitmapButton3, w_MyBitmapButton4, w_MyBitmapButton5, w_MyBitmapButton6; 2在MainFrame.cpp里加在原有函数OnCreate()里加 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) -1) return -1; m_WndDlgBar.Create(this,IDD_MYDIALOG,CBRS_TOP, AFX_IDW_DIALOGBAR); w_MyBitmapButton1.AutoLoad(IDC_BUTTON1,m_WndDlgBar); w_MyBitmapButton2.AutoLoad(IDC_BUTTON2,m_WndDlgBar); w_MyBitmapButton3.AutoLoad(IDC_BUTTON3,m_WndDlgBar); w_MyBitmapButton4.AutoLoad(IDC_BUTTON4,m_WndDlgBar); w_MyBitmapButton5.AutoLoad(IDC_BUTTON5,m_WndDlgBar); w_MyBitmapButton6.AutoLoad(IDC_BUTTON6,m_WndDlgBar); --------------- return 0; } 这里可将原有其它的全注释掉。 3在View.h的public里加 CString MyAddressStr;//用于记录URL地址 4在View.cpp里加全用手工写连同函数本身 void CMyView::OnMyAddress(UINT nID) { switch(nID) { case IDC_BUTTON1:MyAddressStrhttp://www.sohu.com;break; case IDC_BUTTON2:MyAddressStrhttp://www.sina.com;break; case IDC_BUTTON3:MyAddressStrhttp://www.nanshan.edu.cn;break; case IDC_BUTTON4:MyAddressStrhttp://www.163.com;break; case IDC_BUTTON5:MyAddressStrhttp://www. badu.com;break; case IDC_BUTTON6:MyAddressStrhttp://www.yahoo.com;break; } Navigate2(_T(MyAddressStr),NULL,NULL); Refresh(); } 4加映射消息 ViewàClassWizardàClass name里置为视图viewà在右边Messgas表里找到 OnInitialUpdate加到视图View.cpp里 void CMyView::OnInitialUpdate() { CHtmlView::OnInitialUpdate(); Navigate2(_T(MyAddressStr),NULL,NULL); // TODO: This code navigates to a popular spot on the web. // change the code to go where youd like. Navigate2(_T(http://www.microsoft.com/visualc/),NULL,NULL); } 5在View.cpp的构造函数里加 MyAddressStrhttp://www.nashan.edu.com.cn; 8运行能直接上网后你再选你定义的6个网址点哪个就可到哪个网站上。 例2制作网页 利用Visual C6.0中的Web技术的新类型和控件连接涉及HTML和JavaScript的新特性可以通过CHtmlView类和浏览器控件随意加载HTML文档本例中将利用Visual C上的开发工具制作2个Web页并且在下一个实例中例3加载该页。 1创建Web网页 桌面状态双击Visual C系统à文件àNewàFileàHTML Pageà右面路径写e:\vcppà文件名字处写The first pageàOK 2在已有的程序语句BODY和/BODY间加代码 H1 This is my first Web Page! /H1 H1 This is my first Web Page! /H1 H1 This is my first Web Page! /H1 a hrefThe%20Second%20page.htmHyperlink! The next Page!/a//通过这条 //语句实现网页的超级连接。 注意写完后存盘退出FileàSave..--Exit 3再在桌面状态双击Visual C系统à文件àNewàFileàHTML Pageà右面路径写e:\vcppà文件名字处写The Second pageàOK 4添代码 HTML HEAD META NAMEGENERATOR ContentMicrosoft Visual Studio 6.0 TITLE/TITLE /HEAD BODY bgColorblanchedalmond backgroundBalloon.jpg CENTER H1Our first JavaScript Example /H1BRBRINPUT idbutton1 name关闭 typebutton value关闭窗口 onClickCloseWindow(); /CENTER P PRE SCRIPT languageJavaScript document.writeln(Hello and welcome to JavaScript) var dateHello JavaScript; document.writeln(date); document.write(date); function CloseWindow() { window.close() } /SCRIPT PREP/P/PRE/PRE /BODY /HTML 象上面一样存盘退出后你在e:\vcpp中找到The first page和The Second page这2个刚刚建好的文件双击The first page出现一个页你再单击此页上的 Hyperlink! The next Page!就可浏览第二个网页。 注意事先要将实例 59中的图形拷到和你的文件在一起的目录vcpp下名字叫Balloon.jpg 注意要修改你写的HTML文件要双击Visual C系统àFileàOpenà*.*à找到你刚才建的文件名The first page和The Second单击The first pageà打开à就可以修改了。 例3加载网页 由于有了CHtmlView类和浏览器控件我们可任意在自己开发的应用程序中加载HTML文档。先通过一个“打开”对话框获取HTML文件的路径和名称然后通过CHtnlView类的成员函数Navigate2()打开该网页。 1建一个单文档应用程序名为加载网页第6步将基类置CHtmlView 2将系统原有的OPEN打开映射到View中 ViewàClassWizardàClass name处置Viewà左面找到ID_FILE_OPEN点黑 àCOMMANDàAdd FunctionàEdit Code 3添代码 void CMyView::OnFileOpen() { // TODO: Add your command handler code here CFileDialog dlg( TRUE,_T(htm),_T(*.htm), OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, _T(网页 (*.htm)|*.htm|)); if(IDOKdlg.DoModal()) { Navigate2(dlg.GetPathName(),NULL,NULL); } } 运行后在出现的空白窗口上à点文件à打开à在vcpp里找到The first page点黑à打开à出现第1个网页àHyperlink! The next page点黑à便出现第2个网页。 5、CScrollView类 CScrollView类不仅能直接支持视图的滚动操作而且还能管理视图的大小和映射模式能响应滚动条消息、键盘消息以及鼠标消息。 需要说明的是当滚动视图应用程序创建后MFC AppWizard会自动重载CView::OnInitialUpdate,并在该函数调用CScrollView成员函数SetScrollSizes来设置相关参数如映射模式、滚动逻辑窗口的大小、水平或垂直方向的滚动量等。如果仅需要视图具有自动缩放功能而不具有滚动特性则调用 CScrollView::SetScaleToFitSize函数代替MFC AppWizard添加的SetScrollSizes函数调用代码。 6.4.2列表控件和列表视图 列表控件是一种极为有用的控件之一它可以用“大图标”、“小图标”、“列表视图”或“报表视图”等4种不同的方式来显示一组信息见272页图6.16。 1大图标方式是指列表中的所有项的上方均以大图标32*32形式出现用 户可将其拖动到列表视图窗口的任意位置。 2小图标方式是指列表中的所有项的左方均以小图标16*16形式出现用 户可将其拖动到列表视图窗口的任意位置。 3列表视图方式与图标方式不同列表项被安排在某一列中用户不能拖动他们 4报表视图方式是指列表项出现在各自的行上而相关的信息出现在右边最左边的列可以是标签或图标接下来的列则是程序指定的列表项内容。其最引人注目的是他可以有标题头。 1、列表控件的风格及其修改 列表控件的风格有2类一类是一般风格如下面所示另一类是Visual C6.0在原有的基础上添加的扩展风格如LVS_EX_FULLROWSELECT,表示整行选择但它仅用于“报表视图”显示方式。 对于列表控件的一般风格的修改可先调用GetWindowLong来获取当前风格然后调用SetWindowLong重新设置新的风格。对于列表控件的扩展风格可直接调用成员函数CListCtrl::SetExtendedStyle加以设置。 列表控件的一般风格 LVS_ALIGNLEFT 在“大图标”或“小图标”显示方式中所有列表项左对齐 LVS_ALIGNTOP 在“大图标”或“小图标”显示方式中所有列标项被安排在控件的顶部 LVS_AUTOARRANGE在“大图标”或“小图标”显示方式中图标自动排列 LVS_EDITLABELS允许用户编辑项目文本但父窗口必须处理LVN_ENDLABELEDIT通知消息 LVS_ICON“大图标”显示方式 LVS_LIST“列表视图”显示方式 LVS_NOCOLUMNHEADER在“报表视图”显示方式中不显示其标题头 LVS_NOLABELWRAP在“大图标”显示方式中项目文本占满一行 LVS_NOSCROLL禁用滚动条 LVS_NOSORTHEADER当用户单击标题头时不产生任何操作 LVS_OWNERDRAWFIXED指明控件的拥有者而不是Windows负责绘制控件 LVS_REPORT“报表视图”显示方式 LVS_SHAREIMAGELISTS共享图象列表 LVS_SHOWSELALWAYS一直显示被选择的部分 LVS_SINGLESEL只允许单项选择默认时是多项选择 LVS_SMALLICON“小图标”显示方式 LVS_SORTASCENDING按升序排列 LVS_SORTDESCENDING按降序排列 2、列表项的基本操作 CListView按照MFC文档视图结构封装了列表控件CListCtrl类的功能。由于他又是从CCtrlView中派生的因此他既可以调用CCtrlView的基类CView类的成员函数又可以使用CListCtrl功能。当使用CListCtrl功能时必须先要得到CListView封装的内嵌可引用的CListCtrl对象这时可调用CListView的成员函数GetListCtrl例如以下代码 CListCtrl listCtrlGetListCtrl();//listCtrl必须定义成引用 列表控件类CListCtrl提供了许多用于列表项操作的成员函数如列表项与列的添加和删除等 1函数SetImageList *SetImageList(CImageList *pImageList,int nImageList); 其中nImageList用于指定图象列表的类型他可以是LVSIL_NORMAL(大图标)、 LVSIL_SMALL(小图标)和LVSIL_STATE(表示状态的图象列表)。 需要说明的是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_COLOR1616位色等nInitial用于指定图象列表中最初的图象数目nGrow表示当图象列表的大小发生改变时图象可以增加的数目。 int CImageList::Add(CBitmap *pbmImage,CBitmap *pbmMask); int CImageList::Add(CBitmap *pbmImage,COLORREF crMask); int CImageList::Add(HICON hIcon); 此函数用于向一个图象列表添加一个图标或多个位图。成功时返回第1个新图象的索引号否则返回-1。pbmMask表示包含屏蔽的位图指针pbmImage表示包含图象的位图指针crMask表示屏蔽色hIcon表示图标句柄。 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; 结构中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 //查找方式 32位无符号整数 LPCSTR psz; //匹配的文本 指向字符串常量的32位指针 LPARAM lparam; //匹配的值 POINT pt; //查找开始的位置坐标 UINT vkDirection; //查找方向用虚拟方向键值表示 }LVFINDINFO,LPFINDINFO; 结构中flags可以是下列值之一或组合 LVFI_PARAM 查找内容由lParam指定 LVFI_PARTIAL 查找内容由psz指定不精确查找 LVFI_STRING 查找内容由psz指定精确查找 LVFI_WRAP 若没有匹配再从头开始 LVFI_NEARESTXY靠近pt位置查找查找方向由vkDirection确定 5函数Arrange用于按指定方式重新排列列表项其原型如下 BOOL Arrange(UINT nCode); 其中nCode用于指定排列方式它可以是下列值之一 LVA_ALIHNLEFT 左对齐 LVA_ALIGNTOP 上对齐 LVA_DEFAULT 默认方式 LVA_SNAPTOGRID使所有的图标安排在最接近的网格位置处 6函数InsertColumn用于向列表控件插入新的一列函数成功调用后返回新的列的索引否则返回-1。其原型如下 int InsertColumn(int nCol,const LVCOLUMN *pColumn); int InsertColumn(int nCol,LPCTSTR lpszColumnHeading, int nFormatLVCFMT_LEFT,int nWidth-1,int nSubItem-1); 其中nCol用于指定新列的索引 lpszColumnHeading用于指定列的标题文本 nFormat用于指定列排列的方式它可以是LVCFMT_LEFT(左对齐)、 LVCFMT_RIGHT右对齐和LVCFMT_CENTER(居中对齐) nWidth用于指定列的像素宽度-1时表示宽度没有设置 nSubItem表示与列相关的子项索引-1时表示没有子项。 pColumn表示包含新列信息的LVCOLUMN结构地址其结构描述如下 typedef struct _LVCOLUMN { UINT mask; //指明哪些参数有效 int frnt; //列的标题或子项文本格式 int cx; //列的像素宽度 LPTSTR pszText; //列的标题文本 int cchTextMax;//列的标题文本大小 int iSubItem; //和列相关的子项索引 int iImage; //图象列表中的图象索引 int iOrder; //列的序号最左边的列为0 }LVCOLUMNFAR *LPLVCOLUMN 结构中mask可以是0或下列值之一或组合 LVCF_FMT fint参数有效 LVCF_IMAGE iImage参数有效 LVCF_ORDER iOrder参数有效 LVCF_SUBITEM iSubItem参数有效 LVCF_TEXT pszText参数有效 LVCF_WIDTH cx参数有效 结构中frnt可以是下列值之一 LVCFMT_BITMAP_ON_RIGHT位图出现在文本的右边对于从图象列表中选取的图象无效 LVCFMT_CENTER文本居中 LVCFMT_COL_HAS_IMAGES列表头的图象是在图象列表中 LVCFMT_IMAGE从图象列表中显示一个图象 LVCFMT_LEFT文本左对齐 LVCFMT_RIGHT文本右对齐 7函数DeleteColumn用于从列表控件中删除一个指定的列其原型如下 BOOL DeleteColumn(int nCol); 除了上述操作外还有一些函数是用于设置或获取列表控件的相关属性的。例如SetColumnWidth用于设置指定列的像素宽度 GetItemCount用于返回列表控件中的列表项个数等它们的原型如下 BOOL SetColumnWidth(int nCol,int cx); Int GetItemCount(); 其中nCol用于指定要设置的列的索引号 Cx用于指定列的像素宽度它可以是LVSCW_AUTOSIZE,表示自动调整宽度 3、列表控件的消息 在列表视图中可以用MFC ClassWizard映射的控件消息有公共控件消息如NM_DBLCLK、标题头控件消息以及列表控件消息。常用的列表控件消息有 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; 但对于LVN_ITEMACTIVATE来说上述结构变成了NMITEMACTIVATE它在结构NM_LISTVIEW基础上增加了一个成员“UINT uKeyFlags”用于表示: ALT、CTRL和SHIFT键的按下状态它的值可以是LVKF_ALT、LVKF_CONTROL 和LVKF_SHIFT。 例将当前文件夹中的文件用“大图标”、“小图标”、“列表视图”以及“报表视图”等4种不同方式在列表视图中显示出来。当双击某个列表项时还将该项的文本标签内容用消息对话框的形式显示出来。实现这个示例有两个关键问题一个是如何获取当前文件夹中的所有文件另一个是如何获取各个文件的图标以便添加到与列表控件相关联的图像列表中。第一个问题可能通过MFC类CFileFind来解决第二个问题需要使用API函数SHGetFileInfo解决。需要说明的是为了使添加到图像列表中的图标不重复本例还使用了一个字符串数组集合类对象来保存图标的类型每次添加图标时都先来验证该图标是否已经添加过。 操作步骤如书277页项目名为列表控件 1 2public: CImageList m_ImageList; CImageList m_ImageListSmall; CStringArray m_strArray; void CMyView::SetCtrlStyle(HWND hWnd, DWORD dwNewStyle)//该函数用于设置 //列表控件的一般风格 { DWORD dwOldStyle; dwOldStyle GetWindowLong(hWnd, GWL_STYLE);//获取当前风格 if ((dwOldStyleLVS_TYPEMASK)!dwNewStyle) { dwOldStyle ~LVS_TYPEMASK; dwNewStyle | dwOldStyle; SetWindowLong(hWnd, GWL_STYLE, dwNewStyle);//设置新风格 } } 3 4static int nStyleIndex1; DWORD style[4]{LVS_REPORT,LVS_ICON,LVS_SMALLICON,LVS_LIST}; CListCtrl m_ListCtrlGetListCtrl(); SetCtrlStyle(m_ListCtrl.GetSafeHwnd(),style[nStyleIndex]); nStyleIndex; if(nStyleIndex3) nStyleIndex0; 这样当程序运行后同时按下Ctrl、Shift和X键就会切换列表控件的显示方式。 5 LPNMITEMACTIVATE lpItem(LPNMITEMACTIVATE)pNMHDR; int nIndexlpItem-iItem; if(nIndex0) { CListCtrlm_ListCtrlGetListCtrl(); CString strm_ListCtrl.GetItemText(nIndex,0); MessageBox(str); } 这样当双击某个列表项时就会弹出一个消息对话框显示该列表项的文本标签内容。 6//创建图像列表 m_ImageList.Create(32,32,ILC_COLOR8|ILC_MASK,1,1); m_ImageListSmall.Create(16,16,ILC_COLOR8|ILC_MASK,1,1); CListCtrl m_ListCtrlGetListCtrl(); m_ListCtrl.SetImageList(m_ImageList,LVSIL_NORMAL); m_ListCtrl.SetImageList(m_ImageListSmall,LVSIL_SMALL); LV_COLUMN listCol; char *arCols[4]{文件名,大小,类型,修改日期}; listCol.maskLVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; //添加列表头 for (int nCol0;nCol4;nCol) { listCol.iSubItemnCol; listCol.pszTextarCols[nCol]; if(nCol1) listCol.fmtLVCFMT_RIGHT; else listCol.fmtLVCFMT_LEFT; m_ListCtrl.InsertColumn(nCol,listCol); } //查找当前目录下的文件 CFileFind finder; BOOL bWorkingfinder.FindFile(*.*); int nItem0,nIndex,nImage; CTime m_time; CString str,strTypeName; while (bWorking) { bWorkingfinder.FindNextFile(); if (finder.IsArchived()) { strfinder.GetFilePath(); SHFILEINFO fi; //获取文件关联的图标和文件类型名 SHGetFileInfo(str,0,fi,sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_LARGEICON|SHGFI_TYPENAME); strTypeNamefi.szTypeName; nImage-1; for (int i0;im_strArray.GetSize();i) { if(m_strArray[i]strTypeName) { nImagei; break; } } if(nImage0) { //添加图标 nImagem_ImageList.Add(fi.hIcon); SHGetFileInfo(str,0,fi,sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON); m_ImageListSmall.Add(fi.hIcon); m_strArray.Add(strTypeName); } //添加列表项 nIndexm_ListCtrl.InsertItem(nItem,finder.GetFileName(),nImage); DWORD dwSizefinder.GetLength(); if(dwSize1024) 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; } } SetCtrlStyle(m_ListCtrl.GetSafeHwnd(),LVS_REPORT);//设置为报表方式 m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT| LVS_EX_GRIDLINES);//设置扩展风格使得列表项一行全项选择且 //显示出网格线 m_ListCtrl.SetColumnWidth(0,LVSCW_AUTOSIZE);//设置宽度 m_ListCtrl.SetColumnWidth(1,100); m_ListCtrl.SetColumnWidth(2,LVSCW_AUTOSIZE); m_ListCtrl.SetColumnWidth(3,200); 注意程序运行后是显示书上的280页图6.17形式。改变列表显示形式要按CtrlShiftX 6.4.3树控件和树视图 一个“树控件”是一个用于显示层次列表项的窗口比如一个文档中的标题、索引中的项目或磁盘中的文件和目录每一个都包括一个标签和一个可选的位图图像还有一个与其相关的子项的列表单击一个项用户可以展开或缩进该项的相关子项。例如Visual C6.0中的项目工作区窗口就是这种树控件。 树控件类为CTreeCtrl而CTreeView类简化了CTreeCtrl类的使用却CTreeView类提供的成员函数GetTreeCtrl可使我们从CTreeView中得到封装的CTreeCtrl对象。 1、树形视图的风格 常见的树控件风格如下所示书289页表7.11其修改方法与列表控件的一般风格修改方法相同。 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使用热点跟踪 2、树控件的常用操作 树控件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 hParentTVI_ROOT, HTREEITEM hIsertAfterTVI_LAST); HTREEITEM InsertItem(LPCTSTR lpszItem,int nImage,int nSelectedImage, HTREEITEM hParentTVI_ROOT,HTREEITEM hInsertAfterTVI_LAST); 其中参数 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); 其中hItem用于指定要删除的结点的句柄。如果hItem的值是TVI_ROOT则所 有的结点都从此控件中被删除。 3函数Expand用于展开或收缩指定父结点的所有子结点其原型如下 BOOL Expand(HTREEITEM hItem,UINT nCode); 其中hItem指定要被展开或收缩的结点的句柄 nCode用于指定动作标志它可以是 TVE_COLLAPSE 收缩所有子结点 TVE_COLLAPSERESET收缩并删除所有子结点 TVE_EXPAND展开所有子结点 TVE_TOGGLE如果当前是展开的则收缩反之则展开 4函数GetNextItem用于获取下一个结点的句柄 HTREEITEM GetNextItem(HTREEITEM hItem,UINTnCode); 其中hItem指定参数结点的句柄 nCode用于指定与hItem的关系标志常见的标志有 TVGN_CARET 返回当前选择结点的句柄 TVGN_CHILD 返回第1个子结点句柄hItem必须为NULL TVGN_NEXT 返回下一个兄弟结点同一个树支上的结点句柄 TVGN_PARENT 返回指定结点的父结点句柄 TVGN_PREVIOUS 返回上一个兄弟结点句柄 TVGN_ROOT 返回hItem父结点的第1个子结点句柄 5函数HitTest用于测试鼠标当前操作的位置位于哪一个结点并返回该结点句柄 它的原型如下 HTREEITEM HitTest(CPoing pt,UINT *pFlags); 其中pFlags包含当前鼠标所在的位置标志如下列常用定义 TVHT_ONITEM 在结点上 TVHT_ONITEMBUTTON 在结点前面的按钮上 TVHT_ONITEMICON 在结点文本前面的图标上 TVHT_ONITEMLABEL 在结点文本上 除了上述操作外CtreeCtrl还有其他常见操作如下283页表6.13 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);设置由hIem指定的结点的文本 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 hIem); 用于将指定结点的所有子结点排序 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; 例遍历本地磁盘所有的目录文件夹 为了能获取本地机器中有效的驱动器需要使用GetLogicalDrives获取逻辑驱动器和GetDriveType获取驱动器函数本例使用SHGetFileInfo来进行的。操作步骤如下按书284页去作 2CString m_strPath; CImageList m_ImageList; 3打开击项目工作区的ClassView右键击CMyView加成员函数InsertFoldItem 并加以下代码 void CMyView::InsertFoldItem(HTREEITEM hItem, CString strPath) { CTreeCtrltreeCtrlGetTreeCtrl(); if(treeCtrl.ItemHasChildren(hItem))return; CFileFind finder; BOOL bWorkingfinder.FindFile(strPath); while(bWorking) { bWorkingfinder.FindNextFile(); if(finder.IsDirectory()!finder.IsHidden() !finder.IsDots()) treeCtrl.InsertItem(finder.GetFileTitle(),0,1, hItem,TVI_SORT); } } 4打开击项目工作区的ClassView右键击CMyView加成员函数GetFoldItemPath 并加以下代码 CString CMyView::GetFoldItemPath(HTREEITEM hItem) { CString strPath,str; strPath.Empty(); CTreeCtrltreeCtrlGetTreeCtrl(); HTREEITEM folderItemhItem; while(folderItem) { int data(int)treeCtrl.GetItemData(folderItem); if(data0) strtreeCtrl.GetItemText(folderItem); else str.Format(%c:\\,data); strPathstr\\strPath; folderItemtreeCtrl.GetParentItem(folderItem); } strPathstrPath*.*; return strPath; } 5 void CMyView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView (NM_TREEVIEW*)pNMHDR; // TODO: Add your control notification handler code here HTREEITEM hSelItempNMTreeView-itemNew.hItem; CTreeCtrltreeCtrlGetTreeCtrl(); CString strPathGetFoldItemPath(hSelItem); if(!strPath.IsEmpty()) { InsertFoldItem(hSelItem,strPath); treeCtrl.Expand(hSelItem,TVE_EXPAND); } 6 cs.style|TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS; 7 CTreeView::OnInitialUpdate(); CTreeCtrltreeCtrlGetTreeCtrl(); m_ImageList.Create(16,16,ILC_COLOR8|ILC_MASK,2,1); treeCtrl.SetImageList(m_ImageList,TVSIL_NORMAL); CString strPath; GetWindowsDirectory((LPTSTR)(LPCTSTR)strPath,MAX_PATH1); SHFILEINFO fi; SHGetFileInfo(strPath,0,fi,sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON); m_ImageList.Add(fi.hIcon); SHGetFileInfo(strPath,0,fi,sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON|SHGFI_OPENICON); m_ImageList.Add(fi.hIcon); CString str; for(int i0;i32;i) { str.Format(%c:\\,Ai); SHGetFileInfo(str,0,fi,sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON|SHGFI_DISPLAYNAME); if(fi.hIcon) { int nImagem_ImageList.Add(fi.hIcon); HTREEITEM hItemtreeCtrl.InsertItem(fi.szDisplayName,nImage,nImage); treeCtrl.SetItemData(hItem,(DWORD)(Ai)); } } //HWND为窗口句柄 //DWORD 为unsigned long 32位无符号整数段地址和相关的偏移地址 6.5文档视图结构 文档和视图是编程者最关心的应用程序的大部分代码都会被添加在这2个类中。文档和视图紧密相联是用户与文档之间的交互接口。用户通过文档视图结构可实现数据的传输、编辑、读取和保存等。但文档、视图以及和应用程序框架的相关部分之间还包含了一系列非常复杂的相互作用。切分窗口及一档多视是文档和视图相互作用的典型实例。 6.5.1文档与视图的相互作用 正常情况下MFC应用程序用一种编程模式使程序中数据与他的显示形式和用户交互分离开来这种模式就是“文档视图结构“文档视图结构能方便地实现文档和视图的相互作用。一旦在用MFC AppWizard(exe)创建SDI/MDI的第1步中选中了Document/View architecture support文档/视图体系结构支持复选框就可使用下列5个文档和视图相互作用的重要成员函数。287页 1、CView::GetDocument函数 视图对象只有一个与之相联系的文档对象它所包含的GetDocument函数允 许应用程序由视图得到与之相联系的文档。 2、CDocument::UpdateAllViews函数 如果由于种种原因使文档中的数据发生了改变那麽所有的视图都必须被通 知到以便它们能够对所显示的数据进行相应的更新。UpdateAllViews函数就是 起到这样的作用的。他的原型如下 void UpdateAllViews(CView *pSender,LPARAM lHintOL,CObject *pHintNULL); 参数pSender表示视图指针若在应用程序文档类的成员函数中调用该函数则此参数应为NULL,若该函数被应用程序视图类中的成员函数调用则此参数应为this。 lHint 通常表示更新视图时发送信息的提示标识值 pHint表示存储信息的对象指针 当UpdateAllViews函数被调用时如果参数pSender指向某个特定的视图对象那么除了该指定的视图之外文档的所有其他视图的OnUpdate函数都会被调用。 3、CView::OnUpdate函数 这是一个虚函数当应用程序调用了CDocument::UpdateAllViews函数时应 用程序框架就会相应地调用该函数。 virtual void OnUpdate(CView *pSender,LPARAM lHint,CObject *pHint); 参数pSender表示文档被更改的所在视图类指针当为NULL时表示所有的视图需要更新。默认的OnUpdate函数lHint0,pHingNULL使得整个窗口矩形无效。 如果用户想要使视图的某部分无效那么就要定义相关的提示Hint参数给出准确的无效区域lHint和pHint的含义同UpdateAllViews 事实上Hint机制主要用于在视图中根据提示标识值来获取文档或其他视图传递来的数据例如将文档的CPoint数据传递给所有的视图类则有下列语句 GetDocument()-UpdateAllViews(NULL,1,(CObject *)m_ptDraw); 4、 CView::OnInitialUpdate函数 当应用程序被启动时或当用户从“文件”选单中选择了“新建”或“打开” 时该CView虚函数都会被自动调用。该函数除了调用无提示参数lHint0,pHintNULL的OnUpdate函数之外没做其他任何事情。但用户可以重载此函数对文档所需信息进行初始化操作例如如果用户应用程序中的文档大小是固定的那么用户就可以在此重载函数中根据文档大小设置视图滚动范围如果应用程序中的文档大小是动态的那么用户就可以在文档每次改变时调用OnUpdate来更新视图的滚动范围。 5、CDocument::OnNewDocument函数 在文档应用程序中当用户从“文件”选单中选择“新建”命令时框架将 首先构造一个文档对象然后调用该虚函数。 6.5.2应用程序对象指针的互调 在MFC中文档视图机制使框架窗口、文档、视图和应用程序对象之间具有一定的联系通过相应的函数可实现各对象指针的互相调用。 1、从文档类中获取视图对象指针 在文档类中有一个与其关联的各视图对象的列表并可通过CDocument类的 下面2个成员函数来定位相应的视图对象。 1GetFirstViewPosition函数用于获得与文档类相关联的视图列表中第1个可 见视图的位置。 2GetNextView函数用于获取指定视图位置的视图类指针并将此视图位置 移动至下1个位置若没有下一个视图则视图位置为NULL他们原型为 virtual POSTTION GetFirstViewPosition()const; virtual CView *GetNextView(POSTTION rPosition)const; 例如以下代码是使用CDocument::GetFirstViewPosition和GetNextView重绘每个视图 void CMyDoc::OnRepaintAllViews() { POSTTION posGetFirstViewPosition(); while(pos!NULL) { CView *pViewGetNextView(pos); pView-UpdateWindow(); } }//实现上述功能也可直接调用UpdateAllViews(NULL); 2、从视图类中获取文档对象和主框架对象指针 在视图类中获取文档对象指针是很容易的只需调用视图类中的成员函数 GetDocument即可。而函数CWnd::GetParentFrame可实现从视图类中获取主框架指针其原型如下 CFrameWnd *GetParentFrame()const; 该函数将获得父框架窗口指针它在父窗口链中搜索直到一个CFrameWnd(或其派生类)被找到为止。成功返回一个CFrameWnd指针否则返回NULL。 3、在主框架中获取视图对象指针 对于SDI应用程序来说只需调用CFrameWnd类的GetActiveView成员函数即可其原型如下 CView *GetActiveView()const; 函数返回当前CView类指针若没有当前视图则返回NULL 若将此函数应用在多文档应用程序的CMDIFrameWnd类中并不是想象的那样获得当前活动子窗口的视图对象指针而是返回NULL这是因为在一个多文档应用程序中多文档应用程序主框架窗口CMDIFrameWnd没有任何相关的视图对象。相反每个子窗口CMDIChildWnd却有一个或多个与之相关的视图对象。在多文档应用程序中获取活动视图对象指针的正确方法是先获得多文档应用程序的活动文档窗口然后再获得与该活动文档窗口相关联的活动视图如下代码 CMDIFrameWnd *pFrame(CMDIFrameWnd*)AfxGetApp()-m_pMainWnd; //获得MDI的活动子窗口 CMDIChildWnd *pChild(CMDIChildWnd*)pFrame-GetActiveFrame(); //或CMDIChildWnd *pChildpFrame-MDIGetActive(); //获得与子窗口相关联的活动视图 CMyView *pView(CMyView*)pChild-GetActiveView(); 另外在框架类中还可直接调用CFrameWnd::GetActiveDocument函数获得当前活动的文档对象指针。 在同一个应用程序的任何对象中可通过全局函数AfxGetApp来获得指向应用程序对象的指针。如下说明各种对象指针的互调方法 所在的类 获取的对象指针 调用的函数 说明 文档类 视图 GetFirstViewPosition 获取第1个和下一个视图的位置 文档类 文档模板 GetDocTemplate 获取文档模板对象指针 视图类 文档 GetDocument 获取文档对象指针 视图类 框架窗口 GetParentFrame 获取框架窗口对象指针 框架窗口类 视图 GetActiveView 获取当前活动的视图对象指针 框架窗口类 文档 GetActiveDocument 获取当前活动的文档对象指针 MDI主框架类 MDI子窗口 MDIGetActive获得当前活动的MDI子窗口对象指针 6.5.3切分窗口 切分窗口是一种“特殊”的文档窗口它可以有许多窗格pane在窗格中又可包含若干个视图。 1、静态切分和动态切分 对于“静态切分”窗口来说当窗口第1次被创建时窗口就已经被切分好 了窗格的次序和数目不能再被改变但用户可以移动切分条来调整窗格的大小。 每个窗格通常是不同的视图类。 对于“动态切分”窗口来说它允许用户在任何时候对窗口进行切分用户即可以通过选择单项来对窗口进行切分也可以通过拖动滚动条中的切分框对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类。当切分窗口被创建时左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时另一个新添加的视图对象被动态创建当视图沿着2个方向被切分时新添加的3个视图对象则被动态创建。当用户取消切分时所有新添加的视图对象被删除但最先的视图仍被保留直到切分窗口本身消失为止。 无论是静态切分还是动态切分在创建时都要指定切分窗口中行和列的窗格最大数目。对于静态切分窗格在初始时就按用户指定的最大数目划分好了而对于动态切分窗口当窗口构造时第1个窗格就被自动创建。动态切分窗口允许的最大窗格数目是2*2而静态切分允许的最大窗格数目为16*16。 2、切分窗口的CSplitterWnd类 在MFC中CSplitterWnd类封装了窗口切分过程中所需的操作其中成员函数 Create和CreateStatic分别用于创建“动态切分”和“静态切分”的文档窗口函数原型如下 BOOL Create(CWnd *pParentWnd,int nMaxRows,int nMaxCols,SIZE sizeMin, CCreateContext *pContext,DWORD dwStyleWS_CHILD|WS_VISIBLE| WS_HSCROLL|WS_VSCROLL|SPLS_DYNAMIC_SPLIT,UINT Nid AFX_IDW_PANE_FIRST);//书245页有WS_CHILD等的说明 BOOL CreateStatic(CWnd *pParentWnd,int nRows,int nCols,DWORD dwStyle WS_CHILD|WS_VISIBLE,UINT nIDAFX_IDW_PANE_FIRST); 参数pParentWnd表示切分窗口的父框架窗口 nMaxRows表示窗口动态切分的最大行数不能超过2。 nMaxCols表示窗口动态切分的最大列数不能超过2 nRows表示窗口静态切分的行数不能超过16 nCols表示窗口静态切分的列数不能超过16 sizeMin表示动态切分时允许的窗格最小尺寸 CSplitterWnd类成员函数CreateView用于为静态窗格指定一个视图类并创建视 图窗口其函数原型如下 BOOL CreateView(int row,int col,CRuntimeClass *pViewClass,SIZE sizeInit, CCreateContext *pContext); 参数row和col用于指定具体的静态窗格 pViewClass用于指定与静态窗格相关联的视图类 sizeInit表示视图窗口初始大小 pContext用于指定一个“创建上下文”指针 CCreateContext创建“上下文”结构包含当前文档视图框架结构。 3、静态切分窗口实现 利用CSplitterWnd成员函数用户可以在文档应用程序的文档窗口中添加动态或静态切分功能 例将SDI应用程序中的文档窗口静态分成3x2个窗口291页 1建一个单文档的应用程序项目名为切分窗口 2在主框架MainFrm.h文件里加数据成员protected:下 CSplitterWnd m_wndSplitter; 3建一个新视图类CDemoView(基类为CView) 4CRect rc; GetClientRect(rc);//获取客户区大小 CSize paneSize(rc.Width()/2-16,rc.Height()/3-16);//计算每个窗格的平均尺寸 m_wndSplitter.CreateStatic(this,3,2);//创建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; //return CFrameWnd::OnCreateClient(lpcs, pContext); 5#include DemoView.h 6运行结果见293页图6.19 A、在调用CreateStatic函数创建静态切分窗口后必须将每个窗格用CreateView 函数指定相关联的视图类。各窗格的视图类可以相同也可以不同 B、切分功能只应用于文档窗口对于单文档应用程序切分的创建是在CMainFrame 类进行的而对于多文档应用程序添加切分功能时应在文档子窗口类CChildFrame 中进行。 4动态切分窗口的实现 动态切分窗口的创建过程要比静态切分简单得多它不需要重新为窗格指定其他视图类动态切分窗口的所有窗格共享同一个视图。若在文档窗口中添加动态切分功能除了上述方法外还可在MFC AppWizard创建文档应用程序的”Step4” 对话框中单击”高级”按钮通过选中”Advanced Options”对话框Window Styles页面中的”应用拆分窗体”(UseSplit Window)来创建或是通过添加切分窗口组件来创建 例通过添加切分窗口组件来创建动态切分293页 1创建一个单文档的应用程序名为动态切分 2选择ProjectàAdd To ProjectàComponents and Controls弹出293页图6.20所 示的“单文档应用程序动态切分”对话框。 3456 注意通过上述方法可以向应用程序添加许多类似组件如Splash screen(程序启 动画面)、Tip of the day(今日一贴)和Windows Multimedia library(Windows 多媒体库)等 6.5.4一档多视 多数情况下一个文档对应于一个视图但有时一个文档可能对应多个视图这种情况称之为“一档多视”。 1、一档多视模式 MFC对于“一档多视”提供下列3个模式 1在各自MDI文档窗口中包含同一个视图类的多个视图对象。用户有时需要应用程序能为同一个文档打开另一个文档窗口以便能同时使用2个文档窗口来查看文档的不同部分内容。用MFC AppWizard创建的多个文档应用程序支持这种模式当用户选择“窗口”菜单的“新建窗口”命令时系统就会为第1个文档窗口创建一个副本。 2在同一个文档中包含同一个视图类的多个视图对象。这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。 3在单独一个文档窗口中包含不同视图类的多个视图对象。在该模式下多个视图共享一个文档窗口。它有点像“切分窗口”但由于视图可由不同的视图类构造所以同一个文档可以有不同的显示方法。例如同一个文档同时有文字显示方式及图形显示方式的视图。 2、例在MDI中为同一个文档数据提供2种不同的显示和编辑方式一档多视 294页图6.24左边的窗格中可以调整小方块在右边窗格的坐标位置。而若在右边窗格中任意单击鼠标相应的小方块会移动到当前鼠标位置处且左边窗格的编辑框内容也随之发生改变。 1、创建表单应用程序设计表单 1建一个多文档MDI项目名为“一档多视”第6步中将视图的基类选 择为CFormView. 2参看书295页的图6.24为创建出的对话框表单IDD_MY_FORM添加以下 所列一些控件 控 件 ID号 标 题 属 性 组框 默认 坐标设置 默认 静态文本 默认 X 默认 编辑框 IDC_EDIT1 ------- 默认 旋转按钮 IDC_SPIN1 -------- Auto buddy, Set buddy integet, Alignment Right 静态文本 默认 Y 默认 编辑框 IDC_EDIT2 -------- 默认 旋转按钮 IDC_SPIN2 --------- Auto buddy, Set buddy integer, Alignment Right 3加成员变量在View中 295页表6.16 2、添加CMyDoc和CMyView类代码 1CPoint m_ptRect; //用于记录小方块的位置 2m_ptRect.xm_ptRect.y0; //或m_ptRectCPoint(0,0) 3void CMyView::OnChangeEdit() { UpdateData(TRUE); CMyDoc *pDoc(CMyDoc *)GetDocument(); pDoc-m_ptRect.xm_CoorX; pDoc-m_ptRect.ym_CoorY; CPoint pt(m_CoorX,m_CoorY); GetDocument()-UpdateAllViews(NULL,2,(CObject *)pt); } 4void CMyView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if(lHint1) { CPoint *pPoint(CPoint *)pHint; m_CoorXpPoint-x; m_CoorYpPoint-y; UpdateData(FALSE); //在控件中显示 CMyDoc *pDoc(CMyDoc *)GetDocument(); //pDoc-m_ptRect.xm_CoorX; //pDoc-m_ptRect.ym_CoorY; pDoc-m_ptRect*pPoint; //保存在文档类中的m_ptRect } } 5void CMyView::OnInitialUpdate() { CFormView::OnInitialUpdate(); //GetParentFrame()-RecalcLayout(); ResizeParentToFit(); CMyDoc *pDoc(CMyDoc *)GetDocument(); m_CoorXpDoc-m_ptRect.x; m_CoorYpDoc-m_ptRect.y; m_SpinX.SetRange(0,1024); m_SpinY.SetRange(0,768); UpdateData(FALSE); m_bEditOKTRUE; } 6编译并运行程序程序会出现一个运行错误造成这个错误的原因是旋转按钮控件在设置范围时会自动对其伙伴窗口编辑框控件进行更新而此时编辑框控件还没有完全创建好因此需要进行一些处理。 3、处理旋转按钮控件的运行错误 1 BOOL m_bEditOK; 2m_bEditOKFALSE; 3加在上面OnInitialUpdate()最后加m_bEditOKTRUE; 4OnChangeEdit()最前面加if(!m_bEditOK)return; 4、新增CDrawView 类添加框架窗口切分功能 1加新类CDrawView基类为CView 2CRect rect; GetWindowRect(rect); BOOL bResm_wndSplitter.CreateStatic(this,1,2);//创建2个水平静态窗格 m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CMyView), CSize(0,0),pContext); m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDrawView), CSize(0,0),pContext); m_wndSplitter.SetColumnInfo(0,rect.Width()/2,10);//设置列宽 m_wndSplitter.SetColumnInfo(1,rect.Width()/2,10); m_wndSplitter.RecalcLayout();//重新布局 return bRes; //CMDIChildWnd::OnCreateClient(lpcs,pContext); 3#include 一档多视View.h #include DrawView.h 4CSplitterWnd m_wndSplitter; 5编译错是基于这样一些事实在用标准C/C设计程序时有一个原则即 2个代码文件不能相互包含而且多次包含还会造成重复定义的错误。为解 决这个难题Visual C使用#pragma once来通知编译器在生成时只包含打 开一次也就是说在第1次 #include之后编译器重新生成时不会再对 这些包含文件进行包含打开和读取为此在用向导创建的所有类的头文 件中都有 #pragma once这样的语句。然而正是由于这个语句而造成了在第2 次 #include后编译器无法正确识别所引用的类从而发生错误。解决的办法 是在相互包含时加入类的声明来通知编译器这个类是一个实际的调用如下 一步操作。 6class CMyDoc;//声明CMyDoc类需要再次使用 5、添加CDrawView类代码 1CPoint m_ptDraw; 2CRect rc(m_ptDraw.x-5,m_ptDraw.y-5,m_ptDraw.x5,m_ptDraw.y5); pDC-Rectangle(rc);//绘制矩形以后还会详细讨论 3CMyDoc *pDoc(CMyDoc *)m_pDocument; m_ptDrawpDoc-m_ptRect; 4#include 汉字文件名Doc.h 5if(lHint2) { CPoint *pPoint(CPoint *)pHint; m_ptDraw*pPoint; Invalidate(); } 6m_ptDrawpoint; GetDocument()-UpdateAllViews(NULL,1,(CObject*)m_ptDraw); Invalidate(); 7编译运行并测试结果见295页图6.24 程序要点 A、几个视图之间的数据传输是通过CDocument::UpdateAllView和CView::OnUpdate 的相互作用来实现的而且为了避免传输的相互干涉采用提示标识值lHint 来区分。例如当在CDrawView中鼠标的坐标数据经文档类调用UpdateAllView 函数传递提示标识值为1在CMyView类接收数据时通过提示标识值来判 断如第5步中6GetDocument()-UpdateAllViews(NULL,1,(CObject*)m_ptDraw); //传输数据。第2步中4if(lHint1) 再如当CMyView类中的编辑框控件数据改变之后经文档类调用UpdateAllViews函数传递提示标识值为2在CDrawView类接收数据时通过OnUpdate函数判断提示标识值来决定接收数据。 B、 为了能及时更新并保存文档数据相应的数据成员应在用户文档类中定义。这 样由于所有的视图类都可与文档类进行交互因而可以共享这些数据。 附学生档案管理程序 一、首先再介绍一下文档与视图的概念 1、文档 文档类的基类是CDocument类它描述了应用的数据。抽象地说文档是一个应用程序数据基本元素的集合它构成应用程序所使用的数据单元此外文档负责管理和维护应用的数据。具体说文档是一种数据源数据源有很多种最常见的是磁盘文件但是文档不必非要是一个磁盘文件文档的数据源也可以来自串行口或并行口的输入数据。文档对象负责管理来自所有数据源的数据。 2、视图 视图类则继承于视类CView它是一个基于视类的窗口。视图是数据的用户窗口为用户提供了文档的可视的数据显示它把文档的部分或全部内容在窗口中显示出来视图给用户提供了一个同文件中的数据进行交互的界面它把用户的输入转化为对文档中数据的操作。 3、文档与视图的关系 每个文档都会有一个或多个视图显示一个文档可以有多个不同的视图。比如可以将一个集合关系以饼状图的形式显示也可以将它以数据的形式来显示。总之要把握一点文档用来保存数据视图用来显示数据视图是显示出的文档。 4、文档与视图的交互过程 简单的说交互过程中是以下4个函数起了关键性的作用。 1CView类的GetDocument()函数 一个视图对象只能与一个文档对象相联系。视图类CView包含的GetDocument()函数使用户可以在视图类中得到与当前视图相联系的文档。该函数返回的是一个CDocument类或其派生类的指针。从而可以利用得到的文档指针来访问文档中的数据。例如 CMyDoc *pDocGetDocument(); ASSERT_VALID(pDoc); 2CDocument类的UpdateAllViews()函数 上面说的是当视图收到用户数据变化的消息后会通知文档当前的变化。同样当文档的数据发生了变化后文档也要通知视图当前的变化以便让视图能够及时更新忠实反映文档的数据。这样的一件工作是通过文档类中的UpdateAllViews()函数来实现的。 3CView类的OnUpdate()函数 当文档类调用UpdateAllViews()函数来通知更新视图时OnUpdate()函数便会被调用。该函数是一个虚函数当该函数被调用时它会对文档类进行访问读取文档的数据然后对视图进行刷新。它的原理是该过程使视图的某一个部分无效触发了对视图类成员函数OnDraw()的调用从而重新绘制视图客户区。默认情况下OnUpdate()函数是使整个客户区都无效。用户可以重载该函数以使视图能够反映文档的最新更新情况。 4CView类的OnInitialUpdate()函数 该函数原型为virtual void OnInitialUpdate(); 当应用程序被启动或者用户选择了打开文件或新建文件时OnInitialUpdate()函数都会被调用。CView类中的该函数只是调用了OnUpdate()函数并没有做其它方面的事用户要对派生类的OnInitialUpdate()函数进行初始化则可以调用基类的该函数也可以直接调用派生类的OnUpdate()函数。 学生电子档案管理系统实现的具体界面如下 图1学生电子档案管理系统实现界面 图2进入系统界面 图3用弹出式对话框输入班级学生的姓名 图4数据库有改动时的退出界面 二、学生档案管理程序的编写 1、建一个单文档应用程序名为《学生档案管理》。第6步选取CFormView可以在主窗口内添加控件作为视图的基类。 2、可视化设计 根据以下的定义编辑对话框资源设计的对话框如下图所示 下面是设计完后的对话框界面布局图 烟台南山学院Edit Box 级Edit Box专业Edit Box 斑 学生电 信息工程学院 子档案 按输入的姓名 学生电子档案 照片 登记学生信息 Edit Box Combo Box 组合框 单击此处输入学生姓名 BUTTON1 对应 姓名 Edit Box 学号 Edit Box 性别 Edit Box 出生 年月 Edit Box 电话 Edit Box 职务 Edit Box 按如下约定添加控件 对象 属性 属性值 Edit Box ID IDC_EDIT_CLAS //Static 级 Caption Edit Box ID IDC_EDIT_PRO //Static 专业 Caption Edit Box ID IDC_EDIT_CLA //Static 班及学习性质 Button1 ID IDC_BUTTON1 //Static 单击此处输入学生姓名 Caption Static Text ID IDC_STATIC Caption 对应姓名 Edit Box ID IDC_EDIT_NAME//姓名 Caption Static Text ID IDC_STATIC Caption 学号 Edit Box ID IDC_EDIT_STUID//学号 Caption Static Text ID IDC_STATIC Caption 姓别 Edit Box ID IDC_EDIT_SEX//性别 Caption Static Text ID IDC_STATIC Caption 出生年月 Edit Box ID IDC_EDIT_AGE//出生年月 Caption Static Text ID IDC_STATIC Caption 电话 Edit Box ID IDC_EDIT_TEL//电话 Caption Static Text ID IDC_STATIC Caption 职务 Edit Box ID IDC_EDIT_POS//职务 Caption Static Text ID IDC_STATIC Caption 按输入的姓名 Static Text ID IDC_STATIC Caption 登记学生信息 Combo Box ID IDC_COMBO Type Dropdown Sort Checked(stylesw)//选上 VerticalScroll Checked(styles) //选上 Static Text ID IDC_STATIC Caption 学生电子档案 Edit Box ID IDC_EDIT_SCHOOL Multiline Checked //选上 Horizontal Checked //选上 Vertical scroll Checked //选上 3、给文档类添加成员变量 文档类中的数据就是要操作的对象所以必须将其定义为文档类的数据成员。 1修改CMyDoc类的定义 在CMyDoc.h中public:下定义一个结构体 public: struct { char clas[10]; //级 char pro[20]; //专业 char cla[20]; //班及学习性质 char name[20]; //对应姓名 char stuid[20]; //学号 char sex[10]; //性别 char age[20]; //出生年月 char tel[20]; //电话 char pos[20]; //职务 char school[9999];//学生电子档案最多写9999个字符 }m_student[80]; //一个班最多80个学生 2初始化变量 在CMyDoc.cpp的构造函数中对这些变量进行初始化 for(int i0;i80;i) { m_student[i].clas[0]NULL;//级 m_student[i].pro[0]NULL;//专业 m_student[i].cla[0]NULL;//班及学习性质 m_student[i].name[0]NULL; //对应姓名 m_student[i].stuid[0]NULL;//学号 m_student[i].sex[0]NULL;//性别 m_student[i].age[0]NULL;//出生年月 m_student[i].tel[0]NULL;//电话 m_student[i].pos[0]NULL;//职务 m_student[i].school[0]NULL;//学生电子档案 } 4、给视图类添加成员变量和记录姓名的变量 视图类是负责屏幕的显示内容。在此要加入的视图类数据成员实际上就是文档类的数据成员在屏幕上的映射。而将视图类中的变量显示在屏幕上最简捷的方法是将变量与某编辑控件关联即为编辑框引入变量。 1为IDD_MY_FORM对话框中的各个控件引入变量view类中 利用MFC ClassWizard按下面定义为控件引入变量 控件ID 类型 关联变量 说明 IDC_EDIT_CLAS CString m_clas 级 IDC_EDIT_PRO CString m_pro 专业 IDC_EDIT_CLA CStrint m_cla 班及学习性质 IDC_EDIT_NAME CString m_name 对应姓名 IDC_EDIT_STUID CString m_stuid 学号 IDC_EDIT_SEX CString m_sex 性别 IDC_EDIT_AGE CString m_age 出生年月 IDC _EDIT_TEL CString m_tel 电话号码 IDC_EDIT_POS CString m_pos 职务 IDC_COMBO CComboBox m_noList按输入的姓名登记学生信息组合框 IDC_EDIT_SCHOOL CString m_school 学生电子档案 操作如下 ViewàClassWizardàMember Variables(加成员变量)àCMyView(在视图里)à点黑IDC_COMBOàAdd Variable…à写成员变量名m_noListà注意类型用Category:下拉成control变为CComboBox类型其它变量的类型可下拉Variable Type改变成int或CString类型等 按如上步骤完成上面各控件的变量引入。 2添加一个记录姓名的变量 在CMyView.h里 class CMyView : public CFormView { int m_nCurrentNo; //这条是加的记录姓名的变量在私有模式下。 protected: // create from serialization only CMyView(); DECLARE_DYNCREATE(CMyView) ……….. } 5、变量初始化 1修改OnInitialUpdate()函数,做2个工作 1设置Combo Box”姓名” 2将文档类中变量赋给对应的编辑控件变量在View.cpp里 void CMyView::OnInitialUpdate() { CFormView::OnInitialUpdate(); GetParentFrame()-RecalcLayout(); ResizeParentToFit(); CMy1Doc *pDoc GetDocument(); m_noList.ResetContent();//删除组合框的全部项和编辑文本 for (int i0; i80; i) { if(strcmp(pDoc-m_student[i].name, ))//如果对应姓名空 //向组合框尾部添加对应姓名字符串(nIndex-1) m_noList.InsertString(-1, pDoc-m_student[i].name); //将对应的姓名pDoc-m_student[i].name添到组合框末尾 } m_noList.SetCurSel(0);//设置当前选择项(参数为当前选择项索引) m_clas pDoc-m_student[0].clas;//级 m_pro pDoc-m_student[0].pro; //专业 m_cla pDoc-m_student[0].cla; //班及学习性质 m_name pDoc-m_student[0].name;//对应姓名 m_stuid pDoc-m_student[0].stuid;//学号 m_sex pDoc-m_student[0].sex;//性别 m_age pDoc-m_student[0].age;//出生年月 m_tel pDoc-m_student[0].tel;//电话 m_pos pDoc-m_student[0].pos;//职务 m_school pDoc-m_student[0].school;//学生电子档案 UpdateData(false); } 2在构造函数(View.cpp里)对变量m_nCurrentNo(记录学号的变量)初始化 CMyView::CMyView() : CFormView(CMyView::IDD) { //{{AFX_DATA_INIT(CMyView) m_clas _T(); m_pro _T(); m_cla _T(); m_name _T(); m_stuid _T(); m_sex _T(); m_tel _T(); m_pos _T(); m_age _T(); m_school _T(); //}}AFX_DATA_INIT // TODO: add construction code here m_nCurrentNo0; //在这加的 } 6、用另一个对话框输入全班学生的姓名见图3 1插入一个对话框 点击工作区ResourceViewà右键单击DialogàInsert Dialogà见已插入一个对话框ID为缺省IDD_DIALOG1将OK改为“确定”将CANCEL改为“取消” 拖一个静态文本static写“请逐个输入学生姓名”拖一个编辑框Edit BoxID为IDC_EDIT_NUM 2为这个对话框建类 双击这个对话框àOKà类名写CinDlogàOK退出 3用ViewàClassWizard为这个对话框上的编辑框IDC_EDIT_NUM建成员变量m_num 类型为CString 7、弹出对话框并完成从对话框的编辑框向组框加入姓名的工作 1将BUTTON1映射消息加入View.cpp中并将对话框类inDlog包含在View 中。 2加入下列代码 void CMy1View::OnButton1() { // TODO: Add your control notification handler code here UpdateData(true); //将姓名插入到组合框 CinDlog dlg; //dlg.DoModal(); dlg.m_num.TrimLeft(); dlg.m_num.TrimRight(); if(IDOKdlg.DoModal()) { if (dlg.m_num.IsEmpty()) { MessageBox(姓名不能为空!!); return; } if ((m_noList.FindStringExact(-1, dlg.m_num))!LB_ERR) { MessageBox(列表中已有相同的姓名,不能添加!!); return; } } m_noList.InsertString(-1, dlg.m_num); //向组合框尾部添加字符串 //设置当前选择项获取组合框的项数 m_noList.SetCurSel(m_noList.GetCount()-1); m_nCurrentNo m_noList.GetCurSel();//获得当前选择项的索引 //m_clas” “;//级 这三个框不充零一个班所有学生只写一次即可 //m_pro” “//专业 //m_cla” “;//学习性质与班级 m_name ;//姓名 m_stuid” “;//学号 m_sex ;//性别 m_age ;//出生年月 m_tel ;//电话 m_pos ;//职务 m_school ;//学生电子档案 UpdateData(false); } 8、处理数据记录的录入见图4 实现数据记录的编辑就是处理编辑框的EN_CHANGE见第5章最后部分说明事件。为此要为编辑框的EN_CHANGE事件添加响应函数且要在消息响应函数中编写将编辑的数据传给文档类对应的变量代码。 1添加消息响应函数的操作步骤 ViewàClassWizardàMessage MapsàCMyView(在视图类)àIDC_EDIT_NAME àEN_CHANGEàAdd Function…àOK函数加在视图类文件末尾 按如上步骤将以下各消息响应函数添加到View.cpp中 控件ID 消息事件 函数 说明 IDC_EDIT_CLAS EN_CHANGE OnChangeEditClas 编辑与显示级 IDC_EDIT_PRO EN_CHANGE OnChangeEditPro 编辑与显示专业 IDC_EDIT_CLA EN_CHANGE OnChangeEditCla编辑与显示班及学习性质 IDC_EDIT_NAME EN_CHANGE OnChangeEditName编辑与显示对应姓名 IDC_EDIT_STUID EN_CHANGE OnChangeEditStuid 编辑与显示学号 IDC_EDIT_SEX EN_CHANGE OnChangeEditSex 编辑与显示性别 IDC_EDIT_AGE EN_CHANGE OnChangeEditAge 编辑显示出生年月 IDC_EDIT_TEL EN_CHANGE OnChangeEditTel 编辑与显示电话号码 IDC_EDIT_POS EN_CHANGE OnChangeEditPos 编辑与显示职务 IDC_COMBO CBN_SELCHANGE OnSelchangeCombo 选择姓名的组合框 IDC_EDIT_SCHOOL EN_CHANGE OnChangeEditSchool 编辑与显示学生的电子档案 2为函数OnChangeEditName()加代码 void CMyView::OnChangeEditName() //对应姓名 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_name的值与其关联的对应姓名编辑框一致 //下面的if语句是当对应的变量值有所改变时进行值的传递 //这样保证文档类与视图类相对应的变量值时刻保持一致。同时设置 //数据修改标志当用户退出程序时程序会自动提示是否将数据保存 if(strcmp(pDoc-m_student[m_nCurrentNo].name,m_name)) { strcpy(pDoc-m_student[m_nCurrentNo].name,m_name); pDoc-SetModifiedFlag(); } // TODO: Add your control notification handler code here } 3为OnChangeEditStuid()函数添代码 void CMyView::OnChangeEditStuid() //学号 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_stuid的值与其关联的学号编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].stuid,m_stuid)) { strcpy(pDoc-m_student[m_nCurrentNo].stuid,m_stuid); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 4为OnChangeEditClas()函数添代码//级 void CMyView::OnChangeEditClas() //级 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_clas的值与其关联的级编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].clas,m_clas)) { strcpy(pDoc-m_student[m_nCurrentNo].clas,m_clas); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 5为OnChargeEditPro()函数添代码 void CMyView::OnChangeEditPro() //专业 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_pro的值与其关联的专业编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].pro,m_pro)) { strcpy(pDoc-m_student[m_nCurrentNo].pro,m_pro); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 6为OnChangeEditCla()函数添代码 void CMyView::OnChangeEditCla()//班及学习性质 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_cla的值与其关联的班及学习性质编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].cla,m_cla)) { strcpy(pDoc-m_student[m_nCurrentNo].cla,m_cla); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 7为OnChangeEditAge()函数添代码 void CMyView::OnChangeEditAge() //出生年月 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_age的值与其关联的出生年月编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].age,m_age)) { strcpy(pDoc-m_student[m_nCurrentNo].age,m_age); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 8为OnChangeEditSchool()函数添代码 void CMyView::OnChangeEditSchool() //编辑与显示学生电子档案 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_school的值与其关联的学生情况编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].school,m_school)) { strcpy(pDoc-m_student[m_nCurrentNo].school,m_school); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 9为OnChangeEditSex()函数添代码 void CMyView::OnChangeEditSex() //性别 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_sex的值与其关联的性别编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].sex,m_sex)) { strcpy(pDoc-m_student[m_nCurrentNo].sex,m_sex); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 10为OnChangeEditTel()函数添代码 void CMyView::OnChangeEditTel() //电话 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_tel的值与其关联的电话编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].tel,m_tel)) { strcpy(pDoc-m_student[m_nCurrentNo].tel,m_tel); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 11为OnChangeEditPos()函数加代码 void CMyView::OnChangeEditPos() //职务 { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CFormView::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true);//用控件的值去更新与之关联的变量从而使 //m_pos的值与其关联的职务编辑框一致 if(strcmp(pDoc-m_student[m_nCurrentNo].pos,m_pos)) { strcpy(pDoc-m_student[m_nCurrentNo].pos,m_pos); pDoc-SetModifiedFlag();//设置数据修改标志 } // TODO: Add your control notification handler code here } 12为OnSelchangeCombo()函数加代码 void CMyView::OnSelchangeCombo() //选择学号的组合框 { // TODO: Add your control notification handler code here CMyDoc *pDoc GetDocument();//获取指向文档类对象的指针用于 //操作文档类中的数据。 UpdateData(true); CinDlog dlg; m_nCurrentNo m_noList.GetCurSel();//获得当前选择项的索引 int im_noList.GetCurSel(); //m_clas pDoc-m_student[m_nCurrentNo].clas;//级 这三条和上面一样 //m_pro pDoc-m_student[m_nCurrentNo].pro;//专业 注释掉使一个班 //m_cla pDoc-m_student[m_nCurrentNo].cla;//班及学习性质 只输一次 dlg.m_num pDoc-m_student[m_nCurrentNo].name; //文档的姓名赋给对话框//上的编辑框 m_name pDoc-m_student[m_nCurrentNo].name;// 文档姓名赋给视图成员变量 m_stuid pDoc-m_student[m_nCurrentNo].stuid;// 文档学号 m_sex pDoc-m_student[m_nCurrentNo].sex;// 文档性别 m_age pDoc-m_student[m_nCurrentNo].age;// 文档出生年月 m_tel pDoc-m_student[m_nCurrentNo].tel;// 文档电话 m_pos pDoc-m_student[m_nCurrentNo].pos;// 文档职务 m_schoolpDoc-m_student[m_nCurrentNo].school;// 文档学生电子档 // 到这要在视图类View.h的public里加一位图类变量CBitmap m_bitmap; // 见上面5、CFormView类应用”显示背景图”来显示照片。 // 下面是添加照片照片为.bmp类型大小为166*149用photoshop处理好 if(i0) { CDC *pDCGetDC(); HBITMAP hBitmap(HBITMAP)LoadImage(NULL,_T(X06jyz400.bmp), IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE| LR_LOADFROMFILE); m_bitmap.Attach(hBitmap); BITMAP bm; m_bitmap.GetBitmap(bm);//注意此处好出错与你加的图形有关。 CDC dcImage; if(!dcImage.CreateCompatibleDC(pDC)) return; CBitmap *pOldBitmapdcImage.SelectObject(m_bitmap); pDC-BitBlt(0,0,bm.bmWidth,bm.bmHeight,dcImage,0,0,SRCCOPY); //调用BitBlt()函数显示照片 dcImage.SelectObject(pOldBitmap); DeleteObject(m_bitmap.Detach());//释放位图存储空间 } if(i1) { CDC *pDCGetDC(); HBITMAP hBitmap(HBITMAP)LoadImage(NULL,_T(X06jyz401.bmp), IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE| LR_LOADFROMFILE); m_bitmap.Attach(hBitmap); BITMAP bm; m_bitmap.GetBitmap(bm);//注意此处好出错与你加的图形有关。 CDC dcImage; if(!dcImage.CreateCompatibleDC(pDC)) return; CBitmap *pOldBitmapdcImage.SelectObject(m_bitmap); pDC-BitBlt(0,0,bm.bmWidth,bm.bmHeight,dcImage,0,0,SRCCOPY); //调用BitBlt()函数显示照片 dcImage.SelectObject(pOldBitmap); DeleteObject(m_bitmap.Detach());//释放位图存储空间 } if(i2) { CDC *pDCGetDC(); HBITMAP hBitmap(HBITMAP)LoadImage(NULL,_T(X06jyz402.bmp), IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE| LR_LOADFROMFILE); m_bitmap.Attach(hBitmap); BITMAP bm; m_bitmap.GetBitmap(bm);//注意此处好出错与你加的图形有关。 CDC dcImage; if(!dcImage.CreateCompatibleDC(pDC)) return; CBitmap *pOldBitmapdcImage.SelectObject(m_bitmap); pDC-BitBlt(0,0,bm.bmWidth,bm.bmHeight,dcImage,0,0,SRCCOPY); //调用BitBlt()函数显示照片 dcImage.SelectObject(pOldBitmap); DeleteObject(m_bitmap.Detach());//释放位图存储空间 } if(i3) { } UpdateData(false); } 注意下面的13、14可以不做 13在工具栏中添加“打开”和“另存为”按钮 学生档案写完后要保存而用到时要打开。故要设计“打开”和“保存”控件。具体作法如下 1在Workspace窗口的资源ResourceView列表中双击“Toolbar”下的“ID_MAINFRAME”项打开工具拦编辑器单击最后一个空白按钮画上适当图形再双击这个按钮ID处写ID_FILE_MYOPEN下面:Prompt处写 打开\n打开。(“\n”之前是程序运行时状态拦的提示信息“ \n “之后为鼠标光标移动至该按钮时光标下方给出的提示信息) 2按上步骤再加一个保存按钮ID_FILE_MYSAVEPrompt处写保存\n保存 14在CMyView类中添加消息响应函数 1添加“打开”消息响应函数 ViewàClassWizardàMessage MapsàClass name:CMyViewàObject IDs: ID_FILE_MYOPENàMessage:COMMANDàAdd FunctionàEdit Code开始加代码 void CMyView::OnFileMyopen() { // TODO: Add your command handler code here CString strFilterAll Files(*.*)|*.*|Dat File(*.dat)|*.dat||;//打开文件对话框 CFileDialog FileDlg(true,NULL,NULL,OFN_HIDEREADONLY| OFN_OVERWRITEPROMPT,(LPCSTR)strFilter,this); if(FileDlg.DoModal()!IDOK) return; CString strFileNameFileDlg.GetPathName(); CFile f; if(!f.Open(strFileName,CFile::modeRead)) { AfxMessageBox(打开文件失败); return; } CMyDoc *pDocGetDocument();//读出文件中的数据存放 //到文档类的数据成员中 f.Read(m_nCurrentNo,sizeof(int)); for(int i0;i80;i) { f.Read(pDoc-m_student[i].clas,10);//级 f.Read(pDoc-m_student[i].pro,20);//专业 f.Read(pDoc-m_student[i].cla,10);//班 f.Read(pDoc-m_student[i].ins,20);//对应学号 f.Read(pDoc-m_student[i].name,10); f.Read(pDoc-m_student[i].sex,3); f.Read(pDoc-m_student[i].age,4); f.Read(pDoc-m_student[i].tel,14); f.Read(pDoc-m_student[i].pos,20);//职务 f.Read(pDoc-m_student[i].school,999);//学生电子档案 } f.Close(); //将文档类的数据传递给视图类数据成员并显示 m_noList.SetCurSel(m_nCurrentNo);//设置当前选择项 m_claspDoc-m_student[m_nCurrentNo].clas;//级 m_propDoc-m_student[m_nCurrentNo].pro;//专业 m_clapDoc-m_student[m_nCurrentNo].cla;//班 m_inspDoc-m_student[m_nCurrentNo].ins;//对应学号 m_namepDoc-m_student[m_nCurrentNo].name; m_sexpDoc-m_student[m_nCurrentNo].sex; m_agepDoc-m_student[m_nCurrentNo].age; m_telpDoc-m_student[m_nCurrentNo].tel; m_pospDoc-m_student[m_nCurrentNo].pos; //职务 m_schoolpDoc-m_student[m_nCurrentNo].school;//学生电子档案 UpdateData(false); } 2添加“保存”消息响应函数按如上步骤将“保存”函数加到View.cpp里并加代码 void CMyView::OnFileMysave() { // TODO: Add your command handler code here CString strFilterAll File(*.*)|*.*|Dat File(*.dat)|*.dat||; CFileDialog FileDlg(false,NULL,NULL,OFN_HIDEREADONLY| OFN_OVERWRITEPROMPT,(LPCSTR)strFilter,this); if(FileDlg.DoModal()!IDOK) return; CString strFileNameFileDlg.GetPathName(); CFile f; if(!f.Open(strFileName,CFile::modeCreate|CFile::modeWrite)) { AfxMessageBox(创建文件失败); return; } CMyDoc *pDocGetDocument(); f.Write(m_nCurrentNo,sizeof(int)); for(int i0;i80;i) { f.Write(pDoc-m_student[i].clas,10);//级 f.Write(pDoc-m_student[i].pro,20);//专业 f.Write(pDoc-m_student[i].cla,10);//班 f.Write(pDoc-m_student[i].ins,20);//对应学号 f.Write(pDoc-m_student[i].name,10); f.Write(pDoc-m_student[i].sex,10); f.Write(pDoc-m_student[i].age,10); f.Write(pDoc-m_student[i].tel,20); f.Write(pDoc-m_student[i].pos,20);//职务 f.Write(pDoc-m_student[i].school,9999);//学生电子档案 } f.Close(); } 15添加串行化存储和装入 程序运行后你按学号写入一斑学生的信息并可按个浏览。但这样退出程序后一斑学生的信息就没了为了将学生的信息保留下来并在用到时还能打开你要进行串行化存储和装入在CMyDoc.cpp文件的Serialize(CArchive ar)函数里加 void CMyDoc::Serialize(CArchive ar) { if (ar.IsStoring()) { for(int i0;i80;i) { ar.Write(m_student[i].clas,10);//级 ar.Write(m_student[i].pro,20);//专业 ar.Write(m_student[i].cla,20);//班及学习性质 ar.Write(m_student[i].name,20);//对应姓名 ar.Write(m_student[i].stuid,20);//学号 ar.Write(m_student[i].sex,10);//性别 ar.Write(m_student[i].age,20);//出生年月 ar.Write(m_student[i].tel,20);//电话 ar.Write(m_student[i].pos,20);//职务 ar.Write(m_student[i].school,9999);//学生电子档案 } // TODO: add storing code here } else { for(int i0;i80;i) { ar.Read(m_student[i].clas,10);//级 ar.Read(m_student[i].pro,20);//专业 ar.Read(m_student[i].cla,20);//班及学习性质 ar.Read(m_student[i].name,20);//对应姓名 ar.Read(m_student[i].stuid,20);//学号 ar.Read(m_student[i].sex,10);//性别 ar.Read(m_student[i].age,20);//出生年月 ar.Read(m_student[i].tel,20);//电话 ar.Read(m_student[i].pos,20);//职务 ar.Read(m_student[i].school,9999);//学生电子档案 } // TODO: add loading code here } } 程序正确运行后你往里敲学生信息敲完后à保存起一个文件名”一斑学生信息”接着可浏览à退出。再运行此程序时只见是空的信息你要à文件à打开à”一斑学生信息”便显示出一斑的学生信息了接着你还可以进行添加修改等工作。 如果你再建二班学生信息你可在再运行此程序时直接敲进二班学生信息之后再起名”二班学生信息”,其它如此类推。 各个班都建好后你进入e盘的vcpp文件夹找到这个程序点开在Debug里把带图形的那个执行文件拷出来它就可当项目执行了。 注意要为本软件设置密码口令请按第五章“口令设置实例”进行见图2 组合框介绍 本例使用了组合框来存储与显示学号。组合框可以看作是一个编辑框或静态文本框与一个列表框的组合组合框的名称也正是由此而来的。当前选定的项将显示在组合框的编辑框或静态文本框中如果组合框具有下拉列表drop-down list样式则用户可以在编辑框中键入列表框中某一项的首字母在列表框可见时与该字母相匹配的最近的项将被加亮显示在绘制组合框时可以使用控件的”Properties”对话框设置控件的各种属性式样。如 选项 风格 Simple 创建包括编辑框控件和列表框架的简单组合框其中编辑框控件用来接受用户的输入 Dropdown 创建下拉组合框该类型与简单组合框类似但仅当用户单击编辑框控件部分右边的下拉箭头时组合框的列表框部分才被显示。 Drop List 该类型类似于下拉样式只是使用静态文本项代替编辑框控件来显示列表框中的当前选项。 Combo Box控件与CComboBox类关联。CComboBox类常见的成员函数如下 函数 说明 GetCount 获得组合框中列表框项的数目 GetCurSet 返回组合框中列表框的当前选定项的索引 SetCurSel 设置组合框中列表框的一个字符串 DeleteString 从组合框的列表框中删除一个字符串 InsertString 向组合框的列表中插入一个字符串 AddString 向组合框的列表中添加一个字符串 SelectString 在组合框的列表框中查找字符串如果找到在列表中选择该字符串并复制到编辑框控件中。 CArchive类 CArchive类没有基类它提供了串行化对象从文件中读写的类型安全缓冲机制可以把CArchive对象想象成一种二进制流就象输入/输出流一样可以顺序高效的处理二进制对象数据。使用CArchive对象之前必须先创建一个CFile对象同时保证CArchive对象的读写标志设置和文件打开方式相一致。对于一个CArchive对象可以进行存储操作也可以读取但不能两者同时进行。 CArchive类的成员函数 Read、Write 读写指定字节数的缓冲区内容 ReadString、WriteString 读写一行文本 ReadObject、WriteObject 调用一个对象的Serialize函数来读或写 ReadClass、WriteClass 读写一个CRuntimeClass指明对象 IsLoading、IsStoring 判断当前读写状态 Serialize()函数 在“MFC AppWizard”自动生成的程序框架中文件的串行化操作操作都是由CDocument派生类的成员函数Serialize()完成。 在这个成员函数中使用CArchive对象完成具体的操作。参数中传递进来的CArchive引用对象是由MFC程序框架根据用户输入需要执行串行化操作时创建的它包含了所需要的文件的信息使用它可以对各种CArchive类支持的数据格式进行读写、调用其他CObject派生类对象的串行化函数等。 “MFC AppWizard”自动生成的代码已经完成的工作是用户选择“打开”、“保存”或“另存为”等命令时程序框架创建着这个文件的CFile对象将它关联到新创建的CArchive对象上并设置CArchive对象的“Store”或“Load”标志用这个对象来调用CDocument派生类的Serialize()成员函数。在Serialize()函数完成读写操作返回后自动删除Serialize()函数和CFile对象。
http://www.hkea.cn/news/14347257/

相关文章:

  • 网站建设分为几个阶段湛江cms建站
  • 普集网站制作网易企业邮箱收费吗
  • 网站快备asp网站制作
  • 做网站的工作叫什么做英文网站 是每个单词首字母大写 还是每段落首字母大写
  • 网站建设平台安全问题有哪些方面网站会员充值做哪个分录
  • 温州哪里有做网站榆树市住房和城乡建设局网站
  • 网站首页跳出弹窗苏州相城区最新楼盘价格
  • 网站开发的大学生应届简历seo算法
  • 深圳建站公司模板高平企业网站
  • 培训教育网站建设做网站后期维护工资贴吧
  • pc网站转换手机网站代码网页升级访问紧急通通知
  • 在哪里做网站设计英文电商网站建设
  • 网站宣传有文化事业建设费吗网站开发一般过程
  • 广州app网站建设政务网站建设原则
  • 手机网站建设基本流程成都微网站开发
  • 提供常州网站推广网站建设与管理实践报告总结
  • 做exo小说的网站百度搜索广告怎么投放
  • 网站 网页区别是什么iis启动wordpress
  • 万网网站备案查询网站页面设计需要遵循的六大原则
  • 莆田市秀屿区建设局网站背景做网站公司
  • 做一个展示型网站多少钱无锡微信网站建设价格
  • 昆山做企业网站免费设计素材下载
  • 做网站选择哪家运营商装修公司网站模板
  • 北京怎么做网站推广wordpress顶部加横幅
  • 网站建设模板型和定制型网站底部信息
  • 湛江网站制作优化凡客诚品还有人买吗
  • 做网站官网好处怎么在中国做网站网站
  • 网站建设需求分析调研表电子商务以后的就业方向
  • 网站开发周期是什么意思西安哪个公司做网站
  • 推网站wordpress 自适应主题