源码链接:
链接:https://pan.baidu.com/s/1tio4z8Le2sbNJiUFkofTRw
提取码:fk6p

先看效果图
在这里插入图片描述
采用的是QT+VS2017进行开发。要记得引入QT的库!!!这里采用前端后端的说法只是个人觉得这样好理解一点,非专业术语哈!
我们可以把每个绘图事件定义一个类,因为都需要进行绘画,以及更新鼠标拖动结束点并且实时更新绘画效果,所以我们使用继承,即一个父类Figure,被重写的方法要加上virtual.如下
在这里插入图片描述
然后你要写哪种类型的图继承这个Figure父类就行。

一、 画直线Line

这是头文件,代码里面有解释如何进行划线

#pragma once
#include <QPainter>
#include <Figure.h>

class Line:public Figure
{
public:

	//画线的思路:两点确定一条直线。所以得将起始点,终止点确定。
	//QPoint 就是存放一个点,也就是存放一组  x,y
	//然后调用Draw,传入开始点、结束点
	Line(const QPoint _start_point, const QPoint _end_point);

	//QPainter可以进行绘制图形,传入它的引用进行调用drawLine函数进行 绘制直线操作。
	void Draw(QPainter &paint);

	//用于动态更新
	void update(const QPoint _end_point);

private:
	//对理解很重要: 这两个点是存放在 figureVec[i] ,然后取出拿来传入draw方法进行绘图的点!!!
	QPoint start_point;
	QPoint end_point;
};

然后看一下它的实现代码

#include "Line.h"



Line::Line(const QPoint _start_point, const QPoint _end_point)
{
	//设置开始点的 x,y坐标
	start_point.setX(_start_point.x());
	start_point.setY(_start_point.y());

	//设置结束点的 x,y坐标
	end_point.setX(_end_point.x());
	end_point.setY(_end_point.y());

}

void Line::Draw(QPainter & paint)
{
	paint.drawLine(start_point, end_point);
}

void Line::update(const QPoint _end_point)
{
	end_point.setX(_end_point.x());
	end_point.setY(_end_point.y());
}

接下来是QT的viewwidhget绘画操作的头文件。
鼠标点击、移动、释放、绘画事件、存储点
建立GUI工程时继承于QWidget
ViewWidget.h
在这里插入图片描述
在这里插入图片描述
想要详细的了解怎么建立QT程序可以搜索一下,这里不赘述。

ViewWidget.h文件如下:

#pragma once

#include <QWidget>
#include "ui_ViewWidget.h"

#include <QPoint>
#include <QPainter>
#include <qevent.h>
#include <qpainter.h>

#include "Figure.h"
#include "Line.h"
#include "Rect.h"
#include "Polygon.h"
#include "Freehand.h"
#include "Ellipse.h"


#include <vector>
using namespace std;

//创建工具 枚举类型
enum FigureType
{
	KDefault = 0,
	KLine = 1,
	KRectangle = 2,
	KEllipse = 3,
	KPolygon=4,
	KFreehand=5,
};

class ViewWidget : public QWidget
{
	Q_OBJECT

	//作为全局变量,直接写在这里,不用加public,protect这些
	//使用vector,第一种头文件加using namespace std
	//第二种 std::vector<Line* > line_array_; 这样写
	//vector<Line* > line_array_;

public:
	ViewWidget(QWidget *parent = Q_NULLPTR);
	~ViewWidget();



public:
	void mousePressEvent(QMouseEvent* event); //鼠标击发响应函数(左右键,单双击)
	void mouseMoveEvent(QMouseEvent* event); //鼠标移动响应函数
	void mouseReleaseEvent(QMouseEvent* event); //鼠标释放响应函数(左右键,单双击)

public:
	/*
	重写绘图事件,虚函数
	如果在绘图窗口绘图,必须放在绘图事件里实现
	绘图事件内部自动调用,窗口需要重建的时候(状态发生改变)
	*/
	void paintEvent(QPaintEvent*); //Qt所有的绘制都只能在此函数中完成

public:
	void set_figure_type_to_line();
	void set_figure_type_to_rectangle();
	void set_figure_type_to_ellipse();
	void set_figure_type_to_polygon();
	void set_figure_type_to_freehand();
	void undo();

private:
	Ui::ViewWidget ui;

private:
	bool draw_status; //当前绘制状态,true为绘制当前鼠标拖动的图元,false为不绘制
	QPoint start_point; //当前图元的起始位置
	QPoint end_point;  //当前图元的终止位置
	
	FigureType figure_type; //有6种工具 的 实例化
	Figure* current_figure; //当前工具
	vector<Figure* > figureVec;  //存放工具对应的数组
	


};

接下来是ViewWidget.cpp

#include "ViewWidget.h"

ViewWidget::ViewWidget(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);
	draw_status = false; //设置初始状态:不绘制
	current_figure = NULL;
	figure_type = KDefault;
}

ViewWidget::~ViewWidget()
{
	for (size_t i = 0; i < figureVec.size(); i++)
	{
		if (figureVec[i])
		{
			delete figureVec[i];
			figureVec[i] = NULL;
		}
	}

// 每次把 current_figure 在push to figureVec 后都将其设置为 NULL
// 因此下面的 delete 和上面的 delete 不会处理同一指针
	if (current_figure != NULL)
	{
		delete current_figure;
	}

}

//鼠标点击函数体
void ViewWidget::mousePressEvent(QMouseEvent* event)
{
	if (Qt::LeftButton == event->button())//判断是否是鼠标左击
	{
		draw_status = true; //绘制状态:绘制
		start_point = end_point = event->pos (); //将当前位置设置为图元开始位置

		switch (figure_type)
		{
		case KDefault:
			draw_status = false;
			break;
		case KLine:
			current_figure = new Line(start_point, end_point);
			break;
		case KRectangle:
			current_figure = new Rect(start_point, end_point);
			break;
		case KEllipse:
			current_figure = new Ellipse(start_point, end_point);
			break;
		case KPolygon:
			if (current_figure == NULL)
			{
				current_figure = new Polygon(start_point);
			  // 保持鼠标追踪
			  // 无论鼠标是否有键按下,都会持续触发 mouseMoveEvent
				setMouseTracking(true);
			}
			break;
		case KFreehand:
			current_figure = new Freehand(start_point);
			break;
		default:
			break;
		}
	}
}

//鼠标移动函数体
void ViewWidget::mouseMoveEvent(QMouseEvent* event)
{
	if (draw_status) //判断当前绘制状态
	{
		end_point = event->pos(); //若为真,则设置当前位置 为 图元终止点位置
		current_figure->update(end_point);
	}
}

//鼠标释放函数体
void ViewWidget::mouseReleaseEvent(QMouseEvent * event)
{
	if (figure_type != KPolygon)
	{
		draw_status = false;
		if (current_figure != NULL)
		{
			figureVec.push_back(current_figure);
			current_figure = NULL;
			//每次current_figure  pushback存放到容器figureVec后 设置为空,所以析构函数中要单独delete
		}
	}
		else // figure_type == kPolygon
		{
			if (Qt::LeftButton == event->button ())
			{
				//新增一个点
				current_figure->update(1);
			}
			if (Qt::RightButton == event->button ())
			{
				draw_status = false;
				current_figure->update(0); //绘制结束
				figureVec.push_back(current_figure);
				current_figure = NULL;
				setMouseTracking(false);
			}

		}
}


void ViewWidget::paintEvent(QPaintEvent *)
{
	QPainter painter;
	painter.begin(this);

	for (int i = 0; i < figureVec.size(); i++) //已经存放的数据,进行绘制
	{
		figureVec[i]->Draw(painter); //存放的每一种工具类型的数组(点),调用对应的draw函数
	}

	if (current_figure != NULL) //正在绘制...,注释掉发现当鼠标释放后才会显示你刚绘制的图形
	{
		current_figure->Draw(painter);
	}

	painter.end();
	update();
}

void ViewWidget::set_figure_type_to_line()
{
	if (draw_status == false)
	{
		figure_type = KLine;
	}
}

void ViewWidget::set_figure_type_to_rectangle()
{
	if (draw_status==false)
	{
		figure_type = KRectangle;
	}
}

void ViewWidget::set_figure_type_to_ellipse()
{
	if (draw_status==false)
	{
		figure_type = KEllipse;
	}
}

void ViewWidget::set_figure_type_to_polygon()
{
	if (draw_status==false)
	{
		figure_type = KPolygon;
	}
}

void ViewWidget::set_figure_type_to_freehand()
{
	if (draw_status==false)
	{
		figure_type = KFreehand;
	}
}

void ViewWidget::undo()
{
	if (figureVec.size ()>0 )
	{
		delete figureVec.back();
		figureVec.pop_back();
	}
}



可以理解为上述部分为后端,那么现在来看一下前端,也就是界面设计以及使用信号槽机制将两者连接。
MiniDraw.h

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_MiniDraw.h"

#include "ViewWidget.h"


#include <QMenu>
#include <QToolBar>
#include <QAction>
#include <QMenuBar>

#include<vector>
using namespace std;

class MiniDraw : public QMainWindow
{
	Q_OBJECT

public:
	MiniDraw(QWidget *parent = Q_NULLPTR);

private:
	Ui::MiniDrawClass ui;

private:
	ViewWidget* view_widget;  //声明ViewWidget指针

public:
	QMenu* dmeau;
	QToolBar* dtoolbar;
	vector<QAction*> actionVec;  //添加各种动作:Line\Polygon....

public:
	void interfaceDesign(); //设计界面
	void Init(); //初始化viewwidget控件

	


};

MiniDraw.cpp

#include "MiniDraw.h"
#include <string>
using namespace std;

constexpr auto TOOLNUM = 6;;

MiniDraw::MiniDraw(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	interfaceDesign();
	Init();
}

void MiniDraw::interfaceDesign()
{
	// 添加菜单 Figure
	dmeau = menuBar()->addMenu(tr("Figure"));
	dtoolbar = addToolBar(tr("Tool"));

	// 将原有的 toolBar 即 mainToolBar 改名为 Figure Tool
	
	/*dtoolbar = findChild<QToolBar*>(tr("mainToolBar"));
	//这样就可以在ui设计器上找到名字为 mainToolBar  的  QToolBar
	dtoolbar->setWindowTitle(tr("Figure Tool"));*/

	//生成按键
	string toolName[TOOLNUM] = 
	{ "Line","Rectangle","Ellipse",
	  "Polygon", "Freehand", "Undo" };
	string statusTips[TOOLNUM] =
	   {"Select line tool.", "Select rectangle tool.",
		"Select ellipse tool.", "Select polygon tool.", 
		"Select freehand tool.", "Delect lastest firgue."}; 

	for (int i=0; i<TOOLNUM; i++)
	{
		//QIcon 类代表图标
		//给动作添加图标,并显示文字提示。方法如下:
		//new QAction(  QIcon(":MiniDraw/images/haah.bmp" ,tr("当选中前面那个图标时显示这行文字"),this)
		actionVec.push_back(new QAction(
			QIcon(tr(
			(string("I:\\VS2017\\HW1\\MiniDraw\\MiniDraw\\image\\") + toolName[i]
				+ string(".bmp")).c_str())),
			tr(toolName[i].c_str()),
			this));

		dtoolbar->addAction(actionVec[i]); //给工具栏添加动作
		dmeau->addAction(actionVec[i]); //给菜单也添加动作

		//这是添加状态栏的提示语句。状态栏就是  主窗口最下面的一条
		//也就是你选择了哪一个绘图,下面状态栏会有所提示
		//如:Select line tool.
		actionVec[i]->setStatusTip( tr( statusTips[i].c_str() ));	
	}

	//QAction(QIcon(tr("I:\\VS2017\\HW1\\MiniDraw\\MiniDraw\\image\\Line.bmp")), tr("Line"), this);
	
}

void MiniDraw::Init()
{
	view_widget = new ViewWidget(); //实例化ViewWidget控件窗口
	setCentralWidget(view_widget);  //将ViewWidget控件设置为主窗口的中心位置

	//信号槽
	connect(actionVec[0], &QAction::triggered, view_widget, &ViewWidget::set_figure_type_to_line);
	connect(actionVec[1], &QAction::triggered, view_widget, &ViewWidget::set_figure_type_to_rectangle);
	connect(actionVec[2], &QAction::triggered, view_widget, &ViewWidget::set_figure_type_to_ellipse);
	connect(actionVec[3], &QAction::triggered, view_widget, &ViewWidget::set_figure_type_to_polygon);
	connect(actionVec[4], &QAction::triggered, view_widget, &ViewWidget::set_figure_type_to_freehand);
	connect(actionVec[5], &QAction::triggered, view_widget, &ViewWidget::undo);

}

界面设计就是QToolBar工具栏,Qmeau菜单,QAction动作
一般我们给工具栏,菜单栏都加上动作
都要在头文件声明
在这里插入图片描述
不要忘记这个我们要在前端实例化这个控件窗口,就是上面我们进行绘画事件的那个后端
在这里插入图片描述
下面这个就是信号槽
connect(发送哪个动作信号,信号发送方,接受信号的对象,对象中处理信号的函数)

在这里插入图片描述
QT中的对象都不用手动去释放QT会自己释放。
以画直线Line为例理一下整个思路:
1.选择Line画线工具,对应的动作是actionVec[0],
在这里插入图片描述
然后调用后端set_figure_type_to_line函数

connect(actionVec[0], &QAction::triggered, view_widget, &ViewWidget::set_figure_type_to_line);
void ViewWidget::set_figure_type_to_line()
{
	if (draw_status == false)
	{
		figure_type = KLine;
	}
}

将figure_type设置成Kline了,这个函数是在后端绘画ViewWidget.cpp中的,已经可以开始拖动鼠标进行绘画了。
在这里插入图片描述
鼠标点击函数,将起始点终止点赋值给实例化的Line对象current_figure暂时存放点
current_figure = new Line(start_point, end_point);

void ViewWidget::mousePressEvent(QMouseEvent* event)
{
	if (Qt::LeftButton == event->button())//判断是否是鼠标左击
	{
		draw_status = true; //绘制状态:绘制
		start_point = end_point = event->pos (); //将当前位置设置为图元开始位置

		switch (figure_type)
		{
		case KDefault:
			draw_status = false;
			break;
		case KLine:
			current_figure = new Line(start_point, end_point);
			break;
		case KRectangle:
			current_figure = new Rect(start_point, end_point);
			break;
		case KEllipse:
			current_figure = new Ellipse(start_point, end_point);
			break;
		case KPolygon:
			if (current_figure == NULL)
			{
				current_figure = new Polygon(start_point);
			  // 保持鼠标追踪
			  // 无论鼠标是否有键按下,都会持续触发 mouseMoveEvent
				setMouseTracking(true);
			}
			break;
		case KFreehand:
			current_figure = new Freehand(start_point);
			break;
		default:
			break;
		}
	}
}

鼠标移动函数
将当前鼠标(拖动中)的终止点实时赋值给终止点end_point,并存放在current_figure

//鼠标移动函数体
void ViewWidget::mouseMoveEvent(QMouseEvent* event)
{
	if (draw_status) //判断当前绘制状态
	{
		end_point = event->pos(); //若为真,则设置当前位置 为 图元终止点位置
		current_figure->update(end_point);
	}
}

鼠标释放函数:将current_figure数据存放到figureVec(用于存放所有画线的点)中

//鼠标释放函数体
void ViewWidget::mouseReleaseEvent(QMouseEvent * event)
{
	if (figure_type != KPolygon)
	{
		draw_status = false;
		if (current_figure != NULL)
		{
			figureVec.push_back(current_figure);
			current_figure = NULL;
			//每次current_figure  pushback存放到容器figureVec后 设置为空,所以析构函数中要单独delete
		}
	}
		else // figure_type == kPolygon
		{
			if (Qt::LeftButton == event->button ())
			{
				//新增一个点
				current_figure->update(1);
			}
			if (Qt::RightButton == event->button ())
			{
				draw_status = false;
				current_figure->update(0); //绘制结束
				figureVec.push_back(current_figure);
				current_figure = NULL;
				setMouseTracking(false);
			}

		}
}

好,现在我们已经存储了绘画的点了,接下来进行绘画操作
看一下绘画函数void paintEvent(QPaintEvent *),绘画事件基本就是这样固定了

void ViewWidget::paintEvent(QPaintEvent *)
{
	QPainter painter;
	painter.begin(this);

	for (int i = 0; i < figureVec.size(); i++) //已经存放的数据,进行绘制
	{
		figureVec[i]->Draw(painter); //存放的每一种工具类型的数组(点),调用对应的draw函数
	}

	if (current_figure != NULL) //正在绘制...,注释掉发现当鼠标释放后才会显示你刚绘制的图形
	{
		current_figure->Draw(painter);
	}

	painter.end();
	update();
}

存放的哪种类型的点调用它对应的draw函数,看到这里可能有点懵,怎么确定存放的点是哪种类型,来,看这里
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
current_figure存放当前图形类型,然后将current_figure存放到figureVec(感觉跟上面有点重复了),所以figureVec调用哪种图形的函数也就不足为奇了。
这里我们的例子是figureVec存放了Line的点。所以我们figureVec的调用Draw(painter)就是Line的Draw(painter),看一下Line的Draw(painter)函数

void Line::Draw(QPainter & paint)
{
	paint.drawLine(start_point, end_point);
}

所以这就是画直线的整个从前端到后端的操作了。
剩下的操作矩形,椭圆,自由绘画基本就是按这种套路来。累,有空再更~

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐