拖放,拖動和釋放(Drag and Drop),允許用戶利用鼠標在不同組件之間或一個組件上進行數據的拖動。如果哪個組件支持拖放操作,就在該組件的設計上實現某些必要的函數。比如說,我們可能需要在MainWindow這個主窗口上實現拖放,那就在這個類中實現一些函數。如果我們自定義了一個組件,比如說定義了一個類MyDnDWidget繼承自QWidget,我們就在MyDndWidget類中實現特定的函數來支持拖放。
QMimeData類是Qt內部的一個容器,它記錄著大量擁有Mime Type(元類型)的數據??梢园阉敵墒且粋€map,每一個元素都是”Mime Type, Mime Data”這樣的鍵值對,但是使用的時候不要像map一樣使用,只是用于理解。
拖放操作不只局限在文本和圖像上,任何類型的信息在拖放操作中都能被轉換。為了在應用之間拖動信息,這些應用彼此都應該知道他們允許接收哪種數據格式,同時也應該知道他們可以產生哪種數據格式。這個是由MIME Types實現的。
被構造的QDrag對象包含MIME Types的列表,它能使用這些去表現數據,同時接收拖動數據的那個窗體使用這些MIME Types中的一個去訪問數據。Mime Type可以是Qt中提供的,也可以是用戶自定義的,如果需要自定義一個Mime Type,可以重新實現mimeTypes()函數:
mimeTypes
QStringList QAbstractItemModel::mimeTypes() const這個函數返回允許的Mime Types列表。默認情況下,內置的models和views使用內部的Mime Type,比如text/uri-list。 當重新實現一個支持拖放操作的model時,如果設計者想要返回的數據格式不同于內部的Mime Type,那就需要重新實現這個函數來返回自定義的Mime Types列表。通常的實現形式為:
QStringList types;types << /* the custom mime types */;return types;如果在自定義model中重新實現了這個函數,那么同時也需要重新實現mimeData()和dropMimeData()函數:
mimeData
QMimeData *QAbstractItemModel::mimeData(const QModelIndexList &indexes) const這個函數返回一個QMimeData *類型的對象,這個對象包含序列化的數據元素,而那些數據元素存儲在形參indexes中。換句話說,就是將模型索引列表indexes中的數據序列化,然后存儲在一個QMimeData中,最后返回這個QMimeData指針。序列化的方法是使用QByteArray和QDataStream兩個類。通常的實現格式為:
if(indexes.isEmpty()) return 0;QMimeData *mimeData = new QMimeData;QByteArray encodeData;QDataStream stream(&encodeData, QIODevice::WriteOnly);foreach(QModelIndex index, indexes){ if(index.value()) { /* 將index中存儲的數據取出來 */ stream << /* 數據1 */ << /* 數據2 */ << /* ... */; }}mimeData->setData(/* your custom mime types */, encodeData);return mimeData;需要注意的是,當模型索引列表indexes為空時,返回0;
dropMimeData
bool QAbstractItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)當Qt檢測到有Mime Data被釋放到以該模型為model的組件上時,相應model的這個函數被調用。設計者需要重新實現這個函數來處理被釋放的數據,通常是將數據存儲到(插入或追加到)model的數據結構中。如果這個釋放動作和數據被model處理則返回true,否則返回false; 參數中的row, column, parent指明數據釋放的位置。如果row和column為-1,意味著被釋放的數據釋放到了parent上,通常是追加到parent的孩子中。如果row和column大于等于0,則被插入到parent孩子的特定位置。 通常的實現格式為:
if(!data->hasFormats(/* the custom mime types */) return false;if(action == Qt::IgnoreAction) return true;/* 確定插入的位置position */QByteArray encodeData = data->data(/* the custom mime types */);QDataStream stream(&encodeData, Qt::ReadOnly);while(!stream.atEnd()){ stream >> /* 數據1 */ >> /* 數據2 */ >> /* ... */; beginInsertRows(parent, position, position); /* model內部數據結構執行插入操作 */ endInsertRows(); ++position;}return true;在拖放模型中,用戶既可以拖動信息的拷貝(復制)也可以拖動信息本身(剪切)到一個新地方。但是直到拖放操作完成之后程序才知道用戶是想剪切還是想拷貝。這在拖動不同組件之間的信息時沒有什么影響,但是在同一個組件上拖動時,檢查使用了哪種drop action是很重要的。
Qt::DropAction
Qt::MoveAction 移動數據本身(剪切)Qt::CopyAction 復制數據(復制)Qt::IgnoreAction 忽略數據在釋放數據的時候,Qt會調用鼠標所在位置上組件的dropEvent()函數,設計者需要在這個函數中告訴拖放操作的數據源drop action以便數據源確定是復制還是剪切。
dropEvent
void QWidget::dropEvent(QDropEvent *event)通知數據源drop action
event->setDropAction(Qt::MoveAction); /* 通知數據源刪除原數據 */event->accept();或者
event->setDropAction(Qt::CopyAction); /* 通知數據源保留原數據 */event->accept();通常拖動操作都是開始于鼠標點擊(mousePRessEvent()函數),而每個拖動操作都是一個QDrag類,所以在鼠標點擊函數中需要定義一個QDrag類,然后執行拖動操作,等待釋放動作的反饋??梢园堰@個函數看成數據源。
mousePressEvent
void QWidget::mousePressEvent(QMouseEvent *event){ /* ... */ /* 取出點擊位置的數據,進行序列化 */ QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction); if(dropAction == Qt::CopyAction || dropAction == IgnoreAction) { /* 不需要刪除原數據 */ } else if(dropAction == Qt::MoveAction) { /* 刪除原數據 */ update(/* event->pos()處的范圍矩形 */) }}下面這些類處理拖放和必要的mime type的編碼和解碼: QDrag:為以Mime Data為基礎的拖放轉化提供支持。 QDragEnterEvent:這個類型的事件被發送到窗口如果有數據被拖進這個窗口。 QDragLeaveEvent:這個類型的事件被發送到窗口如果有數據被拖離這個窗口。 QDragMoveEvent:當正在拖動時,這個類型的事件會一直被發出。 QDropEvent:當拖放操作完成時,這個類型的時候被發出。
在實現拖放操作時就是圍繞這幾個類來實現特定的函數。
dragEnterEvent
void QWidget::dragEnterEvent(QDragEnterEvent *event);protected作用域的函數。當鼠標拖進一個窗體的時候,該窗體的這個函數被調用,傳遞的事件作為參數。如果這個事件被忽略了(ignored),那么這個窗體不會接收任何拖動事件。換句話說這個函數就是用于提醒Qt該組件接收的數據類型,判斷是accept()還是ignore()這個事件。通常的實現格式為:
if(event->mimeData()->hasFormats(/* the custom mime types */) event->accept();else event->ignore();dragLeaveEvent
void QWidget::dragLeaveEvent(QDragLeaveEvent *event);顧名思義,這個函數就是當拖動超出該組件范圍時調用的。
dragMoveEvent
void QWidget::dragMoveEvent(QDragMoveEvent *event);當正在拖動時,會一直調用這個函數。可以改變一些變量比如記錄此時的投影矩形,然后在繪制函數中繪制。
paintEvent
void QWidget::paintEvent(QPaintEvent *event);這個函數就是用于繪制所在類的。它可以被調用由以下理由: 1.repaint()或者update()函數被調用; 2.窗口部件被遮擋并且已經被Qt發現; 3.其他的理由
繪制的時候使用畫刷類QPainter
QPainter painter;painter.begin(this);/* 繪制代碼 */painter.end();新聞熱點
疑難解答