效果如下图所示,可在场景中手动添加线段,可对整体拖拽,可对两个端点拖拽
对QGraphicsItem 自定义绘图本身很简单,由于项目需求,我需要对某一个端点拖拽,另一个端点在场景的坐标保持不变,另外要保持线段整体在场景的坐标居中(防止边缘矩形错乱),这就比较麻烦
另外是线段的形状函数确定,我没有采用对矩形的旋转,而是两个端点为中心生成一个小矩形,确定好连线的方向,根据两个小矩形相应端点连接成一个多边形即为刚好包含线段的形状,为形状函数。
线段的边界矩形就是包含两个端点的最小矩形然后稍微外扩一点(为了端点的显示效果的不超出边界)
要实现上述需求需要对线段的坐标和场景坐标需要很透彻,首先是item的坐标,始终保持两个端点为中心对称,即(point1 = - point2),边界矩形也是如此,中心点(QPOINTF(0,0))即为场景坐标,每次对端点拖拽都需要更新场景坐标,不然边界矩形就会错乱。
花了一下午搞定的,不知道自己实力如何,之后还会自定义绘制矩形,绘制自定义网格,绘制文本框。
源码如下图所示,使用方式非常简单,在场景中直接添加即可,只需要传入点击的场景坐标。
line.h
#ifndef LINE_H
#define LINE_H
#include <QGraphicsItem>
#include <QPainter>
#include <QObject>
#include <QMouseEvent>
using namespace std;
class line : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
line(QPointF start_point,bool found);
~line();
//画笔
QPen mypen;
QPointF m_p1,m_p2 ;
QPointF m_pos; // 场景坐标点
QPointF m_pressedPos; // 场景坐标点击的点
//两点
QPointF point1;
QPointF point2;
int press_state = -1;
bool flag_found = false;
int size = 5;
QPainterPath shape()const override;
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
double distance(QPointF point1,QPointF point2) const;
int judge_touch(QPointF point);
bool judge_diagonal(QPointF point1,QPointF point2) const;
QPolygonF get_shape() const;
QRectF get_rect() const;
void set_pen();
protected:
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif // LINE_H
line.cpp
#include "line.h"
line::line(QPointF start_point, bool found)
{
this->setFlag(QGraphicsItem::ItemIsMovable, true);
this->setFlag(QGraphicsItem::ItemIsSelectable, true);
this->setAcceptHoverEvents(true);
this->setAcceptDrops(true);
setPos(start_point);
set_pen();
flag_found = found; //正在创建
}
line::~line()
{
}
//设置画笔
void line::set_pen()
{
mypen.setWidth(2); //线宽
mypen.setColor(QColor(0,0,0)); //划线颜色
mypen.setStyle(Qt::SolidLine);//线的类型,实线、虚线等
mypen.setCapStyle(Qt::FlatCap);//线端点样式
mypen.setJoinStyle(Qt::BevelJoin);//线的连接点样式
}
//鼠标双击
void line::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
//左键双击
if (event->button() == Qt::LeftButton)
{
}
}
//判断触碰
int line::judge_touch(QPointF point)
{
int count = -1;
QRectF rect = get_rect();
QPointF key_point1,key_point2;
//获取两个关键点
if(!judge_diagonal(point1,point2)) //主对角线
{
key_point1 = rect.bottomLeft() + QPointF(size,-size);
key_point2 = rect.topRight() + QPointF(-size,size);
}
else
{
key_point1 = rect.topLeft() + QPointF(size,size);
key_point2 = rect.bottomRight() + QPointF(-size,-size);
}
//根据关键点确认点击的是point1还是point2
if(distance(point,key_point1) < size)
{
if(distance(point1,key_point1) < 3)
count = 1;
else if(distance(point2,key_point1) < 3)
count = 2;
}
else if(distance(point,key_point2) < size)
{
if(distance(point1,key_point2) < 3)
count = 1;
else if(distance(point2,key_point2) < 3)
count = 2;
}
else if(get_shape().containsPoint(point,Qt::WindingFill)) count = 0;
qDebug() << "判断触碰" << count;
return count;
}
//鼠标按下
void line::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
qDebug() << "item鼠标按下";
if (event->button() == Qt::LeftButton)
{
m_pressedPos = event->scenePos();
m_pos = this->pos();
m_p1 = point1 + m_pos;
m_p2 = point2 + m_pos;
this->setSelected(true);
//if(!press_ctrl()) emit sendSelected(quid); //如果按下crtl,不取消别的item的选中
//判断按下的点
press_state = judge_touch(event->pos());
}
else if (event->button() == Qt::RightButton) //右键菜单
{
}
this->update();
}
//鼠标移动
void line::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "鼠标移动";
// 获取场景坐标和本地坐标
QPointF scenePos = event->scenePos();
//获取偏移量
qreal xInterval = scenePos.x() - m_pressedPos.x();
qreal yInterval = scenePos.y() - m_pressedPos.y();
if(flag_found) //如果正在创建
{
QPointF no_move = m_p2;
this->setPos((scenePos + no_move)/2);
point1 = QPointF((scenePos.rx() - no_move.rx())/2,(scenePos.ry() - no_move.ry())/2);
point2 = - point1;
}
else if(press_state == 0)
{
m_pressedPos = scenePos;
m_pos = this->pos();
this->setPos(this->pos() + QPointF(xInterval,yInterval));
}
else
{
if(press_state == 1)
{
this->setPos((scenePos + m_p2)/2);
point1 = QPointF((m_p2.rx() - scenePos.rx())/2,(m_p2.ry() - scenePos.ry())/2);
point2 = - point1;
}
else if(press_state == 2)
{
this->setPos((scenePos + m_p1)/2);
point2 = QPointF((m_p1.rx() - scenePos.rx())/2,(m_p1.ry() - scenePos.ry())/2);
point1 = - point2;
}
}
this->update();
}
//鼠标释放
void line::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "item鼠标释放";
flag_found = false;
press_state = -1;
this->update();
}
//判断线段的两点是否在主对角线
bool line::judge_diagonal(QPointF point1, QPointF point2) const
{
//判断point1与point2的连线是否在主对角线
bool flag1,flag2;
if(point1.x() <= point2.x()) flag1 = true;
else flag1 = false;
if(point1.y() <= point2.y()) flag2 = true;
else flag2 = false;
bool out;
if(flag1 == flag2) out = true;
else out = false;
return out;
}
//得到边界矩形
QRectF line::get_rect() const
{
double wide = abs(point1.x() - point2.x());
double height = abs(point1.y() - point2.y());
return QRectF(-wide/2 - size,-height/2 - size,wide + size*2,height + size*2);
}
//得到形状
QPolygonF line::get_shape() const
{
QRectF rect = get_rect();
QPolygonF poly;
int cur_size = size*2;
if(!judge_diagonal(point1,point2)) //主对角线
{
poly.append(rect.bottomLeft());
poly.append(rect.bottomLeft() - QPointF(0,cur_size));
poly.append(rect.topRight() - QPointF(cur_size,0));
poly.append(rect.topRight());
poly.append(rect.topRight() + QPointF(0,cur_size));
poly.append(rect.bottomLeft() + QPointF(cur_size,0));
}
else
{
poly.append(rect.topLeft());
poly.append(rect.topLeft() + QPointF(cur_size,0));
poly.append(rect.bottomRight() - QPointF(0,cur_size));
poly.append(rect.bottomRight());
poly.append(rect.bottomRight() - QPointF(cur_size,0));
poly.append(rect.topLeft() + QPointF(0,cur_size));
}
return poly;
}
//形状函数
QPainterPath line::shape() const
{
QPainterPath path;
path.addPolygon(get_shape());
return path;
}
//边界矩形
QRectF line::boundingRect() const
{
return get_rect();
}
//判断两点距离
double line::distance(QPointF point1,QPointF point2) const
{
return sqrt(qPow(point1.rx() - point2.rx(),2) + qPow(point1.ry() - point2.ry(),2));
}
//重绘
void line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setPen(mypen);
painter->setBrush(QBrush(QColor(255,255,255)));
//测试绘制形状
//painter->drawPolygon(get_shape());
//绘制线段
painter->drawLine(point1,point2);
if(press_state == 0) //绘制蒙版
{
QPen pen = mypen;
pen.setColor(QColor(200,200,200));
painter->setPen(pen);
painter->drawLine(point1,point2);
painter->setPen(mypen);
}
if(this->isSelected())//选中效果
{
QPoint point_wide(size,size);
painter->drawEllipse(QRectF(point1 - point_wide,point1 + point_wide));
painter->drawEllipse(QRectF(point2 - point_wide,point2 + point_wide));
}
}
在QGraphicsScene中使用方式,这里的found标志位是我需要的,不需要的话直接删除或者设为真
line *item = new line(cur_sence_pos,found);
this->addItem(item);