QT中的QPainter画简单图-线段,矩形,椭圆,自由绘画
先看效果图采用的是QT+VS2017进行开发。要记得引入QT的库!!!这里采用前端后端的说法只是个人觉得这样好理解一点,非专业术语哈!我们可以把每个绘图事件定义一个类,因为都需要进行绘画,以及更新鼠标拖动结束点并且实时更新绘画效果,所以我们使用继承,即一个父类Figure,被重写的方法要加上virtual.如下然后你要写哪种类型的图继承这个Figure父类就行。一、 画直线Line这...
源码链接:
链接: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);
}
所以这就是画直线的整个从前端到后端的操作了。
剩下的操作矩形,椭圆,自由绘画基本就是按这种套路来。累,有空再更~
更多推荐
所有评论(0)