一.准备工作
1.根据老师的运行实例,在创建MFC程序时使用的高级视图功能
1)由于老师平时上课时,使用的都是经典的基础视图,而课程设计文档中的视图是高级视图,对比运行的视图,我尝试着把程序的视图设为和课程设计文档里面一样的
2.参考老师发的代码,分析程序所需要的类和各个文件之间的关系
1)设计类
自己创建一个WShape(以W开头便于和MFC的类区分)图元类,派生图元类单独创建.h和.cpp文件,便于管理,虽然切换有些麻烦,但后期修改很方面,我没有像示例程序里面那样把所有的类都放在shape文件里面
基类是WShape
数据成员:
1)原点坐标(鼠标点击的点)
*2)旋转角度(单独为文本类设计,其他图形不使用)
3)和线以及填充有关的属性字段
成员函数
1)有参数的构造函数
2)绘制图元的函数
3)判断鼠标点击的位置是否在图形内部的函数,是否打开属性设计窗口
4)序列化数据的函数
5)重新设置图元的属性的函数
注意:要在每个派生类的.h头文件里面声明该类型支持序列化,并在.cpp源文件里面指定序列化的版本
例如:矩形类
DECLARE_SERIAL(WRectangle)//声明类WRectangle是支持序列化
IMPLEMENT_SERIAL(WRectangle, CObject, 1)//实现类WSquare的序列化,指定版本为1
派生类为(http://www.jizhuomi.com/software/244.html)CDC博客
1)正方形类Square,矩形类Rectangle,对于正方形类,除了基类WShape之外的数据成员外,有一个独立的width数据,而矩形类只是在此基础上加上一个height数据,两者的绘图原理是一样的,CDC类的pDC对象中有绘制矩形的函数
2)圆类Circle,椭圆类Ellipse,椭圆类既有长半轴,也有短半轴,两者的绘图原理是一样的,使用CDC类的pDC对象中绘制椭圆的函数
3)三角类Triangle,使用CDC类的pDC对象中绘制多边形的Polygon函数(图片),(https://msdn.microsoft.com/en-us/library/fxhhde73.aspx#cdc__polyline)参看微软官网上的函数用法,要把多边形的顶点放在一个坐标点数组里面,并指定多边形的顶点的个数
4)文本Text类,采取自定义的字体来实现,旋转角度是自定义字体的一个参数
解决方法
使用CreateFontIndirect(const LOGFONT* lpLogFont)函数创建斜率字体,参数lpLogFont->lfEscapement是字体的角度
网址(http://bbs.csdn.net/topics/20746)
LOGFONT logfont;//创建自己的字体
lstrcpy((LPSTR)logfont.lfFaceName,(LPSTR)"楷体_GB2312");
logfont.lfWeight=700;
logfont.lfWidth=40;
logfont.lfHeight=70;
logfont.lfEscapement=angle;
//这个参数就是用来控制角度,这里是正常显示
logfont.lfUnderline=FALSE;
logfont.lfItalic=FALSE;
logfont.lfStrikeOut=FALSE;
logfont.lfCharSet=GB2312_CHARSET;
hFont=CreateFontIndirect(&logfont);
……
下面就是使用该字体了,
hOldFont=(HFONT*)dc.SelectObject(hFont);
2)设计对话框
使用MFC的控件,来搭建对话框的界面,适当修改控件的ID值
1)Combobox下拉框控件
所用的函数
GetCurSel()来获取鼠标焦点的值的序号,注意下拉框的sort属性,默认是true,会按照自然排序,这样和你添加内容的顺序就会不一样,可以改为false
AddString()来向下拉框控件里面添加内容,也可以到VS2015的属性框里面的data属性里面添加,Ctrl+Enter换行
2)Listbox列表框控件
所用的函数
GetCurSel()来获取鼠标焦点的值的序号
AddString()来向列表框控件里面添加内容
3)MFC ColorButton颜色选取控件
所用的函数
Getcolor()获取当前选择的颜色,类型是COLORREF,以RGB的形式储存
4)Static Text静态文本控件
用来制作前台界面
5)Edit Control编辑文本控件
用来让用户输入参数值
二.开始MFC编程
根据课程设计文档里要实现的功能来一步步编程
1.和对话框相关的功能
使用控件的消息映射以及相关的成员函数来实现
1)初始化对话框(初始化函数)
向下拉框以及列表框里面添加内容
BOOL WAttribute::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: 在此添加额外的初始化
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO);
pComboBox->AddString(L"正方形");
pComboBox->AddString(L"矩形");
pComboBox->AddString(L"圆形");
pComboBox->AddString(L"椭圆形");
pComboBox->AddString(L"正三角形");
pComboBox->AddString(L"文本");
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LINETYPE);
pListBox->AddString(L"SOLID");
pListBox->AddString(L"DASH");
pListBox->AddString(L"DASHDOT");
pListBox = (CListBox*)GetDlgItem(IDC_FILLTYPE);
pListBox->AddString(L"SOLID");
pListBox->AddString(L"BDIALOGAL");
pListBox->AddString(L"CROSS");
m_LineColor.EnableAutomaticButton(_T("默认值"), RGB(0, 0, 0));
m_LineColor.EnableOtherButton(_T("其余颜色"));
m_LineColor.SetColor((COLORREF)-1);
m_LineColor.SetColumnsNumber(10);
m_FillColor.EnableAutomaticButton(_T("默认值"), RGB(0, 0, 0));
m_FillColor.EnableOtherButton(_T("其余颜色"));
m_FillColor.SetColor((COLORREF)-1);
m_FillColor.SetColumnsNumber(10);
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
2)选择图形类型(控件消息)
当用户选择的不是文本类型的对话框,就把和文本框有关的控件隐藏,得到下拉框所选序号,来判断是否要隐藏
void WAttribute::OnCbnSelchangeCombo()
{
// TODO: 在此添加控件通知处理程序代码
int nIndex = m_ComboxType.GetCurSel();
if (nIndex != 2)
{
CStatic* pStatic1 = (CStatic*)GetDlgItem(IDC_TEXT);
pStatic1->ShowWindow(SW_HIDE);
CEdit* pEdit1 = (CEdit*)GetDlgItem(IDC_STEXTEDIT);
pEdit1->ShowWindow(SW_HIDE);
pStatic1 = (CStatic*)GetDlgItem(IDC_TEXTANGLE);
pStatic1->ShowWindow(SW_HIDE);
pEdit1 = (CEdit*)GetDlgItem(IDC_EDITANGLE);
pEdit1->ShowWindow(SW_HIDE);
}
else
{
CStatic* pStatic1 = (CStatic*)GetDlgItem(IDC_TEXT);
pStatic1->ShowWindow(SW_SHOW);
CEdit* pEdit1 = (CEdit*)GetDlgItem(IDC_STEXTEDIT);
pEdit1->ShowWindow(SW_SHOW);
CStatic* pStatic2 = (CStatic*)GetDlgItem(IDC_TEXTANGLE);
pStatic2->ShowWindow(SW_SHOW);
CEdit* pEdit2 = (CEdit*)GetDlgItem(IDC_EDITANGLE);
pEdit2->ShowWindow(SW_SHOW);
}
}
3)选择颜色(控件消息)
获取边框线型的颜色以及填充颜色,因为我这里定义的是颜色控件变量是control的变量类型,所以还要使用该类型的函数才能获取颜色的color值,其实如果仅是为了获取颜色值,可以直接给该控件绑定一个value的变量类型,我在这里还对颜色控件做了一些定制(这个是从网上学到的,博客地址http://blog.csdn.net/akof1314/article/details/5947518)详细代码见初始化对话框函数
void WAttribute::OnBnClickedLinecolor()
{
// TODO: 在此添加控件通知处理程序代码
m_nLineColor = m_LineColor.GetColor();
if (m_nLineColor == -1)
{
m_nLineColor = m_LineColor.GetAutomaticColor();
}
}
void WAttribute::OnBnClickedFillcolor()
{
// TODO: 在此添加控件通知处理程序代码
m_nFillColor = m_FillColor.GetColor();
if (m_nFillColor == -1)
{
m_nFillColor = m_FillColor.GetAutomaticColor();
}
}
4)ok控件(控件消息)
把一些想在对话框点击确定后想保存的值:下拉框的选项,列表框的选项保存下来,供view文件使用,采取在对话框类里面自定义变量来实现
void WAttribute::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
m_nComboxIndex = m_ComboxType.GetCurSel();//获取下拉框的索引
m_nLineTypeIndex = m_LineType.GetCurSel();//获取线型列表框选择内容
m_nFilltypeIndex = m_FillType.GetCurSel();//获取填充类型列表框选择内容
CDialogEx::OnOK();
}
2.Doc数据文件
1)使用一个动态数组来存放视图中绘制的图形的数组,则每个绘制图形就是数组中的一个元素
(动态数组的实现原理:添加新的元素后,就把当前所有的元素copy以及新增的元素,再开辟一块新的内存空间来存放新的数组,实际上也是通过静态的普通数组来实现的)
CObArray m_Elements;
2)析构函数
释放每个图形的所占用的资源,以及这个动态数组
CWDemoDoc::~CWDemoDoc()
{
//析构 父类指针指向的图形资源 函数
int i;
WShape* p;
if (m_Elements.GetCount() > 0)
{
for (i = 0; i < m_Elements.GetCount(); i++)
{
p = (WShape*)m_Elements[i];
delete p;
}
}
m_Elements.RemoveAll();//释放数组
}
3)序列化函数
每个数组元素调用自己的序列化函数,面向对象的封装思想
void CWDemoDoc::Serialize(CArchive& ar)
{
m_Elements.Serialize(ar);
}
3.View文件
1)鼠标左键加上Ctrl键弹出对话框功能
1)设备坐标转化为逻辑坐标(参考博客:http://blog.csdn.net/xuxiaofei77/article/details/5734257)
2)判断Ctrl键是否按下,来决定是否创建一个对话框
3)获取鼠标点击的位置,要放在对话框对象之后,对话框弹出之前
4)弹出对话框后根据用户选择的图形类型来创建相对应的图形及文本,我这里是通过比较用户选择的序号,用switch语句实现的
5)每次在创建图形后要把它加入到动态数组中,最后刷新界面窗口,调用View文件的OnDraw函数绘制对应的图形
2)只按下鼠标左键,而没有按下Ctrl键,则需要判断鼠标当前的落点又没在某个图形内
1)把当前图形的相关参数传入到对话框中,供用户修改图形的相关的参数
2)当用户按下OK键后,再把对话框界面的值传入到当前图形中,重新设置图元的属性的函数,这样刷新窗口后就改变当前图形
void CWDemoView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CWDemoDoc* pDoc = GetDocument();
CClientDC dc(this);
CPoint pntLogical = point;
OnPrepareDC(&dc);
dc.DPtoLP(&pntLogical);//DP->LP进行转换
if ((nFlags&MK_CONTROL) == MK_CONTROL)//Ctrl键按下
{
WAttribute dlg;
//记录鼠标按下的位置
dlg.m_ShapeX = pntLogical.x;
dlg.m_ShapeY = pntLogical.y;
//焦点是否在确定键上,弹出对话框
if (dlg.DoModal() == IDOK)
{
//获取用户修改位置的坐标
pntLogical.x = dlg.m_ShapeX;
pntLogical.y = dlg.m_ShapeY;
//获取用户输入的宽度和高度
int width = dlg.m_ShapeWidth;
int height = dlg.m_ShapeHeight;
//根据对话框用户选择的下拉框的选项索引绘制对应的图形
switch (dlg.m_nComboxIndex)
{
case 0:
{
WRectangle* p = NULL;
p = new WRectangle(pntLogical.x, pntLogical.y, width, height);
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->BoderColor = dlg.m_nLineColor;
p->FillType = dlg.m_nFilltypeIndex;
p->FillColor = dlg.m_nFillColor;
pDoc->m_Elements.Add(p);
break;
}
case 1:
{
WEllipse * p;
p = new WEllipse(pntLogical.x, pntLogical.y, width, height);
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->BoderColor = dlg.m_nLineColor;
p->FillType = dlg.m_nFilltypeIndex;
p->FillColor = dlg.m_nFillColor;
pDoc->m_Elements.Add(p);
break;
}
case 2:
{
WText *p = NULL;
CString content = dlg.m_TextContent;
p = new WText(pntLogical.x, pntLogical.y, content);
p->Angle = dlg.m_Angle;
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->BoderColor = dlg.m_nLineColor;
p->FillType = dlg.m_nFilltypeIndex;
p->FillColor = dlg.m_nFillColor;
pDoc->m_Elements.Add(p);
break;
}
case 3:
{
WCircle *p = NULL;
p = new WCircle(pntLogical.x, pntLogical.y, width);
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->BoderColor = dlg.m_nLineColor;
p->FillType = dlg.m_nFilltypeIndex;
p->FillColor = dlg.m_nFillColor;
pDoc->m_Elements.Add(p);
break;
}
case 4:
{
WSquare* p = NULL;
p = new WSquare(pntLogical.x, pntLogical.y, width);
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->BoderColor = dlg.m_nLineColor;
p->FillType = dlg.m_nFilltypeIndex;
p->FillColor = dlg.m_nFillColor;
pDoc->m_Elements.Add(p);
break;
}
case 5:
{
WTriangle* p = NULL;
p = new WTriangle(pntLogical.x, pntLogical.y, width);
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->BoderColor = dlg.m_nLineColor;
p->FillType = dlg.m_nFilltypeIndex;
p->FillColor = dlg.m_nFillColor;
pDoc->m_Elements.Add(p);
break;
}
default:
MessageBox(L"请您选择图形类型!");
break;
}
Invalidate();//刷新窗口
}
}
else
{
//未按下Ctrl键时左击,则逐个比较,看是否命中图元
int i;
WShape* p;
for (i = 0; i < pDoc->m_Elements.GetCount(); i++)
{
p = (WShape*)pDoc->m_Elements[i];
if (p->IsMatched(pntLogical))
{
//修改图元属性,从图元属性值里面取值赋值给对话框的变量
WAttribute dlg;
dlg.m_ShapeX = p->OrgX;
dlg.m_ShapeY = p->OrgY;
if (dlg.DoModal() == IDOK)
{
//利用改了以后对话框中图元的属性更新到文档图元数组的对象中
p->OrgX = dlg.m_ShapeX;
p->OrgY = dlg.m_ShapeY;
p->BoderColor = dlg.m_LineColor.GetColor();
p->BoderWidth = dlg.m_LineWidth;
p->BoderType = dlg.m_nLineTypeIndex;
p->FillColor = dlg.m_FillColor.GetColor();
p->FillType = dlg.m_nFilltypeIndex;
p->SetAttribute(p->OrgX, p->OrgY, p->BoderColor, p->BoderType, p->BoderWidth, p->FillColor, p->FillType);
}
Invalidate();//刷新窗口
}
}
}
CScrollView::OnLButtonDown(nFlags, point);
}
3)双击鼠标右键删除当前图元
这个消息映射需要在使用时,需要注意把MFC框架自带的鼠标右键消息禁用,不然会有冲突
1)还是先把设备坐标转化为逻辑坐标
2)用父类指针指向子类对象,调用子类对象的IsMatched函数,循环查找用户选中的是动态数组中的哪个图形,找到后使用动态数组对象自带的删除元素函数删除
3)刷新窗口,图形消失
void CWDemoView::OnRButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CWDemoDoc* pDoc = GetDocument();
int i;
WShape* p;
CClientDC dc(this);
CPoint pntLogical = point;
OnPrepareDC(&dc);
dc.DPtoLP(&pntLogical);//DP->LP进行转换
for (i = 0; i < pDoc->m_Elements.GetCount(); i++)
{
p = (WShape*)pDoc->m_Elements[i];
if (p->IsMatched(pntLogical))
{
AfxMessageBox(L"你是否要删除?");
pDoc->m_Elements.RemoveAt(i);
}
}
Invalidate();//刷新窗口
CScrollView::OnRButtonDblClk(nFlags, point);
}