基于C++的简单的MFC绘图程序

一.准备工作
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);
        }
点赞