用户登录
用户注册

分享至

如何基于MFC制作简易绘图软件

  • 作者: 喜欢被宠着你
  • 来源: 51数据库
  • 2021-10-24
		             本文将讲解如何通过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<iostream>
#include<stdlib.h>
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<iostream>
#include<stdlib.h>
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制作的简易绘图软件

软件
前端设计
程序设计
Java相关