本文将讲解如何通过MFC制作一个简易的绘图软件
首先新建一个MFC的工程,进入之后按F5调试,可以看到现在的界面
停止调试,开始看我们的程序,进入资源视图–>myxxxxxx.rc–>Menu->IDR_MAINFRAME
新键入按钮“画图”及其子按钮“线段”,并修改其ID为ID_Line
进入资源视图–>myxxxxxx.rc–>toolbar->IDR_MAINFRAME_256
新键入图形按钮“线段”,并修改其ID为ID_Line
此时调试会发现“线段”这个按钮是灰的,所以我们需要对他添加命令
点击项目–>类向导–>选择ID_Line,记得类名那儿选择xxxxxxxview
然后点进xxxxxxxview会找到一个对应的函数,但内容为空,所以我们可以定义int型的标志type,在函数中写入type=1,表示点击“线段”按钮时type=1,可以作为之后导向画线段函数的标志。
void CMy1900402213View::OnLine()
{
type = 1;
}
void CMy1900402213View::OnSquare()
{
type = 2;
}
void CMy1900402213View::OnCircle()
{
type = 3;
}
要制作绘出圆形矩形甚至画笔颜色以及形状都可以如法炮制
最终调试时的软件界面如下
以画直线为例
首先我们建立一个CLine的类,里面要写一个放置起点和终点的函数,和最后画图的函数
**CLine.h**
#pragma once
class CLine
{
public:
CLine();
void Set_start_point(CPoint p);
void Set_end_point(CPoint p);
private:
CPoint Line_start_point;
CPoint Line_end_point;
public:
void Draw(CDC* pDC);
};
**CLine.cpp**
#include "pch.h"
#include "CLine.h"
CLine::CLine()
{
}
void CLine::Set_start_point(CPoint p)
{
Line_start_point = p;
}
void CLine::Set_end_point(CPoint p)
{
Line_end_point = p;
}
void CLine::Draw(CDC* pDC)
{
pDC->MoveTo(Line_start_point);
pDC->LineTo(Line_end_point);
}
然后在xxxxxxview文件中添加鼠标点击,鼠标移动,鼠标抬起,三个消息处理函数
然后可以在xxxxxxxxview.cpp找到对应的三个空的消息处理函数,我们需要填充他,以直线为例,我们可以在鼠标点击时设置起始点,鼠标移动时一直设置终点,然后从起始点到终点画出一条直线
void CMy1900402213View::OnLButtonDown(UINT nFlags, CPoint point)
{
switch (type)
{
case 1:
{
m_pline = new CLine;
m_pline->Set_start_point(point);
}break;
}
start = true;
CView::OnLButtonDown(nFlags, point);
}
void CMy1900402213View::OnMouseMove(UINT nFlags, CPoint point)
{
if (start == true)
{
CDC* pDC = GetDC();
if (type == 1)
{
m_pline->Set_end_point(point);
m_pline->Draw(pDC);
}
ReleaseDC(pDC);
}
}
void CMy1900402213View::OnLButtonUp(UINT nFlags, CPoint point)
{
start=false;
}
此时调试,画直线时会发现有重影,这是因为鼠标每次移动都新设立了一个终点,也就是每次鼠标的移动都会新画一条线,但只有最后一条线才是我们想要的,可之前画出的线也还留在画布上,这个时候我们需要在画线前用一支反色笔(使用其他颜色的时候,这个反色笔其实是背景色,但如果这条线如果是白色的话,反色笔的颜色将会是黑色)把上一条线覆盖掉,即画出a线,接着鼠标移动画出了b线的同时会用一支白色的笔把a线覆盖掉,最后画布上只剩下b这一条线。
void CMy1900402213View::OnMouseMove(UINT nFlags, CPoint point)
{
if (start == true)
{
CDC* pDC = GetDC();
if (type == 1)
{
pDC->SetROP2(R2_NOTXORPEN);//调用反色笔
m_pline->Draw(pDC);
m_pline->Set_end_point(point);
m_pline->Draw(pDC);
}
ReleaseDC(pDC);
}
}
那么问题又来了,在vs2019的环境下,画好a线时会出现一条从坐标原点到a线起始点的黑线,我推测在画第一条a线的时候会有一条默认从坐标原点到起始点的白线,然后在反色笔的影响下变成了黑色,画出a线时会有一条从原点到a线起始点的黑线。所以我们要做一个标志,判断下我们现在画的是第几条线,如果是第一条线,那我们就需要绕开反色笔涂抹那一段程序,如果不是第一条线,那么就不用绕开。
void CMy1900402213View::OnMouseMove(UINT nFlags, CPoint point)
{
if (start == true)
{
CDC* pDC = GetDC();
if (type == 1)
{
if(go)
{
pDC->SetROP2(R2_NOTXORPEN);//调用反色笔
m_pline->Draw(pDC);
}
else
go=ture;
m_pline->Set_end_point(point);
m_pline->Draw(pDC);
}
ReleaseDC(pDC);
}
}
这样之后就可以正常的画出直线了
圆与矩形也可以用类似的流程来绘出,不过圆需要设置圆心点和计算半径,矩形则需要设置起始点和对角点。
当然如果想改变线的颜色和形状的话
CDC* pDC = GetDC();
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));//PS_SOLID是实线,PS_DOT是虚线,RGB就是那个RGB
CPen* pOldPen = (CPen*)pDC->SelectObject(&pen);
ReleaseDC(pDC);//最后必须释放掉
大家可能已经发现通过第二部分画出的直线是无法保存的,也就是说将窗口最小化后再打开时画的的直线就不见了,所以我们需要画完每一条直线后将其保存,并且一直重新绘制它。
至于如何保存直线的数据,并且提取它的数据来绘制,这里就要用到链表了
**Clist.h**
#pragma once
#ifndef Clist_h
#define Clist_h
#include
#include
using namespace std;
struct Node
{
int now_RGB;//储存画笔颜色
int now_line;//储存线型
int num;
void* data;
int now_type;//储存形状
Node* next;
Node()
{
next = NULL;
}
};
class CSlist
{
public:
CSlist();
Node* first;
void InputFront(Node* Pelem);//在前面插入最新的一个节点
int Length()const;//判断链表长度
bool IsEmpty()const;//判断链表是否为空
void MakeEmpty();//将链表清空
Node* Locate(int i);//将第i个数据取出
int Input_behind_pos(Node* Pelem, int pos);//在第几个数值之后插入新节点
int Input_before_pos(Node* Pelem, int pos);
void Delete(int pos);//删除
};
#endif
**Clist.cpp**
#include"CSlist.h"
#include
#include
using namespace std;
CSlist::CSlist()
{
first=NULL;
};
void CSlist::InputFront(struct Node* Pelem)
{
if (Pelem == NULL) return;
Pelem->next = first;
first= Pelem;
}
int CSlist::Length()const
{
if (first == NULL) return 0;
struct Node* n = first;
int length = 1;
while (n->next)
{
length++;
n = n->next;
}
return length;
}
bool CSlist::IsEmpty()const
{
if (first == NULL)
{
return true ;
}
else
{
return false;
}
}
void CSlist::MakeEmpty()
{
struct Node* d;
while (first)
{
d = first;
first = first -> next;
delete d;
}
return;
}
struct Node* CSlist::Locate(int i)
{
int j=0;
struct Node* L = first;
if (i <= 0) cout << "error"<<endl;
while (L->next)
{
j++;
if (j == i)
{
return L;
}
else L = L->next;
}
cout << "error" << endl;
}
int CSlist::Input_behind_pos(struct Node* Pelem, int pos)
{
struct Node* d = Locate(pos + 1);
Pelem->next = d;//新节点的next指向原来的下一个节点
struct Node* c = Locate(pos);
c->next = Pelem;//上一个节点的next指向新节点
return 1;
}
int CSlist::Input_before_pos(struct Node* Pelem, int pos)
{
struct Node* c = Locate(pos-1);
c->next = Pelem;//上一个节点的next指向新节点
struct Node* d = Locate(pos);
Pelem->next = d;//新节点的next指向原来的下一个节点
return 1;
}
void CSlist::Delete(int pos)
{
struct Node* a = Locate(pos-1);
struct Node* b = Locate(pos);
struct Node* c = Locate(pos+1);
a->next = c;
delete b;
}
了解了链表之后我们要选择一个合适的时机储存我们的图形,所以我们可以在之前放置的鼠标抬起消息处理程序里进行储存,分别在链表中存储图形是什么,即now_type是几,以及图形所需要的点和数据,也就是直线的起始点和终点,圆的圆心与半径,矩形的起始点与对角点
void CMy1900402213View::OnLButtonUp(UINT nFlags, CPoint point)
{
if (start==true)
{
Node* repaint = new Node;
switch (type)
{
case 1:
{
repaint->now_type = 1;
repaint->data =m_pline;
m_line_list.InputFront(repaint);
}
break;
case 2:
{
repaint->now_type = 2;
repaint->data = m_psquare;
m_line_list.InputFront(repaint);
}
break;
case 3:
{
repaint->now_type = 3;
repaint->data = m_pcircle;
m_line_list.InputFront(repaint);
}break;
}
}
go = false;
start = false;
CView::OnLButtonUp(nFlags, point);
}
这样之后你所画的所有图形都被存储在一条链表中,就像一栋公寓,每一个房间里都是一个图形,里面记录着他的形状和他的特殊点。
然后继续在xxxxxxview.cpp文件中寻找ondraw函数,在这个里面添加上重绘图形的程序,
重绘的原理就是,利用循环,把链表中每个图形的数据拿出来,再根据图形的不一样,使用不同的绘图程序绘制。
注意,因为矩形和圆心其实是个背景色的封闭图形,重绘时可能会因为重绘的顺序导致,把一些线条遮住,所以我重绘时采用了空心笔刷,这样他们就是透明的封闭图形了。
void CMy1900402213View::OnDraw(CDC* pDC)
{
CMy1900402213Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: 在此处为本机数据添加绘制代码
int i,j;
j = m_line_list.Length();
if (m_line_list.IsEmpty() == false)
{
for(i=0;i<j+1;i++)
{
CDC* pDC = GetDC();
pDC->SelectStockObject(NULL_BRUSH);//空心笔刷
Node* paint;
paint=m_line_list.Locate(i);
if (paint->now_type == 1)
{
((CLine*)paint->data)->Draw(pDC);
}
if (paint->now_type == 2)
{
((CQuare*)paint->data)->Draw(pDC);
}
if (paint->now_type == 3)
{
((CCircle*)paint->data)->Draw(pDC);
}
ReleaseDC(pDC);
}
}
}
有很多细小的地方很难讲述完毕,大家可以直接下载程序看看
基于MFC制作的简易绘图软件