经过了之前大量mfc已经明白了许多功能,但是之前的项目一直没有使用到mfc的菜单功能,菜单是Win32功能很常见的东西,这个也必须要懂。其实菜单的建立也很简单,配合之前的文件操作功能,已经能够把开始->附件->记事本,就是那个notepad.exe写出来了。
一、基本目标
首先,这个记事本能够最大化,最小化,里面的编辑框也能够跟住最大化最小化,就是记事本里面组件不会失真,这个也不是理所当然的时候,要对OnSize进行写作,否则里面的组件是不会跟着最大化的,这大概就是mfc比vb艰难的原因吧!
之后,这个记事本的窗体大小与平常Windows的窗口一样,能够自由调整大小,里面有相应的餐单。按帮助->关于,能够显示版权信息。其实这里已经把记事本模仿得很像了,要不是那个版权信息太丑的话~
点击“新建”按钮会弹出提示框,告诉用户如果继续执行这个命令,所有没有保存的信息会丢失,如果用户点“是”,则新建一个文件
之后是打开与保存、另存为功能,这里就不再介绍是什么样了,如下图,就是跟你平常用的记事本一模一样
最后是设置字体功能,能够设置记事本显示的字体,这与Windows里面的记事本是一模一样,毕竟记事本从头到尾都是一个纯文本的编辑器,设计字体只是设置其显示字体而已!用户选择相应的字体之后,记事本里面的字体就会以用户选择的字体显示。
二、准备过程
1、首先还是新建一个简单的mfc工程,里面什么都没有,我们之后再添加,主要是windows自带的东西太渣了,我们自己写出来更加漂亮。如果你连mfc的创建都不会,也没有关系,可以看我之前的文章
2、新建出来的工程与之前的《【mfc】用对话框的切换实现重新登录》(点击打开链接)一样,把确定按钮的OnOk函数注释掉,免得这个对话框一按回车就没有
3、之后把对话框里面的组件拉成如下图的形式,同时把各个组件的样式设置好:
4、之后,我们就开始为这个对话框建立菜单,如下图,为这个工程插入资源,如《【mfc】对于对话框程序的优化与为程序创建图标》(点击打开链接)中的图标一样,菜单也是资源的一种,只是菜单的编辑方式不同而已。
5、菜单的编辑如下图所示,里面还有“分隔符”选项,如果一点分隔符,组件中的所有东西都会变灰,系统自动识别这是“分隔符”而不是一个有功能的子菜单
6、长按一个子菜单拖拽可以改变子菜单的位置
7、按照上面的方式,建立如下图的菜单,然后切换的主对话框CxxDlg,其中xx为你的工程名,在这个主对话框中的样式添加菜单
8、之后,如下图为各个菜单中添加相应的类函数
9、把这个菜单的子菜单的消息映射函数与对话框关联起来,则设置其函数名,最后才能添加函数
10、对子菜单创建完消息映射函数则与组件的函数一样,能够在ClassView看到,当然,双击ClassView里面的函数也一样能够编辑,为所有子菜单,也就是那些"新建"、"打开"……添加完消息映射函数,我们则可以正式进行程序的写作。
三、各个子菜单的功能制作
虽然上面的准备功能很多,但是之后的函数就很简单,组件的布局完毕,之后写一些简短的函数就完成了,这就是微软旗下的编程的特点,它不像java中的Swing,单单是布局都要写一大堆代码。现在做微软编程的人看不起写Java的,说写一大段然后才能实现一个功能,写java看不起写微软的,说是拖控件的,谁也看不起谁。其实,无知者才是最可怕的,各有各特点,控件你不懂也不拖,代码你不懂你才觉得写一大段才能实现自己的功能,主要是自己废,不关人家编程语言的事情。你两方面都懂才是王道。好了,废话不多说了,回到这个功能里面来。
(一)编辑框的大小随同窗口的大小变化而变化
1、首先你要为这个主功能添加一个窗口消息处理函数,在里面找到WM_SIZE添加,这个与《【mfc】利用文件的读写,theApp全局变量来现实登录帐号管理系统》(点击打开链接)中添加OnInitDialog与OnDestory一样。不多说了,不明白可以翻翻我之前的日志。
2、之后对这个OnSize函数写上如下代码,这个函数主要是对用户调整窗口大小时做出的响应,包括最大化,但不包括最小化。最小化的功能系统自己就能够实现,但是它会把自身的窗口坐标变成负数,导致下面基于窗口尺寸的控件大小,出现负值,程序就崩溃。所以正确的做法是,一旦窗口的坐标变成负数,就不要执行这段代码。
void CNotepadDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); //最小化以后,控件的窗口坐标值都会变成负数,再还原,此时,lastwindowrect为负, //需要你把下面的代码进行判断,否则程序会出错 if(nType!=SIZE_MINIMIZED){ CWnd *pWnd; //获取ID为i的空间的句柄 pWnd = GetDlgItem(IDC_EDIT1); CRect rect; //获取控件变化前的大小 //判断是否为空,因为对话框创建时会调用此函数,而当时控件还未创建 if(pWnd){ pWnd->GetWindowRect(&rect); ScreenToClient(&rect);//将控件大小转换为在对话框中的区域坐标 //cx/m_rect.Width()为对话框在横向的变化比例 //cy/m_rect.Height()为对话框在纵向的变化比例 rect.left=rect.left*cx/rect.Width();//调整控件大小 rect.right=rect.right*cx/rect.Width(); rect.top=rect.top*cy/rect.Height(); rect.bottom=rect.bottom*cy/rect.Height(); pWnd->MoveWindow(rect);//设置控件大小 } GetClientRect(&rect);//将变化后的对话框大小替换旧大小 } }
这个就不多说,这个与《【mfc】利用文件的读写,theApp全局变量来现实登录帐号管理系统》(点击打开链接)中也多个对话框中切换一模一样,先画好CopyrightDlg这个对话框,再为这个对话框新建类向导,然后因为CopyrightDlg除了显示与自带的关闭功能以外没有任何功能,CopyrightDlg完全不用写东西,只需要在主函数里面的“帮助”->“关于...”类函数,引入头文件,写入打开“关于……”对话框的代码即可
#include "CopyrightDlg.h" void CNotepadDlg::OnCopyright() { // TODO: Add your command handler code here CCopyrightDlg dlg; dlg.DoModal(); }
(三)文件->新建
代码如下,很简单:
void CNotepadDlg::OnFileNew() { // TODO: Add your command handler code here if(IDNO==MessageBox("所有没有保存的信息,将会丢失,是否继续?","记事本",MB_YESNO)) return; SetDlgItemText(IDC_EDIT1,""); SetWindowText("记事本"); filename=""; }
(四)文件->打开
这个有点难,虽然基本原理与《【mfc】使用系统文件对话框打开文件与保存文件、利用StdAfx.h设置全局变量》(点击打开链接)一样,但是这里是对编辑框进行操作,而不是列表空间,因此,你首先要把文件的所有内容读入一个字符数组里面,再读到编辑框里面。注意打开文件之后,把标题栏、当前控制文件的路径更新一样。
void CNotepadDlg::OnFileOpen() { if(IDNO==MessageBox("所有没有保存的信息,将会丢失,是否继续?","记事本",MB_YESNO)) return; CFileDialog dlg(TRUE,"txt","",OFN_HIDEREADONLY,"文本文件(*.txt)|*.txt||"); if(IDCANCEL==dlg.DoModal()){ return; } filename=dlg.GetPathName(); SetWindowText(filename); CFile file; if(!file.Open(filename,CFile::modeRead|CFile::shareDenyNone)){ AfxMessageBox("打开文件失败"); return; } //读取要打开文件的大小 //然后根据打开文件的大小创建一个相应大小的字符数组 CFileStatus stat; file.GetStatus(stat); //+1是因为我们要对这个字符数组,后补\0 char *pText=new char[stat.m_size+1]; //file.Read的返回值是读到的文件大小 int nRet=file.Read(pText,stat.m_size); pText[nRet]='\0'; //把读出来的东西放到文本框里面,然后删除这个字符串数组 SetDlgItemText(IDC_EDIT1,pText); delete []pText; file.Close(); }
首先读取当前控制的文件路径,也就是定义在开头的那个filename,如果读不了,证明用户还没有打开文件,就选择“保存”按钮,直接拖去“另存为”的处理,使用一个CString作为编辑框的内容与文件的过渡,把这个CString的内容写入文件,就完事了。
void CNotepadDlg::OnFileSave() { // TODO: Add your command handler code here CFile file; if(!file.Open(filename,CFile::modeCreate|CFile::modeWrite)){ CNotepadDlg::OnFileSaveas(); return; } CString string; GetDlgItemText(IDC_EDIT1,string); file.Write(string,string.GetLength()); file.Close(); }
你自然要弹出一个“另存为”的CFileDialog给用户爽吧,然后这个通过CFileDialog,如《【mfc】使用系统文件对话框打开文件与保存文件、利用StdAfx.h设置全局变量》(点击打开链接)一样获取到文件路径,然如上面“保存”那样对文件进行写入操作。注意这里还要更新一下标题栏。
void CNotepadDlg::OnFileSaveas() { // TODO: Add your command handler code here CFileDialog dlg(FALSE,"txt","",OFN_OVERWRITEPROMPT,"文本文件(*.txt)|*.txt||"); if(IDCANCEL==dlg.DoModal()){ return; } CFile file; filename=dlg.GetPathName(); if(!file.Open(filename,CFile::modeCreate|CFile::modeWrite)){ AfxMessageBox("保存文件失败"); return; } CString string; GetDlgItemText(IDC_EDIT1,string); file.Write(string,string.GetLength()); file.Close(); SetWindowText(filename); }
主要是设置显示字体,为了使设置的字体的辉煌仅仅停留在OnFontSet这个函数那样段,这里,你还要在开头像写入CString filename;那样写入CFont font;之后,则如下面的代码所示
void CNotepadDlg::OnFontSet() { // TODO: Add your command handler code here //lf是指向字体的指针 LOGFONT lf={0}; //如果现在已经存在字体了,那么获取当前字体的信息 if(font.GetSafeHandle()){ font.GetLogFont(&lf); } //字体设置需要到系统的字体编辑对话框,里面的参数是lf所指向的字体 //一打开字体编辑对话框自动选中这个字体 CFontDialog dlg(&lf); if(IDCANCEL==dlg.DoModal()){ return; } //这样就能获取到,用户选中的字体保存到名为font的CFont对象中 //其中CFont font;已经在本文件的文件开头就定义成本cpp的全局变量了 dlg.GetCurrentFont(&lf); font.DeleteObject(); font.CreateFontIndirect(&lf); //取出操作文字输入框的句柄m_edit,以后m_edit就是操作这个对话框的句柄 CEdit* m_edit=(CEdit*)GetDlgItem(IDC_EDIT1); m_edit->SetFont(&font); }
至此,整个高仿记事本就制作完毕了。当然,还有“编辑”中的撤销功能,这个估计要设计一个链表记录用户的操作,剪切、复制、粘贴设计MFC中的对剪切板ClipBoard.exe的操作,估计应该有自带的函数,还没有研究。查找与替换与CFileDialog、CFontDialog一样,有自带的对话框,利用这个对话框获取到用户输入的内容,把当前编辑框的文本拿个CString存起来,再进行C语言那样的对CString进行遍历,查找与替换的操作……