Receive Drag/Drop events for items in QGraphicsScene
-
Hi,
I'm new to working with QT (though experienced in C++). I'm trying to make a basic chess game, and I've come across the QGraphicsView framework. I think this may be a better fit than using a QGridLayout - if I should turn back now please let me know :)I am using QT 6
I've created my own classes that derive from QGraphicsView, QGraphicsScene and QGraphicsPixmapItem in hopes of being able to handle the events that are sent to each. My CustomView (derives from QGraphicsView) creates the CustomScene (derives from QGraphicsScene) and adds a few CustomItems (derives from QGraphicsPixmapItem). I'm able to see these pop up on the screen and can drag the items around with my mouse - so far so good.
In the CustomView, I can get and handle mousemove events. I can make do with that, but I see that there are also events about dragging and dropping. However, I have never received these events. When does this event occur?
I have tried overriding the drag/drop events in my CustomItems, but I don't ever receive those either (and I've calledsetAcceptDrops(true)
).So my question is can I receive drag and drop events on the individual pixmap items on a QGraphicsScene, or do I have to figure it out manually using the mouse events on the QGraphicsView?
Thanks in advance
-
Hi and welcome to devnet,
Can you share your CustomItems implementation ?
-
Have you checked out the Robot Drag and Drop Example?
-
@SGaist
Thanks! I've included the code below. Locally, I have the code split into headers and source files but I rewrote it inline for readability here.
Also, I lied about the names (CustomView, CustomScene etc) to keep it simple; most of the names below are still self explanatory:// main.cpp #include "Controller.h" #include <QApplication> #include <iostream> int main(int argc, char *argv[]) { QApplication a(argc, argv); Controller w; w.show(); w.setup(); return a.exec(); }
// Controller / main window #include "Controller.h" #include "ui_chess.h" #include "ChessBoard.h" #include <QLayout> #include <QBrush> #include <QPen> #include <QGraphicsPixmapItem> #include <QMainWindow> #include <QPainter> #include <QGridLayout> #include <iostream> #include <memory> QT_BEGIN_NAMESPACE namespace Ui { class Chess; } QT_END_NAMESPACE class Controller : public QMainWindow { public: Q_OBJECT Controller(QWidget *parent) : QMainWindow(parent) , ui(new Ui::Chess) , m_Layout(new QGridLayout()) , m_ChessBoard(new ChessBoard(this)) // ChessBoard is a QGraphicsView* { ui->setupUi(this); QWidget *widget = new QWidget(); widget->setLayout(m_Layout); setCentralWidget(widget); m_Layout->setObjectName("TopLevelGridLayout"); // future plans to add some stuff here, e.g. clock, moves, score etc m_Layout->addWidget(new QWidget(), 0,0); m_Layout->addWidget(new QWidget(), 0,1); m_Layout->addWidget(new QWidget(), 0,5); m_Layout->addWidget(m_ChessBoard, 1,1,3,3); } // separated part of init because I'm not sure what's allowed to happen inside of constructor void setup() { m_ChessBoard->setup(); } private: std::unique_ptr<Ui::Chess> ui; QGridLayout* m_Layout; ChessBoard* m_ChessBoard; };
// Chessboard -> contains the main ChessBoard class and some other minor classes #include <QGraphicsView> #include <QGraphicsScene> #include <QObject> #include <QWidget> #include <QPaintEvent> // For now this class does nothing special class ChessScene : public QGraphicsScene { public: ChessScene(QWidget* parent) : QGraphicsScene(parent) { } }; class ChessPiece : public QGraphicsPixmapItem { public: ChessPiece(const QPixmap &pixmap, QGraphicsItem *parent, std::string name) : QGraphicsPixmapItem(pixmap, parent) , m_name(name) { setAcceptDrops(true); // I think this line is the crucial part, but it still doesn't seem to work... } protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override { std::cout << "Piece: " << m_name << '\t' << __FUNCTION__ << std::endl; QGraphicsPixmapItem::dragEnterEvent(event); } void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override { std::cout << "Piece: " << m_name << '\t' << __FUNCTION__ << std::endl; QGraphicsPixmapItem::dragLeaveEvent(event); } void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override { std::cout << "Piece: " << m_name << '\t' << __FUNCTION__ << std::endl; QGraphicsPixmapItem::dragMoveEvent(event); } void dropEvent(QGraphicsSceneDragDropEvent *event) override { std::cout << "Piece: " << m_name << '\t' << __FUNCTION__ << std::endl; QGraphicsPixmapItem::dropEvent(event); } std::string m_name; }; class ChessBoard : public QGraphicsView { Q_OBJECT public: ChessBoard(QWidget* parent) : QGraphicsView(parent) , m_scene(new ChessScene(this)) { setObjectName(QString("ChessBoardView")); setScene(m_scene); } QSize minimumSizeHint() const override { return {kMinWidth, kMinHeight}; } void setup() { addPiecesToScreen(); setSizeIncrement(1,1); } QGraphicsScene* getScene() { return m_scene; } /* Events */ protected: void dragEnterEvent(QDragEnterEvent *event) { std::cout << __FUNCTION__ << std::endl; } void dragLeaveEvent(QDragLeaveEvent *event) { std::cout << __FUNCTION__ << std::endl; } void dragMoveEvent(QDragMoveEvent *event) { std::cout << __FUNCTION__ << std::endl; } void dropEvent(QDropEvent *event) { std::cout << __FUNCTION__ << std::endl; } void mouseDoubleClickEvent(QMouseEvent *event) { std::cout << __FUNCTION__ << std::endl; QGraphicsView::mouseDoubleClickEvent(event); } void mouseMoveEvent(QMouseEvent *event) { std::cout << __FUNCTION__ << std::endl; QGraphicsView::mouseMoveEvent(event); } void mousePressEvent(QMouseEvent *event) { std::cout << __FUNCTION__ << std::endl; QGraphicsView::mousePressEvent(event); } void mouseReleaseEvent(QMouseEvent *event) { std::cout << __FUNCTION__ << std::endl; QGraphicsView::mouseReleaseEvent(event); } private: void addPiecesToScreen() { auto cp = new ChessPiece(QPixmap{":/resources/assets/bK.png"}, nullptr, "BlackKing"); cp->setFlags(QGraphicsItem::GraphicsItemFlag::ItemIsFocusable | QGraphicsItem::GraphicsItemFlag::ItemIsMovable); m_scene->addItem(cp); cp = new ChessPiece(QPixmap{":/resources/assets/wK.png"}, nullptr, "WhiteKing"); cp->setFlags(QGraphicsItem::GraphicsItemFlag::ItemIsFocusable | QGraphicsItem::GraphicsItemFlag::ItemIsMovable); m_scene->addItem(cp); } private: QGraphicsScene* m_scene; static constexpr const uint8_t kMinWidth { 50 }; static constexpr const uint8_t kMinHeight { kMinWidth }; };
When this runs, I'm able to see prints of Mouse[Press/Move/Release]Events coming from the ChessBoard.
I expect to see those, in addition to drag[Enter/Leave/Move]Events and dropEvents, from both the ChessBoard and the ChessPiece (the QGraphicsPixmapItem).
I'd appreciate any feedback!
-
@mchinand I hadn't seen it until you pointed it out. It looks very relevant, but I can't seem to find the problem in my own code. I'm successfully calling
setAcceptDrops(true)
as it mentions. Are there other changes between QT 5 and QT 6? -
Haven't used d'n'd that much but the difference I see between your code and the example is, that you don't create a
QDrag
object to initalize the dnd process in your mouse handlers.
I dont know if this is necessary in your case. Like I said, haven't used dropEvent in this way before :)Edit:
Finally we execute the drag. QDrag::exec() will reenter the event loop, and only exit if the drag has either been dropped, or canceled. In any case we reset the cursor to Qt::OpenHandCursor.
So, no
QDrag
object, nodropEvents
on the destination widget.Also your logic seems a bit wrong. The to-be-dropped widget has to create the Drag object and the drop destination has to
acceptDrops
.
If I have seen correctly, your to-be-dropped object, thechessPiece
accept drops and not theChessBoard
.(Assuming you want to dnd your chess figures onto the board and move them around by dnd them on one valid field on the chessBoard)
To stay with the example:
ChessPiece
=ColorCircle
ChessBoard
=RobotParts
In the example, the
colorCircles
are dropped on therobotParts
, transferring the corresponding color by passing themimeData
. -
@Pl45m4 Thanks! You're right, I was missing the
QDrag
object. I created that and I'm getting the drop events on the objects now. I had the wrong understanding about drag and drop: I thought that adragEnter
was equivalent to amousePress
, and adragLeave
to amouseRelease
. Thanks for that bit of info :) I'm going to tweak the design a bit with this new info, maybe try getting theChessBoard
toacceptDrops
instead as you mentioned.For anyone in the future, this is what I have for the
QGraphicsView::mouseMoveEvent
:void ChessBoard::mouseMoveEvent(QMouseEvent *event) { std::cout << __FUNCTION__ << std::endl; QGraphicsView::mousePressEvent(event); QDrag *drag = new QDrag(this); QMimeData *mime = new QMimeData; // this is necessary even if nothing is set in the mime data drag->setMimeData(mime); drag->exec(); }
Thank you everyone for your help!
-
@sticky-thermos said in Receive Drag/Drop events for items in QGraphicsScene:
void ChessBoard::mouseMoveEvent(QMouseEvent *event) {
std::cout << FUNCTION << std::endl;
QGraphicsView::mousePressEvent(event);
QDrag *drag = new QDrag(this);
QMimeData *mime = new QMimeData; // this is necessary even if nothing is set in the mime data
drag->setMimeData(mime);
drag->exec();
}Looks good, but I would add a check to differenciate whether your mouseButton is down. Otherwise you are creating a lot of drags from just moving your mouse around on your
chessBoard
.Also, you are passing the
ChessBoard::mouseMoveEvent(QMouseEvent *event)
toQGraphicsView::mousePressEvent(event)
.
QGraphicsView::mouseMoveEvent
would be more fitting :)