How to draw a point (on mouseclick) on a QGraphicsScene?
UPDATE: There is a new class called QGraphicsSceneMouseEvent
that makes this a little easier.
I just finished an example using it here:
https://stackoverflow.com/a/26903599/999943
It differs with the answer below in that it subclasses QGraphicsScene
, not QGraphicsView
, and it uses mouseEvent->scenePos()
so there isn't a need to manually map coordinates.
You are on the right track, but you still have a little more to go.
You need to subclass QGraphicsView
to be able to do something with mouse presses or with mouse releases using QMouseEvent
.
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QMouseEvent>
class MyQGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit MyQGraphicsView(QWidget *parent = 0);
signals:
public slots:
void mousePressEvent(QMouseEvent * e);
// void mouseReleaseEvent(QMouseEvent * e);
// void mouseDoubleClickEvent(QMouseEvent * e);
// void mouseMoveEvent(QMouseEvent * e);
private:
QGraphicsScene * scene;
};
QGraphicsView
doesn't natively have dimension-less points. You will probably want to use QGraphicsEllipse
item or simply, scene->addEllipseItem()
with a very small radius.
#include "myqgraphicsview.h"
#include <QPointF>
MyQGraphicsView::MyQGraphicsView(QWidget *parent) :
QGraphicsView(parent)
{
scene = new QGraphicsScene();
this->setSceneRect(50, 50, 350, 350);
this->setScene(scene);
}
void MyQGraphicsView::mousePressEvent(QMouseEvent * e)
{
double rad = 1;
QPointF pt = mapToScene(e->pos());
scene->addEllipse(pt.x()-rad, pt.y()-rad, rad*2.0, rad*2.0,
QPen(), QBrush(Qt::SolidPattern));
}
Note the usage of mapToScene()
to make the pos()
of the event map correctly to where the mouse is clicked on the scene.
You need to add an instance of your subclassed QGraphicsView
to the centralWidget's layout of your ui if you are going to use a form.
QGridLayout * gridLayout = new QGridLayout(ui->centralWidget);
gridLayout->addWidget( new MyQGraphicsView() );
or if your ui has a layout already it will look like this:
ui->centralWidget->layout()->addWidget( new MyGraphicsView() );
If you don't use a QMainWindow
and a form, you can add it to a QWidget
if you set a layout for it and then add your QGraphicsView
to that layout in a similar manner. If you don't want a margin around your QGraphicsView
, just call show on it and don't put it inside a different layout.
#include <QtGui/QApplication>
#include "myqgraphicsview.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyQGraphicsView view;
view.show();
return a.exec();
}
And that's it. Now you are dangerous with QGraphicsView
's and their interaction with the mouse.
Be sure to read and study about Qt's Graphics View Framework and the related examples to be effective when using QGraphicsView
and QGraphicsScene
. They are very powerful tools for 2D graphics and can have a bit of a learning curve but they are worth it.
How to select a position and add an item in a QGraphicsView by mouse click?
There are few ways to do it:
Reimplement
mousePressEvent(QMouseEvent*)
(so, you need to implement subclass ofQGraphicsView
),Call
installEventFilter(QObject *)
forQGraphicsView
and implementbool eventFilter(QObject *, QEvent *)
to catch all events (and process onlyQEvent::MouseButtonPress
inside this function). In this case you do not need to implement subclass ofQGraphicsView
.
See also: Click event for QGraphicsView Qt and How to draw a point (on mouseclick) on a QGraphicsScene
QGraphicsView/Scene - items are drawn 2x away from mouse click
QGraphicsView
and QGraphicsScene
handle different coordinate systems, in the case of boundingRect()
and paint()
methods they must do in local coordinates with respect to the item and you should not use the pos()
method since that refers to coordinates with respect to the scene.
void NodeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/){
painter->drawEllipse(boundingRect());
}
QRectF NodeItem::boundingRect() const{
return QRectF(QPointF(-15, -15), QSizeF(30,30));
}
How to draw on QGraphicScene in real time?
If anyone will have trouble with something similar, here's my implementation:
DrawArea::DrawArea(QObject *parent) : QGraphicsScene(parent), mRadius(2), mDrawPath{}
{
drawing = false; // drawing is boolean type attribute
}
void DrawArea::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() & Qt::LeftButton)
{
mDrawPath.moveTo(event->scenePos());
event->accept();
drawing = true;
}
else event->ignore();
}
void DrawArea::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() & Qt::LeftButton)
{
drawing = false;
}
}
void DrawArea::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(drawing == false){}
else
{
mDrawPath.lineTo(event->scenePos());
this->clear();
addPath(mDrawPath);
}
}
It's worth to mention that this->clear() method in mouseMoveEvent method is a must-have. Without that performance will be great on beginning but during process of drawing it'll drop fast because you will constantly adding new paths which is performance killer.
PyQt5 - How to draw a dot on mouse click position?
You should only draw within the paintEvent
method, and this paint does not save memory so if you want to graph several points you must store them in some container, for example using QPolygon
.
paintEvent()
is called every time you call update()
or repaint()
, for example it is called every time it is resized, the window is moved, etc.
import sys
from PyQt5 import QtWidgets, QtGui, QtCore, uic
class GUI(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('gui.ui', self)
self.setFixedSize(self.size())
self.show()
self.points = QtGui.QPolygon()
def mousePressEvent(self, e):
self.points << e.pos()
self.update()
def paintEvent(self, ev):
qp = QtGui.QPainter(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
pen = QtGui.QPen(QtCore.Qt.red, 5)
brush = QtGui.QBrush(QtCore.Qt.red)
qp.setPen(pen)
qp.setBrush(brush)
for i in range(self.points.count()):
qp.drawEllipse(self.points.point(i), 5, 5)
# or
# qp.drawPoints(self.points)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = GUI()
sys.exit(app.exec_())
qt get mouse clicked position relative to image in a graphics view
If you want to add a QGraphicsLineItem
you must use the system coordinates of the scene for this you must use the function the scenePos()
method of QGraphicsSceneMouseEvent
and the method mapFromScene()
of the items.
for this we must override the methods mousePressEvent
, mouseMoveEvent
and mouseReleaseEvent
of QGraphicsScene
, all of the above I implemented it in the following class:
class CustomScene : public QGraphicsScene
{
Q_OBJECT
QGraphicsLineItem *item;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event){
item = new QGraphicsLineItem;
addItem(item);
const QPointF p = event->scenePos();
item->setPos(p);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event){
const QPointF p =item->mapFromScene(event->scenePos());
QLineF l = item->line();
l.setP2(p);
item->setLine(l);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
const QPointF p =item->mapFromScene(event->scenePos());
QLineF l = item->line();
l.setP2(p);
item->setLine(l);
}
};
The complete code is on the following link.
Arc in QGraphicsScene
A Subclass of QGraphicsItem, that takes 3 points, and intersects the three with an arc of a circle. The second point is always in the middle. (Selectablity and other properties haven't been fully implemented or tested).
Note: Qt Creator includes more advanced examples of subclassed QGraphicsItem
s such as Colliding Mice, and 40,000 chips examples.
http://qt-project.org/doc/qt-5/examples-graphicsview.html
Also to enable QObject
signals and slots and properties from a QGraphicsItem
, you should use QGraphicsObject
.
Note: added onto github here.
arcgraphicsitem.h
#ifndef ARCGRAPHICSITEM_H
#define ARCGRAPHICSITEM_H
#include <QGraphicsItem>
#include <QLineF>
#include <QPointF>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
class ArcGraphicsItem : public QGraphicsItem
{
public:
ArcGraphicsItem();
ArcGraphicsItem(int i, QPointF point0, QPointF point1, QPointF point2);
~ArcGraphicsItem();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
int type() const { return Type;}
int id() {return m_id;}
QPainterPath shape() const;
protected:
private:
void init();
enum { Type = UserType + 6 };
int m_id;
QPointF startP, midP, endP, p1, p2, p3, center;
QLineF lineBC;
QLineF lineAC;
QLineF lineBA;
QLineF lineOA;
QLineF lineOB;
QLineF lineOC;
QLineF bisectorBC;
QLineF bisectorBA;
qreal startAngle;
qreal spanAngle;
QRectF circle;
QRectF boundingRectTemp;
qreal rad;
};
#endif // ARCGRAPHICSITEM_H
arcgraphicsitem.cpp
#include "arcgraphicsitem.h"
#include "qmath.h"
#include <QPen>
#include <QDebug>
#include <QPainterPath>
ArcGraphicsItem::ArcGraphicsItem(int i,
QPointF point1,
QPointF point2,
QPointF point3)
: m_id(i), p1(point1), p2(point2), p3(point3)
{
init();
}
ArcGraphicsItem::ArcGraphicsItem()
{
p1 = QPointF(0,0);
p2 = QPointF(0,1);
p3 = QPointF(1,1);
m_id = -1;
init();
}
ArcGraphicsItem::~ArcGraphicsItem()
{
}
void ArcGraphicsItem::init()
{
lineBC = QLineF(p2, p3);
lineAC = QLineF(p1, p3);
lineBA = QLineF(p2, p1);
rad = qAbs(lineBC.length()/(2*qSin(qDegreesToRadians(lineAC.angleTo(lineBA)))));
bisectorBC = QLineF(lineBC.pointAt(0.5), lineBC.p2());
bisectorBC.setAngle(lineBC.normalVector().angle());
bisectorBA = QLineF(lineBA.pointAt(0.5), lineBA.p2());
bisectorBA.setAngle(lineBA.normalVector().angle());
bisectorBA.intersect(bisectorBC, ¢er);
circle = QRectF(center.x() - rad, center.y() - rad, rad*2, rad*2);
lineOA = QLineF(center, p1);
lineOB = QLineF(center, p2);
lineOC = QLineF(center, p3);
startAngle = lineOA.angle();
spanAngle = lineOA.angleTo(lineOC);
// Make sure that the span angle covers all three points with the second point in the middle
if(qAbs(spanAngle) < qAbs(lineOA.angleTo(lineOB)) || qAbs(spanAngle) < qAbs(lineOB.angleTo(lineOC)))
{
// swap the end point and invert the spanAngle
startAngle = lineOC.angle();
spanAngle = 360 - spanAngle;
}
int w = 10;
boundingRectTemp = circle.adjusted(-w, -w, w, w);
}
QRectF ArcGraphicsItem::boundingRect() const
{
// outer most edges
return boundingRectTemp;
}
void ArcGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
// Draw arc
if(isSelected())
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
}
else
{
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
}
QPainterPath path;
path.arcMoveTo(circle,startAngle);
path.arcTo(circle, startAngle, spanAngle);
// Draw points
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
qreal ptRad = 10;
painter->drawEllipse(p1, ptRad, ptRad);
painter->drawEllipse(p2, ptRad, ptRad);
painter->drawEllipse(p3, ptRad, ptRad);
}
painter->setBrush(Qt::NoBrush);
painter->drawPath(path);
}
QPainterPath ArcGraphicsItem::shape() const
{
QPainterPath path;
path.arcMoveTo(circle,startAngle);
path.arcTo(circle, startAngle, spanAngle);
return path;
}
Hope that helps
Correct way to draw a QGraphicsItem on QGraphicsScene
You are combining the boundingRect coordinates that are relative to the item with the coordinates relative to the scene. On the other hand, don't complicate creating a custom item, instead use a custom QGraphicsRectItem. Finally it is recommended that you establish a sceneRect.
component.h
#ifndef COMPONENT_H
#define COMPONENT_H
#include <QGraphicsRectItem>
class Component : public QGraphicsRectItem
{
public:
Component(unsigned int id, QString cname, QString ctype, QGraphicsItem *parent=nullptr);
private:
unsigned int m_id;
QString m_cname;
QString m_ctype;
};
#endif // COMPONENT_H
component.cpp
#include "component.h"
Component::Component(unsigned int id, QString cname, QString ctype, QGraphicsItem*parent):
QGraphicsRectItem(parent), m_id(id), m_cname(cname), m_ctype(ctype)
{
setRect(-40, -40, 80, 80);
setFlag(ItemIsMovable);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->graphView->setContextMenuPolicy(Qt::CustomContextMenu);
scene = new QGraphicsScene();
ui->graphView->setScene(scene);
ui->graphView->setSceneRect(QRect(0, 0, 400, 400));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_graphView_customContextMenuRequested(const QPoint &pos)
{
QPointF pp = ui->graphView->mapToScene(pos);
Component* component = new Component(s, n, t);
scene->addItem(component);
component->setPos(pp);
}
Map mouse coordinates to QGraphicsScene coordinates
Use QGraphicsView::mapToScene to convert the mouse position to the coordinates system of your scene.
Related Topics
Incomplete Class Usage in Template
Getting the Size of a C++ Function
Multiple Inheritance: Unexpected Result After Cast from Void * to 2Nd Base Class
Conversion from Derived** to Base**
Releasesemaphore Does Not Release the Semaphore
Calling a Random Number Generating Member Function Doesn't Produce Entirely Random Numbers
Default Move Constructor in Visual Studio 2013 (Update 3)
How Does the Main() Method Work in C
Speed Difference Between If-Else and Ternary Operator in C...
C++ Static_Cast Runtime Overhead
Preferred Cmake Project Structure
Effective Use of C++ Iomanip Library
Is There a Clean Way to Prevent Windows.H from Creating a Near & Far MACro
Image Retrieval System by Colour from the Web Using C++ with Openframeworks
Unique_Ptr and Openssl's Stack_Of(X509)*
How to Check If a File Has Been Opened by Another Application in C++