views:

528

answers:

1

I'm trying to make a simple draggable item using the graphics framework. Here's the code for what I did so far:

Widget class:

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

};

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    DragScene *scene = new DragScene();
    DragView *view = new DragView();
    QHBoxLayout *layout = new QHBoxLayout();

    DragItem *item = new DragItem();
    view->setAcceptDrops(true);
    scene->addItem(item);
    view->setScene(scene);
    layout->addWidget(view);
    this->setLayout(layout);

}

Widget::~Widget()
{
}

DragView class:

class DragView : public QGraphicsView
{
public:
    DragView(QWidget *parent = 0);
};

DragView::DragView(QWidget *parent) : QGraphicsView(parent)
{
    setRenderHints(QPainter::Antialiasing);
}

DragScene class:

class DragScene : public QGraphicsScene
{
public:
    DragScene(QObject* parent = 0);

protected:
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
    void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
    void dragLeaveEvent(QGraphicsSceneDragDropEvent *event);
    void dropEvent(QGraphicsSceneDragDropEvent *event);
};

DragScene::DragScene(QObject* parent) : QGraphicsScene(parent)
{
}

void DragScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event){
}

void DragScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event){
}

void DragScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *event){
}

void DragScene::dropEvent(QGraphicsSceneDragDropEvent *event){
    qDebug() << event->pos();
    event->acceptProposedAction();
    DragItem *item = new DragItem();
    this->addItem(item);
    // item->setPos(event->pos()); before badgerr's tip
    item->setPos(event->scenePos());
}

DragItem class:

class DragItem : public QGraphicsItem
{
public:
    DragItem(QGraphicsItem *parent = 0);
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);

protected:
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
};

DragItem::DragItem(QGraphicsItem *parent) : QGraphicsItem(parent)
{
    setFlag(QGraphicsItem::ItemIsMovable);
}

QRectF DragItem::boundingRect() const{
    const QPointF *p0 = new QPointF(-10,-10);
    const QPointF *p1 = new QPointF(10,10);
    return QRectF(*p0,*p1);
}

void DragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
    if(painter == 0)
        painter = new QPainter();
    painter->drawEllipse(QPoint(0,0),10,10);
}

void DragItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event){

}

void DragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
}

void DragItem::mousePressEvent(QGraphicsSceneMouseEvent *event){
    QMimeData* mime = new QMimeData();
    QDrag* drag = new QDrag(event->widget());
    drag->setMimeData(mime);
    drag->exec();
}

void DragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
}

main.cpp instantiates a Widget and shows it. When I try to drag the circle, the app just creates another circle over the original one, regardless of where I release the drag. qDebug() in DragScene's dropEvent() shows QPointF(0,0) everytime the drag ends. I'm having a hard time trying to understand exactly what I have to do, which classes I should subclass, which methods needs to be overriden, to make this work. The documentation on this isn't very detailed. I'd like to know how to make this work, and if there's some other, more comprehensive resource to learn about the graphics view framework, besides the official documentation (which is excellent btw, but it would be great if there was a more detailed treatise on the subject).

EDIT:

Following badgerr's advice, I replaced item->pos() in DragScene::dropEvent() with item->scenePos(), now the drop event creates a new circle in the drop site, which is more or less what I wanted. But the original circle is still in place, and while the drag is in progress, the item doesn't follow the mouse cursor.

The QGraphicsSceneDragDropEvent documentation says that pos() should return the cursor position in relation to the view that sent the event, which, unless I got it wrong, shouldn't be (0,0) all the time. Weird.

I've read in a forum post that you can use QDrag::setPixMap() to show something during the drag, and in examples I've seen pictures being set as pixmaps, but how do I make the pixmap just like the graphics item I'm supposed to be dragging?

+1  A: 

There is an example with Qt Assistant called the "Drag and Drop Robot Example" which appears to use the QDrag method, I don't know if you've had a look at that already.

edit: Just a quick observation, you appear to be creating a new DragItem in your DropEvent, instead of using the mimeData() of the event itself, and since your item draws itself at 0,0, that might explain why you have a new one appearing at that position regardless of where you drop your DragItem.


When I wrote a graphics dragging thing similar to this, I went about it a slightly different way. Maybe it will help you:

Instead of using the QDrag stuff, I used only the mousePressEvent, mouseReleaseEvent, and mouseMoveEvent functions of QGraphicsItem. The mouse press/release set a flag indicating the drag state, and move followed a process of

  • call prepareGeometryChange();
  • update the bounds of the QGraphicsItem (my boundingRect() function returns these bounds. All my graphics item bounds are in QGraphicsScene space)
  • call update();

Then in my paint() function I draw a shape using the boundingRect().

Sorry I don't have a code sample to share, I'll knock one up if I get time.

badgerr
About the DragItem being created in DragScene::dropEvent(), the documentation on QGraphicsItem says that the overriden paint() method paints in local coordinates, also after I add the DragItem object to the scene, I move it to event->pos(). I thought event->pos() should be the point where the cursor is when I release the mouse button, thus triggering the dropEvent() method, but maybe it's not.
David McDavidson
Try event->scenePos() instead of event->pos().
badgerr
It worked! I'll update the question to mention this. There are still a few unresolved issues though.
David McDavidson