需要用到文本编辑功能,当然是用现成的啦,自己写字符输入那还挺麻烦的。。。比如前面就没解决的问题,后续还有什么只能一行一行写,不能随意跳动行啥的,一系列问题,还有有现成的,不然真的为难死我了。。。菜鸡难过
MFC提供了两个类吧,一个CEditView,一个CRichEditView,虽然我只用了后面那个 ,前面那个没做什么了解。。。。后面这个虽然好用,问题也多。。。。。真的搞人耶,
新建项目:单文档->基类继承CRichEditView ★★★★这里千万注意,使用CRichEditView和RichEdit控件需要注意:MFC中调用DoModal()后窗体未弹出
其实你可以去.App类中去看,找到CTxt**App::InitInstance()函数,它也提示该怎么使用; 只不过这行代码需要插入到CWinAppEx::InitInstance();上面,好像之前插错位置没正常显示,如下图:
现在运行这个程序,是可以直接在界面上进行文本输入的(实际上你还可以粘贴图片。。。不然为什么叫富文本,理论上我们只要写文本,应该是用CEditView的,不过用都用了,改是不可能改的。
。。。发现编辑菜单下面的子菜单,比如什么剪贴复制粘贴都是可以直接用的,这可真是太贴心了,爱了爱了,目前主要就是实现文件菜单下的几个子菜单的功能,新建/打开/保存/另存为
。。。。这几个功能实际上也已经给你实现了一半了,比如点击保存它会给你弹出弹窗让你选择保存地址。。但是它又没有做完,毕竟他无法真的去保存。。。总而言之,自带的功能不够用,需要重写函数自己解决问题,不然要你👨💻干啥
1.读写文件:
第一步当然是先搞定文件的读写,毕竟这些功能都需要建立在这个基础之上,不先写读写怎么验证你写的功能。。。。分别写一个函数用于保存文本至Txt,从Txt读取文本到成员变量(这里因为这个文本一直需要使用修改,所以让其作为一个成员变量),此处需要两个CString变量,一个保存文本,一个保存文件路径,下面这些都写在CTxt**View.h中:
1 CString TxtFileString; // 存储文本文件的字符内容 2 CString TxtFileLoad; // 该文本的保存路径 3 4 bool SaveFileContents(); // 保存文件内容,存内容到指定的Txt文本 5 bool ReadFileContents(); // 读取文件内容,获取指定Txt文本内容
CTxt**View.cpp:具体实现读写函数,采用CFile类实现文件读写,
这里为了方便处理字符编码格式,固定了读写方式,均采用UTF-16-LE编码格式,即写入时,加入文件头部标志,用以表示这是一个UTF-16-LE编码的Txt文本,同样,读取的时候也可以通过检验这个标志来判断是否是UTF-16-LE编码的Txt文本
另外就是关于字符长度,以及字符的字节长度的处理需要搞清楚,往往会因为这个导致一些错误。UTF-16一个字符占两个字节(汉字/英文都是两个字节),在这里使用的CString也是宽字节,也是一个字符占两个字节,这里区别于普通的C/C++程序的字符表示方法(char,string),还是需要谨慎注意!
1 /* 2 function:保存文件内容,存内容到指定的Txt文本 3 1.捕捉异常, 4 2.返回值fasle,表明存在异常,错误;返回值true,表明程序正常处理,保存成功 5 TxtFileString; // 存储文本文件的字符内容 6 TxtFileLoad; // 该文本的保存路径 7 */ 8 bool CTxt0721View::SaveFileContents() 9 { 10 CFile save_file; 11 try 12 { 13 // 打开文件,第一参数文件路径,以写入模式打开,如果不存在则创建 14 save_file.Open(TxtFileLoad, CFile::modeWrite | CFile::modeCreate); 15 BYTE byUnicode[] = { 0xFF, 0xFE }; 16 // 写入文件,第一个参数是写入内容,第二个参数是写入内容的长度 17 save_file.Write(byUnicode, sizeof(byUnicode)); // 文件开头写入byUnicode告知为Unicode编码,此后便可正常输入 18 save_file.Write((LPCTSTR)TxtFileString, wcslen((LPCTSTR)TxtFileString) * sizeof(WCHAR)); 19 // 关闭文件 20 save_file.Close(); 21 // 写入文件成功 22 return true; 23 } 24 catch(CFileException *e) { 25 CString save_errror_str; 26 save_errror_str.Format(L"保存数据失败的原因是:%d", e->m_cause); 27 MessageBox(save_errror_str); 28 save_file.Abort(); 29 e->Delete(); 30 // 写入文件失败 31 return false; 32 } 33 }
1 /* 2 function:读取文件内容,获取指定Txt文本内容 3 1.捕捉异常, 4 2.返回值fasle,表明存在异常,错误;返回值true,表明程序正常处理,读取Txt成功 5 3.可以使用read一次全部读取,考虑会爆,循环多次读取(暂未处理) 6 TxtFileString; // 存储文本文件的字符内容 7 TxtFileLoad; // 该文本的保存路径 8 */ 9 bool CTxt0721View::ReadFileContents() 10 { 11 CFile read_file; 12 try 13 { 14 // 只读模式打开文档 15 read_file.Open(TxtFileLoad, CFile::modeRead); 16 // 获取整个文件的字节长度--由于是UTF-16编码,一个字符占两个字节,文件字节长度是字符长的两倍--逻辑长度 17 DWORD file_length = read_file.GetLength(); 18 // 文件指针定位,干掉前面两个字节--用于标记UTF-16的标记 19 read_file.Seek(2, CFile::begin); // Skip 0xFF, 0xFE 20 // 创建字符串存储内容 21 wchar_t * file_contents = new wchar_t[file_length]; 22 memset(file_contents, 0, sizeof(wchar_t)*file_length); 23 // 一次性读取整个文件 24 read_file.Read(file_contents, sizeof(char)*file_length); 25 // 关闭文件 26 read_file.Close(); 27 // 将读取的文本存入核心变量 TxtFileString 中 28 TxtFileString = (CString)file_contents; 29 // 读取文件成功 30 return true; 31 } 32 catch (CFileException *e) 33 { 34 CString read_errror_str; 35 read_errror_str.Format(L"保存数据失败的原因是:%d", e->m_cause); 36 MessageBox(read_errror_str); 37 read_file.Abort(); 38 e->Delete(); 39 // 读取文件失败 40 return false; 41 } 42 }
在代码注释中也已经提及到了,使用Read函数一次性读完,当文件非常大的时候肯定会爆的,这还是需要控制的,可以设定指定大小空间,循环多次读取,也可使用CStdioFile类的ReadString,一行一行的读取,当然,这里文本比较小,(人为控制了),就不考虑那么多了,(懂的都懂,就是懒的写)
-------------------->>>>>>>>>>>>>> 千万记得Close()关闭打开的文件,
2.文件另存为:这里就要去菜单中添加事件处理程序了,消息响应(在此之前,还需要搞定如何读写RichEditView上的文本,通过程序向RichEditView写文本(缺少流的方式) )
这里考虑到应该设置一个变量用来监控文件是否修改过,然后才能知道这个文本是否需要保存,就和Txt一样,若文本没有改动,是不需要保存的,可以直接关闭,这里也是实现这一点,但是发现有一个函数可以实现这一点,CRichEditCtrl类(提供Rich Edit 控件功能,提供一些函数,在RichEdit上操作)的SetModify函数和GetModify函数,自己查资料吧,SetModify可以设置文本是否被修改过,GetModify能够获取这个状态,当然,除了你手动的通过SetModify设置状态,它也自动的在监测,只要修改了,它就能读取到----->>>>这表明,每一次保存都需要通过SetModify设置,表明文本已经保存,处于没有被修改状态(意思就是,已经存起来了,你关闭啥的随意,不必再次被保存,这个设计点的意义完全参考txt,)
。。。这里先把CTxt**View.h放出来,不然中途临时涉及到的变量不好捋清楚
1 // Txt0721View.h : CTxt0721View 类的接口 2 // 3 4 #pragma once 5 6 class CTxt0721CntrItem; 7 8 class CTxt0721View : public CRichEditView 9 { 10 public: 11 12 CRichEditCtrl *pRichEdit; // = &GetRichEditCtrl(); 13 14 int Number_QueryAccept; // 实现文件拖拽,调用QueryAcceptData函数,因其拖到视图/放开鼠标响应两次, 15 // 穿过视图放到框架响应一次,故设定视图中偶数可响应特定函数,在框架中置0,保证偶数 16 bool IsFileNew; // 文件是否新建 -- 标记当前Txt文本是不是新建出来的文本 17 // GetModify()函数取代了该 标记 功能 18 // bool IsFileSave; // 文件是否保存 -- 标记文件刷新后是否保存(就是看文件内容改变后有没有保存 19 bool IsFileExit; // 是否按过菜单上的退出按键 -- 是否点过退出键 20 // bool IsExit; // 是否退出程序 -- 是否退出程序(功能重复) 21 bool SaveButCancle; // 退出的时候要保存,弹出路径选择框,结果你又点了取消 22 // (在文件另存为,保存选择那里,选择路径时,你点了取消,取消当前操作,一般是退出操作或者保存操作 23 CString TxtFileString; // 存储文本文件的字符内容 24 CString TxtFileLoad; // 该文本的保存路径 25 26 27 28 bool SaveFileContents(); // 保存文件内容,存内容到指定的Txt文本 29 bool ReadFileContents(); // 读取文件内容,获取指定Txt文本内容 30 void InitCTxt0721View(); // 初始化 31 // 询问是否保存文件 -- 然后根据选择调用函数,主要用于(新建)和(打开)功能 32 // ture,保存/不保存文件;false,退出当前功能 33 bool AskToSaveOrNot(CRichEditCtrl *pRichEdit); 34 // 实现文件拖拽时的打开Txt功能 35 bool DragAndDropOpenFile(CString FileLoad); // 执行中退出事件--false,// 正常执行true; 36 37 38 39 40 41 protected: // 仅从序列化创建 42 CTxt0721View(); 43 DECLARE_DYNCREATE(CTxt0721View) 44 45 // 特性 46 public: 47 CTxt0721Doc* GetDocument() const; 48 49 // 操作 50 public: 51 52 // 重写 53 public: 54 virtual BOOL PreCreateWindow(CREATESTRUCT& cs); 55 protected: 56 virtual void OnInitialUpdate(); // 构造后第一次调用 57 virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); 58 59 // 实现 60 public: 61 virtual ~CTxt0721View(); 62 #ifdef _DEBUG 63 virtual void AssertValid() const; 64 virtual void Dump(CDumpContext& dc) const; 65 #endif 66 67 protected: 68 69 // 生成的消息映射函数 70 protected: 71 afx_msg void OnDestroy(); 72 afx_msg void OnFilePrintPreview(); 73 afx_msg void OnRButtonUp(UINT nFlags, CPoint point); 74 afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); 75 DECLARE_MESSAGE_MAP() 76 public: 77 afx_msg void OnFileOpen(); 78 afx_msg void OnFileSaveAs(); 79 afx_msg void OnFileSave(); 80 afx_msg void OnFileNew(); 81 afx_msg void OnAppExit(); 82 83 84 virtual HRESULT QueryAcceptData(LPDATAOBJECT lpdataobj, CLIPFORMAT* lpcfFormat, DWORD dwReco, BOOL bReally, HGLOBAL hMetaFile); 85 86 bool IsUTF8(const void* pBuffer, long size); 87 }; 88 89 #ifndef _DEBUG // Txt0721View.cpp 中的调试版本 90 inline CTxt0721Doc* CTxt0721View::GetDocument() const 91 { return reinterpret_cast<CTxt0721Doc*>(m_pDocument); } 92 #endif
现在开始编写另存为事件处理程序:(点击菜单后,弹出另存为对话框,然后选择路径,保存)
1)文本类型处理,这里直接百度解决的,使用那个文件过滤器,就是确定一种后缀或者指定格式;
wchar_t szFilter[] = _T("文本文件(*.txt)|*.txt|All Files(*.*)|*.*||"); ---->>> Txt,或者,所有类型
CFileDialog SaveAsFileDlg(false, NULL, NULL, 0, szFilter); ----->>>参数false和true分别表示另存为和打开对话框,szFilter就是用来指定文件类型的,就是可惜没有一个字符编码的选项(我不知道有没有,我自己没搞清楚,我也还没查这个代码,就单纯抄了一下)
2)然后用SaveAsFileDlg.DoModal(),产生一个模态对话框(就是你必须先处理这个对话框,盖在你的原窗口上面,你不得不先处理),有确定和取消两个选项,分别对应这个函数的返回值,IDOK///IDCANCEL,可以用来检测用户到底选择了哪一个选项,
如果点击确定,表明,用户选择保存文件,这时就需要记录用户写入的文件路径,GetPathName()函数解决问题,然后就是关于文件后缀的问题,需要和szFilter[]数组结合起来判断,根据下标做判断就好了,具体实现看代码吧; 此处还有的一个问题就是是否存在同名文件,利用得到的文件路径去判断是否已经存在,然后询问用户是替换,还是就此结束,若替换,则直接保存到这个文件(直接替换),反之,则再次调用另存为函数,用来保存这个文件,直到用户保存了文件或者用户选择退出
如果点击了取消:表明另存为终止,这里是需要做出标记的,因为可能这是在退出过程中询问文件是否保存而引出的另存为事件,若这里用户选择取消,程序是不可以退出的,这里是一个细节的把控,需要注意,此处设计了一个变量SaveButCancle,用来专门标记这种情况,此外,设置SetModify(TRUE),表明该文件改动过,(因为每一次保存都会设置为文件没有被改动,所以保存失败需要重新设置为文件改动过)
1 /* 2 function:文件另存为 3 1.弹出窗口,选择路径,然后->是,把文本内容保存到该路径,即复制当前窗口内容, 4 然后根据文件路径建立一个txt文本文档,然后写入数据,关闭该文档,回到原窗口,结束 5 2.检查输入的文件路径是否已经存在 6 */ 7 void CTxt0721View::OnFileSaveAs() 8 { 9 // TODO: 在此添加命令处理程序代码 10 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 11 // 文件过滤,可以打开txt文本,目前设置只打开*txt文本。 12 wchar_t szFilter[] = _T("文本文件(*.txt)|*.txt|All Files(*.*)|*.*||"); 13 // wchar_t szFilter[] = _T("文本文件(*.txt)|*.txt||"); 14 // 用于文件另存为的选择对话框 15 CFileDialog SaveAsFileDlg(false, NULL, NULL, 0, szFilter); 16 // 判断另存为事件 点击了保存还是取消 保存 与 取消 17 if (IDOK == SaveAsFileDlg.DoModal()) // 选择保存文件 18 { 19 // 获取对话框返回的文件名------获取的是整个文件的路径 20 TxtFileLoad = SaveAsFileDlg.GetPathName(); 21 // 判断有无后缀,先检查选用的哪一个文件保存类型 .txt/.* 22 if (SaveAsFileDlg.m_ofn.nFilterIndex == 1) 23 { 24 // .txt 类型文件,判断是否有.txt后缀,没有就加上 25 if (TxtFileLoad.Right(4) != L".txt") 26 TxtFileLoad += ".txt"; 27 } 28 else if (SaveAsFileDlg.m_ofn.nFilterIndex == 2) 29 { 30 // All Files(*.*)|*.* 类型文件,检查是否有后缀(.),没有直接默认为 .txt 31 if (TxtFileLoad.Find('.') == -1) // 无后缀 32 TxtFileLoad += ".txt"; 33 } 34 // 文件另存为,表明文件已经保存,有文件路径了--不是新建文件 35 IsFileNew = false; 36 // 然后调用文件保存函数,保存文件数据------需要调用保存函数 CTxt0721View::OnFileSave() 37 // 判断是否已有同名文件 38 if (PathFileExists(TxtFileLoad)) // 已经存在同名文件 -- 询问是否替换 39 { // 文件已经存在 40 int ID_Message = MessageBox(L"该文本已存在,是否替换该文本?", L"提示", MB_YESNO); 41 if (ID_Message == IDYES) // 直接替换该文本 42 { 43 CTxt0721View::OnFileSave(); 44 } 45 else 46 { // 点否之后,没有路径 47 IsFileNew = true; 48 CTxt0721View::OnFileSaveAs(); // 再次调用另存为函数,保存文件 49 } 50 } 51 else 52 CTxt0721View::OnFileSave(); // 不存在同名文件,直接替换 53 } 54 else 55 { 56 SaveButCancle = false; // 退出的时候要保存,结果你又点了取消 57 // 此处即退出另存为函数,并且结束当前的功能,同时结束其父功能 58 // 例如,在 退出 功能中,调用另存为函数,然后在路径选择时,点了取消 59 // 则,此时不仅关闭 (另存为) 功能,同时需要关闭 (退出) 功能 60 IsFileNew = true; // 点取消之后,没有路径 61 pRichEdit->SetModify(TRUE); // 中途退出,文件没保存, 62 } 63 }
3.文件保存:这里就简单多了,
首先用SetModify(FALSE),表式文本已经保存过,没被改动(这里也是为什么另存为函数CTxt0721View::OnFileSaveAs()中没有出现这一句代码的原因,因为另存为函数不会直接保存文本到Txt,它还是通过获取文件路径后调用的OnFileSave()函数来实现保存的,所以另存为函数不需要这一行代码)
其次就是获取视图上(CRichEditView)的文本,然后写入文件:根据目前有无文件路径做出判断,有文件路径--可以直接保存,调用写入文本函数CTxt0721View::SaveFileContents();若没有文件路径,则调用另存为函数,进行存储; 实际上是可以发现的,另存为函数主要是为了得到文件路径,文本保存工作主要还是通过保存函数OnFileSave()调用SaveFileContents()实现写入Txt;
1 /* 2 function:文件保存 3 1.若文本是新建出来的,即IsFileNew = true,此文件没有保存路径,按保存需弹出另存为的功能。 4 2.若文本已存在路径,则按照该路径对文本数据进行保存即可。 5 */ 6 void CTxt0721View::OnFileSave() 7 { 8 // TODO: 在此添加命令处理程序代码 9 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 10 // 用于记录文本是否修改过,FALSE未修改,每一次保存调用一次,表明未修改 11 pRichEdit->SetModify(FALSE); 12 // 获取需要保存的文本TxtFileString,即CRichEditView上的文本信息 13 // pEdit->GetSelText(); 获取选中区域的文本 14 pRichEdit->GetWindowText(TxtFileString); 15 // 调用文件保存函数,使数据保存到Txt文本之中 16 if (IsFileNew) 17 { // IsFileNew = true,文本是新建出来的,文件没有保存路径 18 // 弹出 另存为 的功能 ---- 用于获取 文件的保存路径 19 CTxt0721View::OnFileSaveAs(); 20 } 21 else 22 { 23 // IsFileNew = false,文件有保存路径,直接保存 24 CTxt0721View::SaveFileContents(); 25 } 26 }
4.新建:说实话,这个看的有点懵,因为孙鑫那本书讲了一堆,又没看出有什么用。。。(虽然我是写完了代码才看到有这段。。。。)我的解决方案就很简单了,直接询问是否保存当前界面上的数据,然后就直接清空界面,把若干数据全部都置为初始化状态,ok,问题解决。这不就是新建(真是个小机灵鬼)(Txt就是这个流程,虽然不知道它是怎么实现的)
AskToSaveOrNot(写在第六点 -- 6.)用来询问用户是否保存,(若保存,就调用函数实现保存)
这个函数的返回值并不是表示文件保存与否,而是用来判断用户是否中途截断了运行,(比如点击新建菜单,内部会调用这个函数,询问是否保存当前文本,但是如果这时用户直接关闭了这个询问保存的对话框,就是截断了运行,此时,程序也只能结束它的父功能,即新建功能)(设计AskToSaveOrNot这个函数,主要还是多个地方会用到,都需要这样的一个功能)
后面几行代码比较简单,就是重置参数,和清空界面,可以查询CRichEditCtrl类,
1 /* 2 功能:新建 3 1.保存现有文件(暂无法判断文件是否修改后保存了) -- 判断已可以实现 4 2.清空当前数据,当作新文件 -- 造假新建 5 */ 6 void CTxt0721View::OnFileNew() 7 { 8 // TODO: 在此添加命令处理程序代码 9 // 保存文件 10 // CTxt0721View::OnFileSave(); 11 // 询问是否保存 12 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 13 // 判断文件是否保存/不保存=true; 取消--退出当前功能=false; 14 if (CTxt0721View::AskToSaveOrNot(pRichEdit) == false) 15 return; 16 // pRichEdit->GetModify() = fasle 文件未修改 -- 文件已经保存 或者 新建文件没有动过 17 18 // 执行到此处,当前 文件已经保存 或者 文件未保存,可以新建文档界面 19 // 新建文件 IsFileNew 20 CTxt0721View::InitCTxt0721View(); // 重置各种参数 21 // 清空数据信息 CRichEditView 22 // CRichEditView界面,文字全选,清空 23 pRichEdit->SetSel(0, -1); 24 pRichEdit->Clear(); 25 // 用于记录文本是否修改过,FALSE未修改,新建出来的文档默认未修改 26 pRichEdit->SetModify(FALSE); 27 }
5.打开文件:
同样的意思,也是先调用AskToSaveOrNot进行处理,询问是否保存当前已有的文本,然后重置参数(迎接新的文本),弹出对话框,选择待打开的文本,读取文件,显示文本到View,思路简单
1 /* 2 function:打开文件 3 0.打开文件,首先需要保存当前现有文件,然后关闭当前文件,打开新文件 4 1.根据文件选择对话框,选择需要打开的文件,获取其路径 5 2.根据路径打开文件,读取数据信息,然后写入 CRichEditView 6 */ 7 void CTxt0721View::OnFileOpen() 8 { 9 // TODO: 在此添加命令处理程序代码 10 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 11 // 判断文件是否保存/不保存=true; 取消--退出当前功能=false; 12 if (CTxt0721View::AskToSaveOrNot(pRichEdit) == false) 13 return; 14 15 CTxt0721View::InitCTxt0721View(); // 重置各种参数 16 17 // true参数打开文档,false参数则是另存为 18 // 文件过滤,可以打开txt文本,目前设置只打开*txt文本。 19 wchar_t szFilter[] = _T("文本文件(*.txt)|*.txt|All Files(*.*)|*.*||"); 20 // wchar_t szFilter[] = _T("文本文件(*.txt)|*.txt||"); 21 // 用于文件选择对话框 22 CFileDialog OpenFileDlg(true, NULL, NULL, 0, szFilter); 23 // 判断打开与否---判断点击了哪一个按钮,是打开,还是,取消 24 if (IDOK == OpenFileDlg.DoModal()) 25 { 26 // 获取对话框返回的文件名------获取的是整个文件的路径 27 TxtFileLoad = OpenFileDlg.GetPathName(); 28 // 打开文件,表明文件并非是新建的,已经有文件路径了 29 IsFileNew = false; 30 // 按照上述路径,读取文件内容,调用 ReadFileContents 函数 31 CTxt0721View::ReadFileContents(); 32 // 写入 CRichEditView 33 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 34 // 文本全选 35 pRichEdit->SetSel(0, -1); 36 // 使用下面两行的目的是为了替换的时候不至于出现 文字全选而出现的颜色, 37 pRichEdit->Clear(); 38 pRichEdit->SetSel(0, -1); 39 // 替换选中的文本,即输入读取的文本信息 40 pRichEdit->ReplaceSel(TxtFileString); 41 // 用于记录文本是否修改过,FALSE未修改,新建出来的文档默认未修改 42 pRichEdit->SetModify(FALSE); 43 } 44 }
6.两个函数,一个用来重置参数,CTxt0721View::InitCTxt0721View();
一个用来询问,当前文本是否保存,CTxt0721View::AskToSaveOrNot();
1 /* 2 功能:对CTxt0721View 对象进行初始化操作 3 1.对应新建操作后的响应 -- 简而言之,初始化操作 4 */ 5 void CTxt0721View::InitCTxt0721View() 6 { 7 IsFileNew = true; // 默认是新建文档,即打开程序后的空白文档。 8 IsFileExit = false; // true表示已经经历过退出键了,OnClose不必再次退出 9 SaveButCancle = true; // 退出的时候要保存,弹出路径选择框,结果你又点了取消 10 // (在文件另存为,保存选择那里,选择路径时,你点了取消,取消当前操作,一般是退出操作或者保存操作 11 TxtFileString.Empty(); // 初始文本内容为空 12 TxtFileLoad.Empty(); // 文本的保存路径 13 } 14 15 16 /* 17 功能:询问是否需要保存当前文件,并根据决策做出保存操作 18 1.实现询问是否保存以及保存功能 19 2.若是在保存中退出,则返回当前一个标识,提示结束当前功能 20 */ 21 bool CTxt0721View::AskToSaveOrNot(CRichEditCtrl *pRichEdit) 22 { 23 // 判断当前文件是否修改,是否需要保存 24 if (pRichEdit->GetModify()) // 文件已修改 -- 需要保存 -- 选择直接 保存 或者 另存为 25 { 26 CString save_message = L"是否要将更改保存到"; 27 if (IsFileNew) 28 save_message += L" 无标题 "; 29 else 30 save_message += TxtFileLoad; 31 // 弹出对话框,询问是否保存现有文件 MB_YESNOCANCEL -- Yes -- No -- Cancel 32 int ID_Message = MessageBox(save_message, L"Txt提示", MB_YESNOCANCEL); 33 if (ID_Message == IDYES) // 保存文件 34 { 35 // 检测有无文件路径 36 if (!IsFileNew) // 有文件路径 --- 调用保存函数 37 { 38 CTxt0721View::OnFileSave(); 39 } 40 else // 无文件路径 --- 调用另存为函数 41 { 42 CTxt0721View::OnFileSaveAs(); 43 } 44 if (pRichEdit->GetModify()) // 确定保存,但是最后没有保存 -- 等效于取消当前事件 45 return false; 46 } 47 else if (ID_Message == IDCANCEL) 48 return false; // 取消 -- 直接退出 -- 同时需要退出 当前功能 49 } 50 return true; 51 }
7.最后一个功能 -- 退出:
退出嘛,不就是退出三部曲,WM_CLOSE,WM_DESTORY,WM_QUIT,在退出函数CTxt0721View::OnAppExit()中发送消息WM_CLOSE不就得了。。。这个世界总是无情棒喝单纯的人。。
先不管,照常写退出函数,按照Txt功能,退出前首先是要询问是否保存当前文本的(前提是当前文本改动过没保存),这是老操作了,不难,只要在合适位置发送WM_CLOSE消息就行了,问题解决。。(解决个屁)
1 /* 2 功能:退出 -- 此处主要是实现菜单部分的退出按键 3 1.若有文件路径已保存,则可以直接退出,反之需要询问是否保存 4 2.弹出窗口,询问是否保存(保存,不保存,取消) 5 */ 6 void CTxt0721View::OnAppExit() 7 { 8 // TODO: 在此添加命令处理程序代码 9 SaveButCancle = true; // 退出的时候询问是否保存,点了保存后,选择路径时,又点了取消 10 // 结果你又点了取消,取消退出,置fasle,-- 正常情况下可以直接退出,SaveButCancle = true 11 IsFileExit = true; // 默认按过退出菜单,置true 12 13 // CRichEditCtrl *pRichEdit = &GetRichEditCtrl(); 14 // pEdit->GetModify() -> 判断文件是否已经保存 15 if (pRichEdit->GetModify()) // 文件已修改,未保存 -- 提示用户是否保存 16 { 17 // 提示消息罢了 18 CString save_message = L"是否要将更改保存到"; 19 if (IsFileNew) 20 save_message += L" 无标题 "; 21 else 22 save_message += TxtFileLoad; 23 // 退出时,弹出对话框,询问是否保存 MB_YESNOCANCEL -- Yes -- No -- Cancel 24 int ID_Message = MessageBox(save_message, L"Txt退出", MB_YESNOCANCEL); 25 if (ID_Message == IDYES) // YES按键 -- 保存文件,然后退出 26 { 27 // 检测有无文件路径 28 if (!IsFileNew) // 有文件路径 --- 调用保存函数 29 { 30 CTxt0721View::OnFileSave(); 31 } 32 else // 无文件路径 --- 调用另存为函数 33 { 34 CTxt0721View::OnFileSaveAs(); 35 // 检查 另存为 功能是否完整实现 36 if (SaveButCancle == false) // 另存为功能中途退出 37 { 38 IsFileExit = false; // 退出菜单无效,退出该功能,置false 39 return; // 直接退出--退出功能 40 } 41 } 42 AfxGetMainWnd()->PostMessage(WM_CLOSE); // 发送WM_CLOSE消息,退出 43 } 44 else if (ID_Message == IDNO) // 文件不保存,直接退出即可 45 { 46 AfxGetMainWnd()->PostMessage(WM_CLOSE); // 发送WM_CLOSE消息,退出 47 } 48 else if (ID_Message == IDCANCEL) // 取消--无操作 49 { 50 IsFileExit = false; // 退出菜单无效,退出该功能,置false 51 return; // 直接退出--退出功能 52 } 53 } 54 else // 文件未修改,即文件(已经保存过 或者 刚新建没动直接 , 退出即可 55 { 56 AfxGetMainWnd()->PostMessage(WM_CLOSE); // 发送WM_CLOSE消息,退出 57 } 58 }
问题在哪儿?你会发现,当你点击退出菜单之后,会弹出一个对话框询问你是否保存,你点击之后发现还会弹出一个对话框。。。。
那么为什么会出现这个问题。。。。。
回到系统自动为我们生成的代码,当你运行时,你编辑文本后,点击退出,发现一样会跳出对话框询问你是否保存,
那现在就能够理解了,弹出的两个对话框一个是OnAppExit()中我们自己写的,还有一个是系统自己给你提供的。。。。是不是突然感觉自带功能也不是那么友好。。。。
那到底它在哪里控制这个弹窗的出现呢?
寻寻觅觅:发现:肯定是和ID_FILE_CLOSE消息有关的,经过我一番查找(我也不记得是在哪找到的了,反正就是一番查找)(老老实实翻文档呗)(关闭对话框 介绍对话框的创建与关闭)
看到了吧,意思就是说,SaveModified()函数中判断文本是否修改,修改了就给你弹出对话框问你保不保存,那现在问题就简单了,我去Doc中拦截这个处理;
SaveModified()是一个虚函数,类向导,阿巴阿巴,当然就是干掉自带的那一句return CRichEditDoc::SaveModified(); 然后我们自己来写return就好了,它的返回值类型是BOOL,。。。真的不喜欢这样写,搞得我总以为是bool,其实是int,。。。 这里返回return false;(将表示不希望窗口关闭,相当于对关闭消息不作处理,你发任你发),返回值return true;理论上也就是返回1,这表明将关闭窗口,也就是后续会调用OnCloseDocument,销毁文档(如果这里有什么我理解错了,希望能够指点我一下,这里仅个人对这个资料的理解,刚学,怕有理解错误)
OK,问题解决,这样就好办了,去SaveModified()中做调整就好了;
。。。。。。问题又来了,菜单上的退出确实没问题,可以达到目的,但是这里不是还有一个退出。。。
当我点击这个叉叉时,由于我重写的SaveModified(),他不会给我提供对话框询问保存与否,。。。所以这里实际上是需要我们做一个调控的,用一个变量IsFileExit来记录是否有执行过OnAppExit()函数(也就是询问到底有没有弹出对话框来询问是否保存)(特别说一点,这里弹出对话框询问是否保存都是针对文本已修改未保存的情况,特别注意哦),没有执行就去在执行一次,这次问题算是真的解决了。
1 BOOL CTxt0721Doc::SaveModified() 2 { 3 // TODO: 在此添加专用代码和/或调用基类 4 5 // return CRichEditDoc::SaveModified(); 6 // 如果继续并关闭文档,则为非零值;如果文档不应关闭,则为0。 7 CMainFrame *pMain = (CMainFrame *)AfxGetApp()->m_pMainWnd; 8 CTxt0721View *pView = (CTxt0721View *)pMain->GetActiveView(); 9 10 // 退出发问,有没有按过退出(只执行一次 退出 功能)---没有那手动给你按,按过,滚蛋 11 // 执行过IsFileExit = true; 没有执行过IsFileExit = fasle; 12 if (pView->IsFileExit == false) 13 pView->OnAppExit(); // 手动执行 退出 功能 14 15 // 1.关闭窗口需要保证执行过(退出)功能,pView->IsFileExit判断是否执行过 16 // 2.关闭窗口需要保证,整个(退出)功能完整执行,pView->SaveButCancle判断是否中途退出 17 if (pView->IsFileExit && pView->SaveButCancle) 18 return true; 19 else // 窗口不关闭 20 return false; 21 }
至此,差不多告一段落了,基本功能都实现了吧,
下一篇03:MFC实现Txt03之CRichEditView实现文本拖拽功能
2022-09-14:回来挨打,这里的××该怎么拦截呢?当然是使用函数OnSysCommand(UINT nID, LPARAM lParam),专门拦截控制栏信息,nID就是控制栏的各种消息ID,如果想拦截××,只需要做判断if (nID == SC_CLOSE),然后在if里面做出相应处理即可
这个保存了代码,有时间搞上来,。。很懒
2022-08-17(0720)