QListWidget拖放事件

我們可以設定元件間的拖放行為,要執行拖放的幾個基本動作為:

  1. 將setAcceptDrops()設定為true,讓此元件能夠進行拖放的動作。
  2. 建立QMimeData,設定拖放時要交換的資訊,例如文字、影像等。
  3. 建立QDrag,將QMimeData設定給QDrag,此時另外設定拖放時顯示的圖示等資訊。
  4. 執行QDrag的exec()方法,由傳回值判斷使用者所接受的放置動作,以進行後續處理。

這邊範例建立兩個自創的DnDListWidget類別,繼承自QListWidget類別,我們可以將其中的QListWidgetItem在兩者之中拖放。

dndlistwidget.h

#ifndef DNDLISTWIDGET
#define DNDLISTWIDGET

#include <QListWidget>
#include <QApplication>
#include <QPoint>
#include <QMouseEvent>
#include <QMimeData>
#include <QDrag>
#include <QListWidgetItem>
#include <QIcon>

class DnDListWidget : public QListWidget {
    Q_OBJECT

public:
    DnDListWidget(QWidget *parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void dragEnterEvent(QDragEnterEvent *event);
    void dragMoveEvent(QDragMoveEvent *event);
    void dropEvent(QDropEvent *event);
private:
    void performDrag();
    QPoint startPos;
};

#endif 

dndlistwidget.cpp

#include "dndlistwidget.h"

DnDListWidget::DnDListWidget(QWidget *parent) : QListWidget(parent) {
    setAcceptDrops(true);
}

void DnDListWidget::mousePressEvent(QMouseEvent *event) {
    if(event->button() == Qt::LeftButton){
        startPos = event->pos();
    }
    QListWidget::mousePressEvent(event);
}

void DnDListWidget::mouseMoveEvent(QMouseEvent *event){
    if (event->buttons() & Qt::LeftButton) {
        int distance = (event->pos() - startPos).manhattanLength();
        if (distance >= QApplication::startDragDistance())
            performDrag();
    }
    QListWidget::mouseMoveEvent(event);
}

void DnDListWidget::performDrag(){
    QListWidgetItem *item = currentItem();
    if (item) {
        QMimeData *mimeData = new QMimeData;
        mimeData->setText(item->text());
        mimeData->setImageData(item->icon());
        QDrag *drag = new QDrag(this);
        drag->setMimeData(mimeData);
        drag->setPixmap(item->icon().pixmap(QSize(15, 15)));
        if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
        delete item;
    }
}

void DnDListWidget::dropEvent(QDropEvent *event){
    DnDListWidget *source = qobject_cast<DnDListWidget *>(event->source());
    if (source && source != this) {
        QIcon icon = event->mimeData()->imageData().value<QIcon>();
        QString text = event->mimeData()->text();
        addItem(new QListWidgetItem(icon, text));
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}

void DnDListWidget::dragEnterEvent(QDragEnterEvent *event) {
    DnDListWidget *source = qobject_cast<DnDListWidget *>(event->source());
    if (source && source != this) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}

void DnDListWidget::dragMoveEvent(QDragMoveEvent *event) {}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QHBoxLayout>
#include"dndlistwidget.h"

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
private:
    DnDListWidget *listWidget1;
    DnDListWidget *listWidget2;
    QHBoxLayout *mainLayout;

};

#endif 

widget.cpp

#include "widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent)
{
    setWindowTitle(tr("拖放事件"));
    DnDListWidget *listWidget1 = new DnDListWidget;
    listWidget1->insertItem(0, new QListWidgetItem(QIcon("up.jpg"), tr("往上")));
    listWidget1->insertItem(1, new QListWidgetItem(QIcon("down.jpg"), tr("往下")));

    DnDListWidget *listWidget2 = new DnDListWidget;
    listWidget2->insertItem(0, new QListWidgetItem(QIcon("left.jpg"), tr("往左")));
    listWidget2->insertItem(1, new QListWidgetItem(QIcon("right.jpg"), tr("往右")));

    mainLayout = new QHBoxLayout(this);
    mainLayout->addWidget(listWidget1);
    mainLayout->addWidget(listWidget2);
}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

Drag & Drop


  • mousePressEvent()函式中,當檢測到點擊滑鼠左鍵時,記錄目前位置為startPos。
  • mouseMoveEvent()函式中,當移動時的位置和startPos的x值差的絕對值、y值差的絕對值,兩者相加(也就是manhattanLength)大於Qt預設的startDragDistance時,呼叫performDrag()。
  • performDrag()函式開始處理拖放的過程。我們創建一個QMimeData 對象,裡面儲存設定的文字和標誌資料,接著創建QDrag對象,使用QMimeData存儲數據,使用setPixmap()設定拖動時滑鼠的樣式,exec()會阻塞拖動的操作,直到使用者完成操作或者取消操作,它接受不同類型的動作作為參數,返回值是真正執行的動作,類型一般為Qt::CopyAction、Qt::MoveAction和Qt::LinkAction。返回值還可能有一個Qt::IgnoreAction用於表示使用者取消了拖放。
  • dragMoveEvent()和dropEvent()都判斷事件的來源(source),由於我們是兩個DnDListWidget之間相互拖動,所以來源也應該是DnDListWidget類別,當而且這個事件來源不能是自己,所以加上判斷式source != this,dragMoveEvent()中檢查的是被拖動的對象,dropEvent()中檢查的是放置的對象。
  • dragMoveEvent()直接覆寫保持空白,是為了要覆寫父類別的dragMoveEvent()定義,