MFC基础知识与课程设计思路

引言

        本文致力于提供MFC的相关知识,以方便大家更好地认识MFC的使用方法。介绍将会分为以下几个部分:MFC初始文件的理解、MFC我们所使用的框架理解、MFC的进阶用法、MFC我在使用过程中遇到的问题及解决方法。

MFC初始文件的理解

        MFC的初始文件中有两项文件是我们经常需要使用的,其余文件可以暂时忽略,我们基本不会用上。一个是MFCApplicationView,我们简称View类,一个是MFCApplicationDoc,我们简称Doc类。

        Doc类是Document的缩写,是用来处理程序与计算机文件的关系的,我们读取、保存文件就是通过这个类实现的,我们如果想要获取读取的图像信息,也都是通过Doc类进行读取的。例如以下这个我们常用的代码段。

	CMFCApplication1Doc* pDoc = GetDocument();// 获取文档		//
	long	lSrcLineBytes;		//图象每行的字节数
	long	lSrcWidth;      //图象的宽度和高度
	long	lSrcHeight;
	int     lpSrcBitCount;       //图像的位深
	LPSTR	lpSrcDib;		//指向源图象的指针	
	LPSTR	lpSrcStartBits;	//指向源像素的指针
	lpSrcDib = (LPSTR) ::GlobalLock((HGLOBAL)pDoc->GetHObject());// 锁定DIB
	if (!lpSrcDib) return;
	/*
	if (pDoc->m_dib.GetColorNum(lpSrcDib) != 256)// 判断是否是8-bpp位图
	{
		   AfxMessageBox(L"对不起,不是256色位图!");// 警告
		   ::GlobalUnlock((HGLOBAL) pDoc->GetHObject());// 解除锁定
		   return;									//返回
	 }										//判断是否是8-bpp位图,不是则返回
	 */
	lpSrcStartBits = pDoc->m_dib.GetBits(lpSrcDib);			// 找到DIB图象像素起始位置	
	lSrcWidth = pDoc->m_dib.GetWidth(lpSrcDib);					// 获取图象的宽度		
	lSrcHeight = pDoc->m_dib.GetHeight(lpSrcDib);					// 获取图象的高度	
	lpSrcBitCount = pDoc->m_dib.GetBitCount(lpSrcDib);                    //获取图像位深
	lSrcLineBytes = pDoc->m_dib.GetReqByteWidth(lSrcWidth * lpSrcBitCount);		// 计算图象每行的字节数
///

        其中GetDocument()函数是View类的函数,其功能就是提取先前Doc类已经读取好的内容(通过指针直接读取Doc类的内存)而此后的pDoc->m_dib是CDib类,CDib的功能是存储图像信息。

        为什么信息存放在CDib中而不是直接存放在Doc类中,这是个值得思考的问题,大家可以通过查看两个类中定义好的函数,感知一下两个类功能上的差别,这个差别很难准确描述。

        View类是一个用于与用户交互的类,其功能包含很广泛,比如用户按下某个选项后应该进行什么函数操作(其实就是我们做的部分)、弹窗、鼠标的形状改变等。

        如果要进行一个比较形象的比喻,View类是前端,Doc类是后端。

MFC我们所使用的框架理解

        我们使用的MFC框架基本流程如下:

        在菜单MENU中(你可以理解为View类给出的对话框),我们可以定义选项卡,命名为ID_名字。我们在View类事件中直接添加COMMAND,可以添加该ID的On函数,并自己编写改函数。我们所编写的函数体将会在这个选项卡被点击后执行。执行的内容可以是直接进行某项操作,例如弹出对话框,也可以调用指定的对话框类函数(可以不调用)。

        其实以上这一部分理解了之后我们就已经可以进行很多功能的实现了,只是为了让我们与用户的交互性更强,我们需要定义对话框类来收集用户想要输入的信息。(一般情况下,对话框类只负责收集信息,收集完信息后,我们仍需要在View类中处理这个输入对应的结果)

        搭建对话框类的时候,我们需要在Dialog中创建一个IDD_名字,这个东西是我们的设计页面。

        在进行页面设计的过程中,最好对同一组物体按照顺序生成,如我想要创建三个单选框,我就直接按顺序创建IDC_RADIO1、IDC_RADIO2、IDC_RADIO3,而不是在创建IDC_RADIO1后创建一个文本框,再创建IDC_RADIO2,或者创建IDC_RADIO1、IDC_RADIO2之后,删掉IDC_RADIO2再创建一个。设计页面的时候最好提前构思,少做修改,因为我们创建的所有控件都会在后台有一个数字ID,例如IDC_RADIO1可能对应1001,那么IDC_RADIO2就会对应1002,用于让电脑快速识别,不按照顺序创建可能会打乱这个ID,影响以后的操作。

        在设计完页面之后,我们需要先创建类,右键对话框直接创建一个CDlg名字类就行。创建完毕后,我们可以直接在IDD页面添加成员变量,在CDlg类中添加对话框函数与控件函数,也可以通过类向导构建。类向导构建的速度更快,上手之后使用会很方便。

        下图就是类向导页面,我们可以观察资源这里是不是我们想要的IDD名称,如果是ABOUTBOX,说明你还没有添加类,一定要添加类才能使用类向导。类向导有五个部分组成,命令、消息、虚函数、成员变量、方法。方法一般用不到,我不做解释。

        其中,命令是用于添加控件函数的,包含了所有的ID,里面有很多是不需要我们管的,我也不知道为什么他不进行一个筛选。我们可以手动翻看IDC开头的所有ID,如果某个对象的消息函数不是只有上图所示的这两个消息的时候,就说明这个ID是我们的IDD页面里面的控件,可以放心添加函数了。

        消息包含了所有的对话框的消息函数,比如你按下鼠标的时候(OnLButtonDown)、抬起鼠标的时候(OnLButtonUp),移动鼠标的时候对应的函数。(OnMouseMove)。虚函数则是系统定义过了,我们可以重写的函数,比如OnInitDialog(初始化函数)、OnOK(按下确定按钮后发生的事情)。消息和虚函数的差别就是,消息函数都是可有可无的函数,我移动鼠标可以有函数,也可以没有。而虚函数都是必须有的函数,你没写,系统就会用他默认的那一套。

        成员变量就不用细说了,每个控件可以添加一个值与一个CButton类的控件。CButton类的控件可以控制这个控件的状态。下面这个框里的可见,禁用,只读什么的应该都能控制。值就是值,是控件的内容,是编辑框的值,是单选框的序号。

 

         针对单选框,有一个需要注意的地方。单选框是有组选项的,我们可以通过设置第一个单选框的组为TRUE,第N+1个为TRUE,来让第一个到第N个单选框的成为一组。如果不需要分多组,只需要让第一个单选框的组为TRUE,其余全为FALSE即可。这一切的大前提是你的RADIO的ID是按照顺序来的,没有乱,不然计算机就没法将他们识别为一组了。

        在类向导中,一组的单选框只有第一个能设置成员变量与函数,我也不知道为什么,但是你可以通过手动添加函数的方式,在CDlg类中直接添加。对于单选框来说,值就是他们的序号,当你按下第一个单选框,值会变成0,按下第二个,值会变成1,按下第N个,值会变成N-1。

         在创建完对话框类类,添加完对话框类函数、每个控件的变量与函数之后,我们需要将对话框类添加到View类中。我们需要在View类中添加头文件以导入对话框类,然后通过函数去调用,一般函数如下:

	CDIG名字 dlgPara;// 创建对话框		
	dlgPara.变量= View里的变量;//导入一些对话框需要的变量,比如图像数据之类的

	if (dlgPara.DoModal() != IDOK)// 显示对话框, 
	{
		return;
	}

    View里的变量 = dlgPara.变量;//将对话框的变量导出来,我们可以通过这个知道用户输入了什么,并进行相应的操作
    //此处有处理函数
    pDoc->SetModifiedFlag(true);
	pDoc->UpdateAllViews(NULL);//是否保存更改后的图像

        其中if(dlgPara.DoModal()!=IDOK){return;}的作用是,打开对话框,并在关闭对话框后结束这个语句。

        综上所述,实际上整个框架的流程是:View类控制Doc类读取文件内容,点击选项卡,(View类调用对话框,人输入信息),View类处理相关操作。

MFC的进阶用法

        一些常规的操作使用MFC的基本框架是很难实现的,我分享一些我用到的、大家经常看到但是不知道什么用的函数与操作。
        1、UpdateData()

        此函数经常出现于对话框类中,UpdateData其实是用于控制控件上显示的值(如编辑框中用户输入的值)与后台存储的值之间转化用的。UpdateData(TRUE)会将编辑框中的值全部复制到后台存储的值(其实就是你这个控件添加的那个值变量);UpdateData(FALSE)会将后台存储的值复制到编辑框中,形成一个交互的作用。

        2、BeginWaitCursor()

        这个函数是View类中的函数,用处是将鼠标变成转圈圈的等待状态,可以用EndWaitCursor()取消等待状态。如果有些函数的时间复杂度比较高,可以考虑在函数前BeginWaitCursor(),结束后EndWaitCursor()。

        3、预览图像

        预览图像的操作在传统框架中很难实现。因为按照我们之前的框架的逻辑,我们在结束对话框前是不会进行图像的处理操作的,因为我们的对话框只是收集数据。如果我们想要在对话框内修改数据,就必须面临一个问题:如果将在对话框中调用pDoc的函数,毕竟我们至少要使用

    pDoc->SetModifiedFlag(true);
    pDoc->UpdateAllViews(NULL);//是否保存更改后的图像

这两个函数才可以。

        方法很简单,就是传参。我们在对话框.h文件中加入头文件#include "MFCApplication1Doc.h",并在对话框类的public中添加    CMFCApplication1Doc* m_pDoc;成员变量,最后只需要在View类中输入    dlgPara.m_pDoc = pDoc;即可。以下是我编写的一个View类中的选项函数的例子:

void CMFCApplication1View::OnStylize()
{
	CMFCApplication1Doc* pDoc = GetDocument();// 获取文档	
	CDlgStylize dlgPara;// 创建对话框	
	dlgPara.m_pDoc = pDoc;
	if (dlgPara.DoModal() != IDOK)// 显示对话框 
	{
		return;
	}
}

        剩下的我们就只需要在对话框类中预览图像按钮的OnClickButton函数中加入我们的处理函数即可。取消预览只需要交换后台数据即可。(我们还需要重写OnOK函数,OnOK函数只需要调用我们的预览图像函数即可)

        以上方法是通过小改动实现的预览图像,效果并不是很好。加入我们通过平移操作处理了这幅图像,然后预览了这幅图像的油画效果,这时候我们点取消预览,结果连平移效果也没有了。有什么方法可以解决这个问题吗?有的,理论可行,我没实现,方法如下:

        给每一个操作编号,使用栈存储用户输入的操作,预览与确定即入栈,取消预览与取消即出栈,我们每次更新视图的时候,将图像与后台交换,并从栈底按照操作编号操作到栈顶即可。

        4、存储并读取对话框中的数据

        

#include <fstream>
//存储
CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, _T("*.txt|All Files (*.*) |*.*|"), this);
fileDlg.DoModal();
CString strFilePath = fileDlg.GetPathName();		//文件路径
ofstream fout;
fout.open(strFilePath, ios::out);
if (!fout.is_open())
{
	AfxMessageBox(L"无法打开文件");
	return;
}
char buff[1024] = { 0 };
fout << 数据<<" ";

fout.close();

//读取
CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, _T("*.txt|All Files (*.*) |*.*|"), this);
fileDlg.DoModal();
CString strFilePath = fileDlg.GetPathName();		//文件路径
ifstream fin;
fin.open(strFilePath, ios::in);
if (!fin.is_open())
{
	AfxMessageBox(L"无法打开文件");
	return;
}
fin >> 数据;

fin.close();
UpdateData(FALSE);

MFC过程中遇到的问题

        暂时就遇到一个比较严重的问题:当你在CDlg类中同时包含Doc类与function类时,由于View类中有function类,会重定义。修改所有function类中的函数,添加static即可。

        其他的内存读取冲突问题可能是空指针

猜你喜欢

转载自blog.csdn.net/qq_51135645/article/details/125048903
今日推荐