I want to make one QGraphicsItem in a QGraphicsScene move (or change size) when another one moves. But, trying to access either QGraphicsItem from the QGraphicsScene object causes crashes.
Here's a dummy example. I want to automatically:
Resize BlueItem's width when I click and drag RedItem.
Move RedItem when I click and drag BlueItem.
The dummy code:
redItem.h (A pixmap item)
#include <QGraphicsPixmapItem>
class RedItem : public QGraphicsPixmapItem
{
public:
RedItem();
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
};
redItem.cpp
RedItem::RedItem()
{
setPixmap(QPixmap(redPixMap));
setFlags(ItemIsMovable);
setCacheMode(DeviceCoordinateCache);
}
void RedItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
setPos(event->scenePos(),0); //I can override this part just fine.
}
blueItem.h (A resizable rectangle item)
#include <QGraphicsRectItem>
class BlueItem : public QGraphicsRectItem
{
public:
BlueItem();
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
};
blueItem.cpp is similar to redItem.cpp
graphicsViewWidget.h (The GraphicsView)
#include <QGraphicsView>
#include "blueItem.h"
#include "redItem.h"
class GraphicsViewWidget : public QGraphicsView
{
Q_OBJECT
public:
explicit GraphicsViewWidget(QWidget *parent = 0);
protected:
virtual void mouseMoveEvent(QMouseEvent *event);
private:
RedItem *red;
BlueItem *blue;
};
graphicsViewWidget.cpp
#include "graphicsViewWidget.h"
#include <QRectF>
void Qx::createItem(int frame)
{
red = new RedItem;
red->setPos(0, 0);
scene()->addItem(red);
blue = new BlueItem;
blue->setPos(10, 10);
scene()->addItem(blue);
}
void GraphicsViewWidget::mouseMoveEvent(QMouseEvent *event) //QGraphicsSceneMouseEvent
{
QGraphicsView::mouseMoveEvent(event);
//if(redItem moves)
// resize blueItem;
//if(blueItem moves)
// move redItem;
}
Any help or advice would be greatly appreciated.
If you make item B a child of item A, then it will live in its coordinate space. This means all transformations - moving, scaling, rotating... everything you apply to the parent item will also be applied to all its children and their children and so on...
Related
I am getting an error I don't know how to resolve.
I created a class CustomScene that inherits QGraphicsScene and I want to override the mouse functions in this class.
I am trying to create a rectangle on the scene by dragging and dropping and when even I try to get the position of the mouse using event->pos().x() I get QGraphicsSceneMouseEvent member access to incomplete type QGraphicsSceneMouseEvent
#ifndef CUSTOMSCENE_H
#define CUSTOMSCENE_H
#include <QGraphicsScene>
#include <customrectitem.h>
class CustomScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit CustomScene(QObject *parent = nullptr);
QGraphicsScene* scene = new QGraphicsScene;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif // CUSTOMSCENE_H
#include "customscene.h"
#include <QDebug>
CustomScene::CustomScene(QObject *parent)
: QGraphicsScene{parent}
{
}
void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "the scene know that the mouse is pressed";
QGraphicsRectItem* rect = new QGraphicsRectItem(event->pos().x(),event->pos.y(),100,100);
//the line of the error
this->addItem(rect);
}
void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "the scene know that the mouse is moving";
}
void CustomScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "the scene know that the mouse is released";
}
A "member access to incomplete type" usually happens when you are trying to work with a type (i.e. call a method) that has only been declared using forward declaration.
In this case QGraphicsSceneMouseEvent is forward declared in qgraphicsscene.h. The actual declaration is in qgraphicssceneevent.h. To use that just put
#include <QGraphicsSceneMouseEvent>
in your source. Note that this is also explicitly stated in the first paragraph of the documentation.
I've the following situation:
Basically I've a QTreeWidget with some items, and a canvas that's a subclass of a QGraphicsView. What I want to accomplish is to drag a QTreeWidgetItem in the QGraphicsView subclass. When the mouse is released in the QGraphicsView subclass I want to create a new shape (let's say a circle) with some data of the dragged item (let's say the name itself written inside the circle).
I was able to enable the drag from the QTreeWidget by using the setDragEnabled(true) method, and I'm able to receive the event in the QGraphicsView, because when I enter its area it calls the dragEnterEvent.
In my subclass I've a QGraphicsScene, like this:
#ifndef SEQUENCECANVAS_HPP_
#define SEQUENCECANVAS_HPP_
#include <QWidget>
#include <QGraphicsScene>
#include <QGraphicsView>
class SequenceCanvas : public QGraphicsView {
Q_OBJECT
public:
SequenceCanvas(QWidget* parent = nullptr);
virtual ~SequenceCanvas() = default;
protected:
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
private:
void createWidgets();
void createLayout();
private:
QGraphicsScene m_scene;
};
#endif // !SEQUENCECANVAS_HPP_
#include "SequenceCanvas.hpp"
#include <QString>
#include <QHBoxLayout>
#include <QDragEnterEvent>
#include <QMimeData>
SequenceCanvas::SequenceCanvas(QWidget* parent) :
QGraphicsView(parent) {
createWidgets();
createLayout();
setScene(&m_scene);
setAcceptDrops(true);
}
void SequenceCanvas::dragEnterEvent(QDragEnterEvent* event) {
event->acceptProposedAction(); // no filter at the moment I just want to test
}
void SequenceCanvas::dropEvent(QDropEvent* event) {
int i = 0; // just for putting a breakpoint now. Its breakpoint is never raised
}
void SequenceCanvas::createWidgets() {
}
void SequenceCanvas::createLayout() {
}
Even if the dragEnterEvent is called when my mouse enter my widget during the drag, the dropEvent method is not called, so when I release the mouse inside my SequenceCanvas class nothing happens.
Basically I've two questions:
How can I raise the drop event inside my SequenceCanvas class that's a subclass of QGraphicsView?
How can I pass custom data (let's say a vector of strings) with the drop event, so I can pass all needed information for managing the event?
EDIT:
I missed to reimplement the void dragMoveEvent(QDragMoveEvent* event) method. I've added it and now I receive drop events. I need to know how to pass custom data with a drag-drop event now... Thanks to zeFrenchy.
My class body now is:
SequenceCanvas::SequenceCanvas(QWidget* parent) :
QGraphicsView(parent) {
createWidgets();
createLayout();
setScene(&m_scene);
setAcceptDrops(true);
bool ok = acceptDrops();
}
///////////////////////////////////////////////////////////////////////////////
// VIRTUAL PROTECTED SECTION //
///////////////////////////////////////////////////////////////////////////////
void SequenceCanvas::dragEnterEvent(QDragEnterEvent* event) {
event->acceptProposedAction();
}
void SequenceCanvas::dragMoveEvent(QDragMoveEvent* event) {
event->acceptProposedAction();
}
void SequenceCanvas::dropEvent(QDropEvent* event) {
int i = 0;
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE SECTION //
///////////////////////////////////////////////////////////////////////////////
void SequenceCanvas::createWidgets() {
}
void SequenceCanvas::createLayout() {
}
The documentation says the following about DragMoveEvent:
A widget will receive drag move events repeatedly while the drag is within its boundaries, if it accepts drop events and enter events. The widget should examine the event to see what kind of data it provides, and call the accept() function to accept the drop if appropriate.
So you should be provide an override for dragMoveEvent(QDragMoveEvent* event) in which you simply accept the proposed action.
When I'm using a QTreeWidget (via qdesigner) with the dragDropMode set to InternalMove and then attempt to move one of the items, the scrollbar snaps back to the original position of the item I moved. I would like the scrollbar to stay at the position the item was dropped. Is there an easy way to do that? I adjusted the defaultDropAction, but to no avail.
Figured it out...
class MyTree : public QTreeWidget
{
Q_OBJECT
public:
MyTree(QWidget *parent = 0);
signals:
void verticalScrollBarValue(int val);
protected slots:
void setTreeVerticalScroll(int val);
protected:
void dropEvent(QDropEvent *event);
};
MyTree::MyTree(QWidget *parent) : QTreeWidget(parent)
{
connect(this, &MyTree::verticalScrollBarValue,
this, &MyTree::setTreeVerticalScroll);
}
void MyTree::setTreeVerticalScroll(int val)
{
verticalScrollbar()->setValue(val);
}
void MyTree::dropEvent(QDropEvent *event)
{
int v = verticalScrollbar()->value();
QTreeWidget::dropEvent(event);
emit verticalScrollBarValue(v);
}
Disclaimer: There may be a typo in there somewhere, I can't copy directly from my work environment.
I am doing a project in Qt and there is an object QPainter which is declared as :
QPainter painter(this);
Where this points to the present class. My problem is that I need to declare this object such that it is accessible to the entire class functions.
If I declare it inside the constructor then its scope is not valid for other functions, and I cannot declare outside all function in my .cpp file as this variable doesn't make any sense.
So how can I declare my object such that it is accessible to all the functions?
Edit : Painter Code :
void MainWindow :: paintEvent(QPaintEvent * e)
{
QMainWindow::paintEvent(e);
if(1)
{
QPainter painter(this);
QPen paintpen(Qt::red);
paintpen.setWidth(5);
QPoint p1;
p1.setX(mFirstX);
p1.setY(mFirstY);
painter.setPen(paintpen);
painter.drawPoint(p1);
}
}
Mouse Event Code :
void MainWindow :: mousePressEvent(QMouseEvent *e)
{
mFirstX=0;
mFirstY=0;
mFirstClick=true;
mpaintflag=false;
if(e->button() == Qt::LeftButton)
{
//store 1st point
if(1)
{
mFirstX = e->x();
mFirstY = e->y();
mFirstClick = false;
mpaintflag = true;
qDebug() << "First image's coordinates" << mFirstX << "," << mFirstY ;
update();
}
}
}
PROBLEM : I want to create the point but I don't want it to disappear from the widget(mainWindow). Here since the object is created during the paintEvent method each time the point that I am drawing is disappearing when the next point is drawn.
And if I declare it outside the paintEvent then I get the following error:
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
In general, declaring the QPainter object globally is not a good idea. It is better to construct it in the QWidget::paintEvent (where it will be active) and, as #Mat said, pass it (by reference) to the functions that need it.
In your particular case the point disappears because the widget is redrawn, not because the QPainter is destroyed. So the strategy should be to create and manage a buffer of points. Add a point to the buffer in a mouse event, e.g. QWidget::mousePressEvent (there do some management too, e.g. limiting the number of points) and in the paint event - paint all the points from the buffer.
Here is an oversimplified example which could easily be adapted for your specific purpose:
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
QList<QPoint> m_points;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QPalette p = QPalette();
p.setColor(QPalette::Window, Qt::white);
setPalette(p);
setAutoFillBackground(true);
resize(400, 400);
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
m_points.append(event->pos());
update();
}
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setClipping(true);
painter.setClipRect(event->rect());
painter.setPen(QPen(Qt::red, 5));
foreach (QPoint point, m_points) { painter.drawPoint(point); }
}
That's what class attributes are for :
class MyObject : public QObject {
Q_OBJECT
public:
MyObject(QObject *parent = 0) : QObject(parent), painter_(this) {
// Other things if you need them
}
void aFunction() {
// You can use painter_ here !!
}
private :
QPainter painter_;
}
In the private access specifier of your main window class in mainWindow.h, you could write this line
QPainter *painterPointer
creating a pointer accessible in all MainWindow slots. You could use that painterPointer as though it were any painter pointer initialised in any slot. However if you wanted to use this pointer in a developer defined function or method outside of the MainWindow class, you would have to pass the pointer as a reference (or pointer to the pointer).
might have to include a line similar to this in the MainWindow Constructor
painterPointer = new QPainter(this);
I want my QGraphicsPixmapItem become selectable (i.e. clickable in more general way) on QGraphicScene but it doesn't. I'm actually modifying Qt's Diagram Scene sample, where QGraphicsItem's subclass is used and is selectable. I appreciate your help.
cpp code (partial):
#include <iostream>
#include <QtGui>
#include "overlayPixmapItem.h"
OverlayPixmapItem::OverlayPixmapItem(DiagramType diagramType, QMenu *contextMenu,
QPixmap img, QGraphicsItem *parent,
QGraphicsScene *scene)
: QGraphicsPixmapItem(img, parent), QObject()
{
myDiagramType = diagramType;
myContextMenu = contextMenu;
this->setAcceptsHoverEvents(true);
this->setAcceptHoverEvents(true); //
this->setAcceptedMouseButtons(Qt::LeftButton);
this->setAcceptedMouseButtons(Qt::RightButton);
this->acceptDrops();
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
this->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
}
:
header (partial):
#ifndef OVERLAYPIXMAPITEM_H
#define OVERLAYPIXMAPITEM_H
#include <QGraphicsPixmapItem>
#include <QList>
#include <QObject>
class QPixmap;
class QGraphicsItem;
class QGraphicsScene;
class QTextEdit;
class QGraphicsSceneMouseEvent;
class QMenu;
class QGraphicsSceneContextMenuEvent;
class QPainter;
class QStyleOptionGraphicsItem;
class QWidget;
class QPolygonF;
class OverlayPixmapItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
public:
enum { Type = UserType + 15 };
enum DiagramType { Crosshair };
OverlayPixmapItem(DiagramType diagramType, QMenu *contextMenu,
QPixmap img, QGraphicsItem *parent = 0, QGraphicsScene *scene = 0);
DiagramType diagramType() const { return myDiagramType; }
QPolygonF polygon() const { return myPolygon; }
int type() const { return Type;}
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
void hoverEnterEvent ( QGraphicsSceneHoverEvent * event );
void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event );
public: signals:
void mouseHoveredOnElem(OverlayPixmapItem *i);
void mouseHoveredOutOfElem(OverlayPixmapItem *i);
private:
DiagramType myDiagramType;
QPolygonF myPolygon;
QMenu *myContextMenu;
};
#endif // OVERLAYPIXMAPITEM_H
As pointed out in my first comment, you call setAcceptedMouseButtons() method twice. While the first call actually set the correct button to be accepted, the second call make this setting lost.
To accept both buttons on this item, you have to combine Qt MouseButtons flags, this way :
item->setAcceptedMouseButtons(Qt::LeftButton|Qt::RightButton) ;