工作上用 QT 已經有一陣子了,雖然部門裡只有我一個人在用,但是其它人在用的 Java 和 C# 看起來實在不熟悉,感覺入門又要更久了。學習QT的過程中,也是一直拼拼湊湊,比起純粹的C,原理的確是不求甚解。想說把自己學習 QT 的知識,紀錄下來,這樣下次就不用再拼湊了~ 畢竟這不是工作中的主要程式語言,不常用記不住。
QT 使用版本
本文 QT 使用的版本是 qt-opensource-windows-x86-mingw492-5.6.2.exe,可以直接在網上搜檔名,以後位置如果變更也可以下載的到。安裝的話,一直下一步即可。之前測試的過程中,發現有些例子在不同版本會不相容,請儘量使用此版本,以確保可產生同樣的結果。
導入 Dragdrop 範例
開啟 「Qt Creator」,選擇「範例」,再搜尋中選擇輸入 「drag」,選擇 「Draggable Icons Example」。
畫面會跳出一個說明,直接關掉即可。還有一個 Configure Project 的對話框,直接按下「Configure Project」即可。
接著按下執行,可以看到出現左右兩個區域,裡面的 ICON 在同區域拖拉會移動位置,而跨區則可複製一份。
程式碼說明
這個範例的主體是 class DragWidget,是由 QFrame 所洐生出來的,其包含4個成員函式。
- mousePressEvent: 滑鼠按下,準備開始拖拉
- dragEnterEvent: 拖拉進入區域
- dragMoveEvent: 拖拉移動
- dropEvent: 滑鼠放開 drop
main() 中會產生一個 QHBoxLayout ,然後使用 addWidget 來加入兩個 dragWidget 的 instances。dragWidget 的 constructor 會產生三個 icon,這邊的 icon 是以 QLabel 來實現的,其圖示是以 setPixmap() 來指定圖檔,造成 icon 的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
DragWidget::DragWidget(QWidget *parent) : QFrame(parent) { setMinimumSize(200, 200); setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); setAcceptDrops(true); QLabel *boatIcon = new QLabel(this); boatIcon->setPixmap(QPixmap(":/images/boat.png")); boatIcon->move(10, 10); boatIcon->show(); boatIcon->setAttribute(Qt::WA_DeleteOnClose); QLabel *carIcon = new QLabel(this); carIcon->setPixmap(QPixmap(":/images/car.png")); carIcon->move(100, 10); carIcon->show(); carIcon->setAttribute(Qt::WA_DeleteOnClose); QLabel *houseIcon = new QLabel(this); houseIcon->setPixmap(QPixmap(":/images/house.png")); houseIcon->move(10, 80); houseIcon->show(); houseIcon->setAttribute(Qt::WA_DeleteOnClose); } |
mousePressEvent()
mousePresssEvent() 是在滑鼠按下時觸發的,程式首先透過 childAt(event->pos())取得該位置的QLabel元件,有取到才繼續。透過一些資料的操作,該 Label 的 pixmap 與 pos 被存入了 QDrag 的新物件中,QDrag 的 pixmap 也被設為拖拉的圖示,而原 QLabel 的 pixmap 則被改為網點。
最後呼叫了 drag->exec () 來執行拖拉的行為,當拖拉行為返回時,若動作是 MOVE,則舊的 QLabel 會被 close (刪除)。否則則是恢復其 pixmap,顯示原狀。這也就是一開始講的,同區域就移動跨區域就複製。所以所謂的 MOVE,其實是在新位置複製一份,再刪除舊的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
void DragWidget::mousePressEvent(QMouseEvent *event) { QLabel *child = static_cast<QLabel*>(childAt(event->pos())); if (!child) return; QPixmap pixmap = *child->pixmap(); QByteArray itemData; QDataStream dataStream(&itemData, QIODevice::WriteOnly); dataStream << pixmap << QPoint(event->pos() - child->pos()); QMimeData *mimeData = new QMimeData; mimeData->setData("application/x-dnditemdata", itemData); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->setPixmap(pixmap); drag->setHotSpot(event->pos() - child->pos()); QPixmap tempPixmap = pixmap; QPainter painter; painter.begin(&tempPixmap); painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127)); painter.end(); child->setPixmap(tempPixmap); if (drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction) { child->close(); } else { child->show(); child->setPixmap(pixmap); } } |
dragEnterEvent() and dragMoveEvent()
這兩個 event 分別是在移到和進入不同區域時呼叫,首先判斷這個 event 的來源是不是 “application/x-dnditemdata”,這是在一開始 drag 時設置的,若有才代表是原先的動作,不是則忽略。
接著判斷來源於是否等於目前的區域,若是同區的,則動作永遠設為 MOVE。不同區則依照系統的,這也是符合同區為移動,跨區為產生新的行為設定。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void DragWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("application/x-dnditemdata")) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } } else { event->ignore(); } } |
dropEvent()
最後則是當滑鼠放開的 dropEvent,如同前面,也是先判斷這個 event 的來源是不是 “application/x-dnditemdata”。將之前存入 QDrag 的一些參數,從 QDropEvent 中取出。接著產生一個新的 QLabel,設定其 pixmap 與位置。接著一樣是根據其來源,設定其為 MOVE 或接受系統設定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void DragWidget::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat("application/x-dnditemdata")) { QByteArray itemData = event->mimeData()->data("application/x-dnditemdata"); QDataStream dataStream(&itemData, QIODevice::ReadOnly); QPixmap pixmap; QPoint offset; dataStream >> pixmap >> offset; QLabel *newIcon = new QLabel(this); newIcon->setPixmap(pixmap); newIcon->move(event->pos() - offset); newIcon->show(); newIcon->setAttribute(Qt::WA_DeleteOnClose); if (event->source() == this) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } } else { event->ignore(); } } |
透過上面這幾個 event 的實現,就可以完成 dragdrop 的行為了。
結語
乍看之下有點不太懂,但仔細花點時間琢磨,還是能懂程式在寫什麼 : )
Latest Comments