Walletfox.com

Drag and drop QGraphicsRectItem at a specific position


This article demonstrates how to implement drag and drop of a QGraphicsRectItem at a specific position. The situation is illustrated below. The orange item on the left can be dragged onto the gray items.

To obtain this functionality we need to do the following:

  • subclass QGraphicsRectItem and reimplement mouseMoveEvent() and mouseReleaseEvent()
  • introduce a boolean variable m_dragged that indicates that an item is being dragged
  • introduce a variable QPointF anchorPoint into which we place the dragged item if the user attempts to drop it in an empty area. The position of the anchorPoint in our example can be seen below.
  • subclass QMainWindow in a standard manner

How to subclass QGraphicsRectItem

Below you can see the header of the DraggableRectItem class that is a subclass of the QGraphicsRectItem with all of the above mentioned requirements.

#ifndef DRAGGABLERECTITEM_H
#define DRAGGABLERECTITEM_H

#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>

class DraggableRectItem : public QGraphicsRectItem
{
public:
    DraggableRectItem(QGraphicsItem* parent = 0);
    void setAnchorPoint(const QPointF& anchorPoint);
protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
private:
    QPointF anchorPoint;
    bool m_dragged;
};

#endif // DRAGGABLERECTITEM_H

In the constructor, we initialize the variable m_dragged and allow that the item is selectable and movable. Next, we reimplement the mouseMoveEvent(), anytime mouseMoveEvent() is invoked we change m_dragged to true.

DraggableRectItem::DraggableRectItem(QGraphicsItem* parent):
    QGraphicsRectItem(parent), m_dragged(false)
{
    setFlags(QGraphicsItem::ItemIsSelectable|
             QGraphicsItem::ItemIsMovable);
}

void DraggableRectItem::setAnchorPoint(const QPointF &anchorPoint){
    this->anchorPoint = anchorPoint;
}

void DraggableRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
    m_dragged = true;
    QGraphicsRectItem::mouseMoveEvent(event);
}

The most important part of the functionality is implemented in mouseReleaseEvent(). There are two possibilities of how the user might release the dragged item:

  1. If the user releases the dragged item outside the area of gray items, i.e. the dragged item does not collide with any of the gray items, we move the dragged item back to the anchorPoint.
  2. If the user releases the dragged item inside the area of gray items, there are some coliding gray items and we drop the dragged item onto the closest gray item.
void DraggableRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
    if(m_dragged){
        QList<QGraphicsItem*> colItems = collidingItems();
        if(colItems.isEmpty())
            this->setPos(anchorPoint);
        else {
            QGraphicsItem* closestItem = colItems.at(0);
            qreal shortestDist = 100000;
            foreach(QGraphicsItem* item, colItems){
                QLineF line(item->sceneBoundingRect().center(),
                            this->sceneBoundingRect().center());
                if(line.length() < shortestDist){
                    shortestDist = line.length();
                    closestItem = item;
                }
            }
            this->setPos(closestItem->scenePos());
        }
        m_dragged = false;
    }
    QGraphicsRectItem::mouseReleaseEvent(event);
}

The 'else' block contains the algorithm for the determination of the closest gray item. We first initialize the closestItem and the shortestDistance to arbitrary values. The shortestDistance is a helper variable for the determination of the closestItem and its initial value has to be large enough to serve as a starting point for the iteration (in this case 100000). After that, we iterate through the list of collidingItems() while determining the distance (dragged item, colliding item). The distance between the dragged item and the colliding item is represented by the distance of their centers and is computed with the help of QLineF (line.length()). If line.length() is shorter than the distance currently stored in the shortestDistance, we replace the shortestDistance with line.length() and set the closestItem to the current item. Otherwise, we don't do anything. Finally, we set the position of the dragged item to the position of the closestItem.

How to use DraggableRectItem

The code snippet below shows how to use our new class. The first three items are standard gray QGraphicsRectItems, The dItem is an instance of DraggableRectItem. Notice that for the draggable item I set the anchorPoint to the original item's position, but in general, I can set the anchorPoint to any other point.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    scene = new QGraphicsScene(this);
    scene->setSceneRect(0,0,280,240);
    view = new QGraphicsView(scene);
    setCentralWidget(view);

    QRectF rect(0,0,80,40);
    QBrush myBrush(Qt::darkGray, Qt::Dense5Pattern);

    QGraphicsRectItem *rItem1 = new QGraphicsRectItem(rect);
    scene->addItem(rItem1);
    rItem1->setPos(160,40);
    rItem1->setBrush(myBrush);

    QGraphicsRectItem *rItem2 = new QGraphicsRectItem(rect);
    scene->addItem(rItem2);
    rItem2->setPos(160,100);
    rItem2->setBrush(myBrush);

    QGraphicsRectItem *rItem3 = new QGraphicsRectItem(rect);
    scene->addItem(rItem3);
    rItem3->setPos(160,160);
    rItem3->setBrush(myBrush);

    DraggableRectItem* dItem = new DraggableRectItem;
    scene->addItem(dItem);
    dItem->setRect(rect);
    dItem->setPos(30,100);
    dItem->setBrush(QBrush(QColor("#ffa07a")));
    dItem->setAnchorPoint(dItem->pos());
}

That's it! You can try out the example.