Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 called setAcceptDrops(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


  • Lifetime Qt Champion

    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?



  • @sticky-thermos

    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, no dropEvents 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, the chessPiece accept drops and not the ChessBoard.

    (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 the robotParts, transferring the corresponding color by passing the mimeData.



  • @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 a dragEnter was equivalent to a mousePress, and a dragLeave to a mouseRelease. Thanks for that bit of info :) I'm going to tweak the design a bit with this new info, maybe try getting the ChessBoard to acceptDrops 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) to QGraphicsView::mousePressEvent(event).
    QGraphicsView::mouseMoveEvent would be more fitting :)