I need to draw a connector between two items in a QSceneView
. I've subclssed QGraphicsObject
for creating this line (it's not a QGraphicsLineItem
because I need to update its appearance later). This is the class:
#ifndef TRANSITIONDRAGLINE_HPP_
#define TRANSITIONDRAGLINE_HPP_
#include "Style/CanvasTransitionDragLine.hpp"
#include <QGraphicsObject>
#include <QPointF>
#include <string>
class TransitionDragLine : public QGraphicsObject {
Q_OBJECT
public:
TransitionDragLine(const Style::CanvasTransitionDragLine& style, QGraphicsItem* parent = nullptr);
virtual ~TransitionDragLine();
void setStartPoint(const QPointF& startPoint);
void setEndPoint(const QPointF& endPoint);
public:
QRectF boundingRect() const override;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
private:
Style::CanvasTransitionDragLine m_style;
QPointF m_startPoint;
QPointF m_endPoint;
};
#endif // !TRANSITIONDRAGLINE_HPP_
#include "TransitionDragLine.hpp"
#include <QPainter>
///////////////////////////////////////////////////////////////////////////////
// PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
TransitionDragLine::TransitionDragLine(const Style::CanvasTransitionDragLine& style, QGraphicsItem* parent) :
QGraphicsObject(parent),
m_style(style) {
}
TransitionDragLine::~TransitionDragLine() {
}
void TransitionDragLine::setStartPoint(const QPointF& startPoint) {
m_startPoint = startPoint;
}
void TransitionDragLine::setEndPoint(const QPointF& endPoint) {
m_endPoint = endPoint;
update();
}
///////////////////////////////////////////////////////////////////////////////
// VIRTUAL PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
QRectF TransitionDragLine::boundingRect() const {
qreal dx = m_endPoint.x() - m_startPoint.x();
qreal dy = m_endPoint.y() - m_startPoint.y();
qreal x{ 0.0 };
qreal y{ 0.0 };
qreal w{ 0.0 };
qreal h{ 0.0 };
qreal penHalfWidth{ m_style.getPen().widthF() * 0.5 };
if (dx >= 0.0 && dy >= 0.0) {
x = 0.0 - penHalfWidth;
y = 0.0 - penHalfWidth;
w = dx + penHalfWidth;
h = dy + penHalfWidth;
}
else if (dx >= 0.0 && dy < 0.0) {
x = 0.0 - penHalfWidth;
y = dy - penHalfWidth;
w = dx - penHalfWidth;
h = -dy - penHalfWidth;
}
else if (dx < 0.0 && dy >= 0.0) {
x = dx - penHalfWidth;
y = 0 - penHalfWidth;
w = -dx - penHalfWidth;
h = dy - penHalfWidth;
}
else {
x = dx - penHalfWidth;
y = dy - penHalfWidth;
w = -dx - penHalfWidth;
h = -dy - penHalfWidth;
}
return QRectF(x, y, w, h);
}
void TransitionDragLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
qreal px = m_endPoint.x() - m_startPoint.x();
qreal py = m_endPoint.y() - m_startPoint.y();
painter->setPen(m_style.getPen());
painter->drawLine(0.0, 0.0, px, py);
}
When I want to use it, I set a flag in my Canvas
class, that inherits QGraphicsView
, and I update it. When I press the mouse button I set the start point and then I set the endpoint (and call update()
), every time that I move the mouse until left button is pressed. Basically I call startTransitionLineDrag
in polling until the mouse is pressed, and when I release the mouse button I call endTransitionLineDrag
in my Canvas (subclass of QGraphicsView
):
void Canvas::mouseMoveEvent(QMouseEvent* event) {
switch (m_currentState) {
case CurrentState::PressedOnTransition: {
if (event->buttons() == Qt::LeftButton) {
startTransitionLineDrag(event);
}
} break;
}
QGraphicsView::mouseMoveEvent(event);
}
void Canvas::mouseReleaseEvent(QMouseEvent* event) {
if (m_currentState == CurrentState::PressedOnTransition) {
if (true /* business logic here not useful for the problem */) {
endTransitionLineDrag(event);
}
}
QGraphicsView::mouseReleaseEvent(event);
}
void Canvas::startTransitionLineDrag(QMouseEvent* event) {
if (m_transitionDragLine == nullptr) {
m_transitionDragLine = new TransitionDragLine(m_style.getTransitionDragLineStyle());
m_transitionDragLine->setStartPoint(mapToScene(event->pos()));
m_scene->addItem(m_transitionDragLine);
m_transitionDragLine->setPos(mapToScene(event->pos()));
}
m_transitionDragLine->setEndPoint(mapToScene(event->pos()));
//repaint();
}
void Canvas::endTransitionLineDrag(QMouseEvent* event) {
/* other business logic */
deleteDragTransitionLine();
}
void Canvas::deleteDragTransitionLine() {
if (m_transitionDragLine) {
m_scene->removeItem(m_transitionDragLine);
delete m_transitionDragLine;
m_transitionDragLine = nullptr;
}
}
The logic works: when I activate the dragging I can see the line and it's update until the mouse button is pressed. But you can see in the attached image that I've a rendering problem:
The rendering is not working properly for the line; I've a trail of past images of the line, like the QGraphicsView
is not updated properly.
I sense a code smell when in TransitionDragLine::setEndPoint
I need to call update()
after setting the end point of the line (otherwise the line is not displayed) but I don't find a way to solve the issue.
What I'm doing wrong?
EDIT:
I've seen a solution here:
Artifacts showing when modifying a custom QGraphicsItem
I've tried to call prepareGeometryChange()
every time that I update the end point but artifacts remain:
void TransitionDragLine::setEndPoint(const QPointF& endPoint) {
m_endPoint = endPoint;
prepareGeometryChange();
update();
}