实现QGraphicsItem拖拽功能

目录

Qt 的 Grpahics View 框架提供拖拽功能支持,如拖拽图片以移动滚动条。但如果需要更复杂的交互功能,就需要编写自己的 QGraphicsItem 类,重新实现相关方法。

拖拽简介

拖拽(Drag and Drop)是多种动作的组合,包括:
按住鼠标左键
移动鼠标
在另外一个位置释放鼠标
涉及两个对象:源对象和目标对象。

需要实现的Qt事件

源对象需要处理 mouse 事件,目标对象需要处理 drag 和 drop 事件。
源对象事件:
mousePressEvent:点击左键
mouseMoveEvent:拖动鼠标
mouseReleaseEvent:释放鼠标
目标对象事件:
dragEnterEvent:拖动对象进入目标对象
dragLeaveEvent:拖动对象离开目标对象
dropEvent:拖动对象到目标对象中

数据结构

拖拽是一种交互信息的方式,拖拽的其实是数据,当然这数据可以在拖拽时生成。拖拽时会生成一个 QMimeData 对象,保存拖拽的数据。我需要实现一种特殊的功能,保存某个对象的指针,默认的 QMimeData 中无法保存指针,需要自定义。根据文档,继承 QMimeData 需要实现三个方法:hasFormat()、formats()、retrieveData()。
接着在目标对象的 dropEvent 函数中,识别我定义的特殊 MimeData。通过 qobject_cast 判断 event 的 memeData() 是否能强制转换到自定义的 mime data。

const WorkSpaceGraphicsImageItemMimeData *mime_data =
        qobject_cast<const WorkSpaceGraphicsImageItemMimeData *>(event->mimeData());
if (mime_data) {
// 使用自定义的方法获取 mime data 中的值
}

我的 MimeData 定义如下:

class WorkSpaceGraphicsImageItemMimeData : public QMimeData
{
    Q_OBJECT
public:
    WorkSpaceGraphicsImageItemMimeData();
    ~WorkSpaceGraphicsImageItemMimeData();
    virtual bool hasFormat ( const QString & mimeType ) const;
    virtual QStringList formats () const;
    WorkSpaceContent *getWorkSpaceContent() const;
    void setWorkSpaceContent(WorkSpaceContent *value);
protected:
    virtual QVariant retrieveData ( const QString & mimeType, QVariant::Type type ) const;
private:
    QString mime_type_;
    WorkSpaceContent *work_space_content;
};

保存 WorkSpaceContent 指针,上面 dropEvent 函数通过 getWorkSpaceContent() 获取该指针。

实现

源对象

mousePressEvent:改变鼠标指针

void WorkSpaceGraphicsImageItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if( FormularAction == interaction_action_type_)
    {
        setCursor(Qt::ClosedHandCursor);
    }
    else
    {
        QGraphicsPixmapItem::mousePressEvent(event);
    }
}

mouseMoveEvent:生成 QDrag 对象,装入 WorkSpaceGraphicsImageItemMimeData

void WorkSpaceGraphicsImageItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if( FormularAction == interaction_action_type_)
    {
        if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
                .length() < QApplication::startDragDistance()) {
            return;
        }
        QDrag *drag = new QDrag(event->widget());
        WorkSpaceGraphicsImageItemMimeData *mime = new WorkSpaceGraphicsImageItemMimeData();
        mime->setWorkSpaceContent(work_space_content_);
        drag->setMimeData(mime);
        setCursor(Qt::ClosedHandCursor);
        drag->exec();
    }
    else
    {
        QGraphicsPixmapItem::mouseMoveEvent(event);
    }
}

mouseReleaseEvent:恢复鼠标指针

void WorkSpaceGraphicsImageItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if( FormularAction == interaction_action_type_)
    {
        setCursor(Qt::OpenHandCursor);
    }
    else
    {
        QGraphicsPixmapItem::mouseReleaseEvent(event);
    }
}

目标对象

需要在构造函数中设置接受拖拽:

setAcceptDrops(true);

dragEnterEvent:判断是否是特定的拖拽操作,只接受上述拖拽。

void WorkSpaceGraphicsImageItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
    const WorkSpaceGraphicsImageItemMimeData *mime_data =
            qobject_cast<const WorkSpaceGraphicsImageItemMimeData *>(event->mimeData());
    if (mime_data) {
        event->setAccepted(true);
    }
    else
    {
        QGraphicsPixmapItem::dragEnterEvent(event);
    }
}

dropEvent:接受拖拽事件,执行拖拽后的操作

void WorkSpaceGraphicsImageItem::dropEvent(QGraphicsSceneDragDropEvent *event)
{
    const WorkSpaceGraphicsImageItemMimeData *mime_data =
            qobject_cast<const WorkSpaceGraphicsImageItemMimeData *>(event->mimeData());
    if (mime_data) {
        WorkSpaceContent *source_work_space_content = mime_data->getWorkSpaceContent();
        WorkSpaceContent *target_work_space_content = work_space_content_;
        if(source_work_space_content == target_work_space_content)
        {
            qWarning()<<"[WorkSpaceGraphicsImageItem] dropEvent: image is same.";
            return;
        }
        QMessageBox::information(0, "Image Viewer", "WorkSpaceGraphicsImageItem::dropEvent");
    }
    else
    {
        QGraphicsPixmapItem::dropEvent(event);
    }
}