MFC课程论文
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《MFC编程及应用》课程设计报告
题目:直线和曲线的绘制与计算器的编制
时间: 2011年6月8日
一.直线和曲线的绘制程序
1.程序的功能简介
本程序是单文档程序,程序的功能是按下鼠标左键可以画红色的直线,按下
鼠标左键并同时按下SHIFT键可以画任意黑色的曲线。
可以利用菜单对已存储的直线进行删除,在程序退出时,会提示对文档进行保存(只保存绘制的直线,曲线不予保存),可以将之前保存的图形重新绘制出来。
2.程序的设计思路和实现方法
(1)对直线和曲线的绘制是在视图类中完成的
在视图类里定义了四个私有成员变量:
int m_nDraw;//显示画线状态,1表示画线,0表示不画线,在视图类的构
//造函数里初始化赋值为0
HCURSOR m_hCursor;//光标句柄,改变画线时的光标形状
CPoint m_ posOld;//当前鼠标位置(画线终点坐标)
CPoint m_posOrigin;//鼠标起始位置(画线起点坐标)
添加鼠标左键按下的消息响应函数,代码为
void CNie_homeworkView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_posOld=point;//画线的起点和终点位置都
m_posOrigin=point;//初始化为鼠标按下的位置
this->SetCapture();//连续跟踪鼠标消息
m_nDraw=1;//开始画线
m_hCursor=AfxGetApp()->LoadStandardCursor(IDC_CROSS);
//设置画线的光标为十字形
RECT theRect;
GetClientRect(&theRect);//得到视图窗口的客户区大小
ClientToScreen(&theRect);//进行坐标转换
ClipCursor(&theRect);//只在客户区显示光标
CView::OnLButtonDown(nFlags, point);
}
添加鼠标移动消息响应函数,代码为:
void CNie_homeworkView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
SetCursor(m_hCursor);//使用十字光标画线
CClientDC dc(this);//产生当前窗口的设备对象
if(m_nDraw)//画线状态为1时,画线
{
CPen NewPen,*pOldPen;
NewPen.CreatePen(PS_DASH,1,RGB(255,0,0));//产生一个像素宽的红色虚线画笔
pOldPen=dc.SelectObject(&NewPen);//将当前画笔设置为刚刚产生的画笔属性
if(nFlags & MK_SHIFT)//按下SHIFT键时,画任意曲线
{
dc.MoveTo(this->m_posOld);
dc.LineTo(point);//从终点继续画线
this->m_posOld=point;//将鼠标位置赋给终点
}
else //画直线
{
dc.SetROP2(R2_WHITE);//设置画笔颜色为白色
dc.MoveTo(m_posOrigin);
dc.LineTo(m_posOld);//从起点到终点画直线
dc.SetROP2(R2_COPYPEN);//使用画笔颜色
dc.MoveTo(m_posOrigin);
dc.LineTo(point);//从起点到鼠标位置画直线
m_posOld=point;//把鼠标位置赋给终点
}
dc.SelectObject(pOldPen);
UpdateWindow();//通知视图窗口更新
}
CView::OnMouseMove(nFlags, point);
}
在鼠标左键放开时,完成直线或曲线的绘制。
添加鼠标左键放开的消息响应函数:void CNie_homeworkView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
SetCursor(m_hCursor);//使用十字光标画线
CPen *pOldPen;
CPen NewPen(PS_SOLID,1,RGB(255,0,0));
pOldPen=dc.SelectObject(&NewPen);
dc.SetROP2(R2_NOT);//设置画笔颜色为与画板相反的颜色
if(nFlags & MK_SHIFT)//按下SHIFT键时,画任意曲线
{
dc.MoveTo(this->m_posOld);
dc.LineTo(point);//从终点继续画线
this->m_posOld=point;//将鼠标位置赋给终点
}
else
{
dc.MoveTo(m_posOrigin);
dc.LineTo(m_posOld);//从起点到终点画直线
dc.SetROP2(R2_COPYPEN);//使用画笔颜色
dc.MoveTo(m_posOrigin);
dc.LineTo(point);//从起点到鼠标位置画直线
m_posOld=point;//把鼠标位置赋给终点
CNie_homeworkDoc *pDoc=GetDocument();
pDoc->AddLine(m_posOrigin.x,m_posOrigin.y,point.x,point.y);
//将直线的信息存储到数组中}
this->m_nDraw =0;//画线完成,画线状态重设为0
::ReleaseCapture ();//释放鼠标
ClipCursor(NULL);//允许鼠标点击
this->m_hCursor=AfxGetApp()->LoadStandardCursor(IDC_ARROW);
//重设光标形状为箭头CView::OnLButtonUp(nFlags, point);
}
(2)对直线的存储与删除是在文档类中实现的
在工程中添加了一个新类CLine,定义了四个类的私有成员,分别存放的是直线的起始点的x、y坐标。
在文档类中添加了CObArray类的一个对象m_posArray作为受保护的成员,里面存放的是指向Cline类的对象的指针。
为了能够对数据进行存储,我们对CLine类和文档类进行了串行化处理。
在CLine类中进行了申明:
protected:DECLARE_SERIAL(CLine)//申明对数据的串行化处理
在CLine类的构造函数之前也进行了申明:
IMPLEMENT_SERIAL(CLine,CObject,1);
在文档类中进行了申明:
protected:DECLARE_DYNCREATE(CNie_homeworkDoc)
在视图类的构造函数之前进行了申明:
IMPLEMENT_DYNCREATE(CNie_homeworkView, CView)
在CLine类和文档类中分别对数据串行化函数进行了重载。
代码如下:
void CLine::Serialize(CArchive &ar){//函数重载,进行数据的串行化处理if(ar.IsStoring())
ar<<m_nStartX<<m_nStartY<<m_nEndX<<m_nEndY;
else
ar>>m_nStartX>>m_nStartY>>m_nEndX>>m_nEndY;
}
void CNie_homeworkDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
this->m_posArray.Serialize(ar);
}
else
{
// TODO: add loading code here
m_posArray.Serialize(ar);
}
}
为了能够利用菜单进行直线的删除操作,在文档类中定义了下列函数:public:
void AddLine(int,int,int,int);//成员函数,将CLine类的对象添加进入数组内CLine *GetLineAt(int);//成员函数,获取数组内的一个CLine对象
int GetNumberOfAllLines(void);//成员函数,获取数组中所有CLine对象的个数void DeleteContent(void);//删除数组中的CLine对象
对菜单的撤销命令,利用类向导连接了以下两个命令函数:
void CNie_homeworkDoc::OnEditUndo()
{
// TODO: Add your command handler code here
int nIndex=m_posArray.GetUpperBound();//获得数组中直线的条数
if(nIndex>=0)//数组中有直线时
{
delete m_posArray.GetAt(nIndex);/得到最后一条直线
m_posArray.RemoveAt(nIndex);//删除最后一条直线
}
UpdateAllViews(NULL);//更新视窗
SetModifiedFlag();
}
void CNie_homeworkDoc::OnUpdateEditUndo(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(m_posArray.GetSize());//数组中没有直线时,菜单项中撤销命令变为不可操作
}
此外,在程序退出时,删除所有存储的直线,这只需在文档类的析构函数中调用DeleteContent();//程序结束时,删除动态数组占用的空间
为了能够打开之前保存的图形,在CLine类里定义了如下成员函数
void CLine::DrawLine(CDC *pDC) //CLine类的成员函数,画直线
{
CPen NewPen(PS_SOLID,1,RGB(255,0,0));
CPen *pOldPen=pDC->SelectObject(&NewPen);//产生画笔
pDC->SetROP2(R2_COPYPEN);//使用画笔颜色
pDC->MoveTo(m_nStartX,m_nStartY);
pDC->LineTo(m_nEndX,m_nEndY);//从起点到终点画直线
pDC->SelectObject(pOldPen);
}
3.程序的编制过程中遇到的主要问题和解决办法
程序在编制过程中遇到的最大的一个问题是数据的串行化处理时,我只在CLine类中进行了申明protected:DECLARE_SERIAL(CLine)
而忘记在CLine类的构造函数前添加申明
IMPLEMENT_SERIAL(CLine,CObject,1);
导致在编译过程中程序出错,而自己还不知道错在哪里。
请教老师之后,我也查了相关的书籍(参考文献[1][2]),看到了书中的例子,对照着改了过来。
4.程序还可加以改进的地方
程序还可以改进的地方有不少。
第一个是画笔颜色和形状不可选,也没有设置橡皮擦,另一个就是对绘制的曲线不能进行存储。
限于时间有限,并没有往这几个方面多加考虑。
二.计算器的编制程序
1.程序的功能简介
本程序是有模式对话框程序,嵌入在直线和曲线的绘制程序内。
程序的功能是可以利用该计算器进行一般的科学运算,包括加、减、乘、除、求倒数、求相反数、向下取整;求正弦值、余弦值、正切值、自然对数值、常数对数值、自然底数幂;求平方、立方,开平方、开立方,阶乘;对错误输入的更改或清除,对运算结果的输出显示以及更改或清除。
2.程序的设计思路和实现方法
(1)参考了文献[1]中的第五章
首先在工程中建立一个新的类:class CCalculatorDlg : public CDialog
类中定义一个成员变量protected: HICON m_hIcon;//对话框的图标
在CalculatorDlg.cpp文件中添加头文件"math.h"和下列全局变量
double result=0;//初值赋为0,表示计算结果
int count=0;//初值赋为0,指示数据按钮的输入状态,1表示有数据按钮按下,//0表示无数据按钮按下
char c=' ';//初值赋为空格,指示二元运算符,即c='+','-','*','/'
接着创建一个新的对话框,添加了一个编辑控制框,两个成组框,三十三个按钮。
得到如下形式的一个对话框:
将这个对话框的ID值设为IDD_Calculator,在菜单中添加一项,命名为Calculator,对应的ID值也设为IDD_Calculator。
给编辑控制框添加一个成员CEdit m_edit;
(2)下面给三十三个按钮连接代码
对于数字按钮0~9,连接的代码类似。
以0为例:
void CCalculatorDlg::OnBUTTONnum0()
{
// TODO: Add your control notification handler code here
if(count==0)
m_edit.SetWindowText("0");//若第一次按下0,则在窗口中显示0 else
{
char b[40];
m_edit.GetWindowText(b,20);
int len=strlen(b);
b[len]='0';
b[len+1]='\0'; //将0作为字符加入字符数组的末尾
m_edit.SetWindowText(b);
}
count=1;//指示已有数据按钮按下
}
在数据输入时,对小数点的处理比较麻烦,我的处理方法是:
void CCalculatorDlg::OnBUTTONdot()
{
// TODO: Add your control notification handler code here
char b[40];
m_edit.GetWindowText(b,20);
int len=strlen(b);
if(len==0||(len==1&&b[0]=='-'))//如果之前无数据输入或输入
//的是一个单个的负号,
return; //则对该按钮不予处理
else
{
int i;
for(i=0;i<len;i++)
{
if(b[i]=='.')//如果之前的数据中已有小数点,
break;//对该按钮不予处理
}
if(i==len)
{
b[len]='.';
b[len+1]='\0';//之前的数据中无小数点,则将小数点加到数据的最后}
m_edit.SetWindowText(b);
}
}
二元运算的处理也比较麻烦,程序代码如下(只以除法为例,其余加、减、乘和等号类似):
void CCalculatorDlg::OnBUTTONdevide()
{
// TODO: Add your control notification handler code here
char b[40];
m_edit.GetWindowText(b,20);//获得当前的参与运算的数据
if(strlen(b)==0||(strlen(b)==1&&b[0]=='-'))//若之前的数据没有或者return; //只是一个负号,则该按钮不予处理else if(c=='/'&&atof(b)==0)
{
m_edit.SetSel(0,-1);
m_edit.ReplaceSel("0不能作除数!");//若为除法运算且除数为0,
//则给出提示
}
else
{
switch(c)
{
case '+':
result+=atof(b);//若之前按下了加法按钮,
//则进行加法运算,下面均类似
break;
case '-':
result-=atof(b);
break;
case '*':
result*=atof(b);
break;
case '/':
result/=atof(b);
break;
default:
result=atof(b);//若之前没有二元运算符按下,则结果就是当前数据}
sprintf(b,"%f",result);
m_edit.SetWindowText(b);
}
c='/';//除法按钮被按下,c值变为'/'
count=0;//一次运算结束,进行另外一次数据输入
}
对于取相反数的运算,程序代码为:
void CCalculatorDlg::OnBUTTONsign()
{
// TODO: Add your control notification handler code here
char b[40];
m_edit.GetWindowText(b,20);//获得之前的数据
int len=strlen(b);//得到数据作为字符串时的长度
if(len==0)
{
b[0]='-';
b[1]='\0';
m_edit.SetWindowText(b); //当之前的数据为空时,将当前数据设为负号
count=1;//已有数据按钮被按下
}
else if(len==1&&b[0]=='-')
{
m_edit.SetSel(0,-1);
m_edit.ReplaceSel("");//之前数据为负号时,将当前数据取空
count=0;//进行下一次的数据输入
}
else if(atof(b)>0)
{
for(int i=len;i>0;i--)
b[i]=b[i-1];
b[0]='-';//之前的数据为正时,在数据最前面加负号
b[len+1]='\0';
m_edit.SetWindowText(b);
}
else if(atof(b)<0)
{
for(int i=0;i<len;i++)
b[i]=b[i+1];//之前的数据为负时,去掉负号
m_edit.SetWindowText(b);
}
else
return;
}
对于其它的运算,由于都是一元运算,所以程序代码也都类似,下面只举一例,开平方运算:
void CCalculatorDlg::OnBUTTONsqrt()
{
// TODO: Add your control notification handler code here
char b[40];
m_edit.GetWindowText(b,20);
double n=atof(b);//将之前的数据转化为double型数据
if(n<0||b[0]=='\0'||(b[0]=='-'&&b[1]=='\0'))
{
m_edit.SetSel(0,-1);
m_edit.ReplaceSel("对不起,本计算器不支持负数开平方运算!");//之前的
//数据为负数或者只是一个负号的时候,给出提示}
else
{
n=sqrt(n);
sprintf(b,"%f",n);
m_edit.SetWindowText(b);
}
count=0;//一次运算结束,进行下一次的数据输入
}
(3)对话框的显示
参考文献[1]第6章第3节,在视图类中添加了菜单项Calculator对应的菜单命令:
void CNie_homeworkView::OnCalculator()
{
// TODO: Add your command handler code here
CCalculatorDlg calculatorDlg;
calculatorDlg.DoModal();
}
这里必须要指出的是,不能忘记添加头文件"CalculatorDlg.h"(开始的时候我就忘记了添加这个头文件,所以在进行点运算的时候发现没有出来这个类的成员,这时才发现自己没有添加头文件)。
3.程序的编制过程中遇到的主要问题和解决办法
开始时我为框架类设置自定义的图标,没能成功。
参考了文献[1]中第7章的内容,对照着书中的例子我自己试着装载新图标,然后就成功了。
不过,书中给的例子是从程序外部资源装载动态图标,我是直接绘制了一个新图标,赋上ID 值,在MainFrm.cpp文件内添加了下列语句:
HICON hNew,hOld;
hNew=AfxGetApp()->LoadIcon(IDI_ICON1);//装载新图标
hOld=(HICON)GetClassLong(m_hWnd,GCL_HICON);//原来在标题栏上的图标
if(hNew!=hOld)
{
DestroyIcon(hOld);//销毁远标题栏图标,并释放所占用的空间
SetClassLong(m_hWnd,GCL_HICON,(long)hNew);//设置新的图标
RedrawWindow(NULL,NULL,RDW_FRAME);//非客户区重画}
其实,我还在CCalculator类里添加了成员函数
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
以及它们相应的函数代码,编译运行也都没有问题。
但是就是无法显示我自己为对话框设计的图标。
目前,还没有找到解决办法。
4.程序还可加以改进的地方
可以改进的地方一个就是可以设计自己的图标,让程序看起来更具有属于自己的特性。
另外,我觉得应该也可以让计算器的使用者自行决定运算时的精度(本程序中默认为小数点后6位,运算的操作数及结果不超过40位);还应该让使用者能够决定运算时使用弧度制还是角度制;应该加入圆括号按钮,可以改变运算的顺序等等。
三.MFC课程学习的体会以及个人的一些建议
通过对这门课程的学习,使我对windows运行机制有了更深刻的了解,同时也使我个人的程序编制能力(无论是VC语言编程还是其它程序语言编程)得到了很大的提高,对于一些其它的编程语言的理解也更容易了。
我觉得这门课程对一个人最大的锻炼就是让人能够适应并且学会站在机器的角度考虑问题,考虑程序。
相信经过了这样的锻炼,对于今后的计算机的学习会轻松不少。
当然,不得不说的就是,这样坚持锻炼下来的结果固然是让人获益匪浅,但是锻炼的过程却是比较痛苦的,尤其是刚开始的那几节课。
由于没有教材,光看老师的PPT,总有些地方不是很明白,而在图书馆里借的MFC相关的书籍也都不很适应(在课程快要结束的时候我才在学校外面的旧书店里看到老师您编的《MFC编程及应用》(第二版),买下来看过之后,以前课上讲的一些东西就更清楚易懂了)。
鉴于我自己的体会和观察,我想向这门课程提两个建议。
第一就是希望老师可以注册一个公共邮箱,公布给班上的同学。
老师可以往邮箱里上传课件,PPT,作业,一些源程序代码等,然后同学们就各自去登陆这个公共邮箱进行下载就好,或者也可以把这个公共邮箱当作一个讨论区,大家有问题就往里面发邮件提问。
我想这样的话对教学也会是个很大的方便。
第二就是希望老师可以为选了MFC这门课程并且想认真学的同学指定教材,因为从我个人的体验来说,有了适合的教材之后再配上老师的讲解,这样学起来更快更轻松,掌握得也更牢固一些。
而且,老师讲解起来应该也会更轻松些。
最后,谢谢老师您这一个学期来的教导。
参考文献
[1] 杨均匀. MFC编程及应用(第二版)[M]. 南京:南京理工大学印刷厂,2007.
[2] 刘晓华等. 精通MFC[M]. 北京:电子工业出版社,2003.
[3] 张正军,许春根,张军. 计算机应用开发技术[M]. 北京:科学出版社,2010.
[4] 任哲等. MFC Windows应用程序设计[M]. 北京:清华大学出版社,2004.。