前面的例子都是使用的系統(tǒng)提供的拖放對象 QMimeData 進行拖放數(shù)據(jù)的存儲,比如使用 QMimeData::setText() 創(chuàng)建文本,使用 QMimeData::urls() 創(chuàng)建 URL 對象。但是,如果你希望使用一些自定義的對象作為拖放數(shù)據(jù),比如自定義類等等,單純使用 QMimeData 可能就沒有那么容易了。為了實現(xiàn)這種操作,我們可以從下面三種實現(xiàn)方式中選擇一個:
第一種方法不需要繼承任何類,但是有一些局限:即是拖放不會發(fā)生,我們也必須將自定義的數(shù)據(jù)對象轉(zhuǎn)換成 QByteArray 對象;如果你希望支持很多種拖放的數(shù)據(jù),那么每種類型的數(shù)據(jù)都必須使用一個 QMimeData 類,這可能會導致類爆炸;如果數(shù)據(jù)很大的話,這種方式可能會降低系統(tǒng)的可維護性。然而,后兩種實現(xiàn)方式就不會有這些問題,或者說是能夠減小這種問題,并且能夠讓我們有完全控制權。我們先來看一個應用,使用 QTableWidget 來進行拖放操作,拖放的類型包括 plain/text,plain/html 和 plain/csv。如果使用第一種實現(xiàn)方法,我們的代碼將會如下所示:
void MyTableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
performDrag();
}
QTableWidget::mouseMoveEvent(event);
}
void MyTableWidget::performDrag()
{
QString plainText = selectionAsPlainText();
if (plainText.isEmpty())
return;
QMimeData *mimeData = new QMimeData;
mimeData->setText(plainText);
mimeData->setHtml(toHtml(plainText));
mimeData->setData("text/csv", toCsv(plainText).toUtf8());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
if (drag->exec(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction)
deleteSelection();
}
對于這段代碼,我們應該已經(jīng)很容易的理解:在 performDrag() 函數(shù)中,我們調(diào)用 QMimeData 的 setText() 和 setHTML() 函數(shù)存儲 plain/text 和 plain/html 數(shù)據(jù),使用 setData() 將 text/csv 類型的數(shù)據(jù)作為二進制 QByteArray 類型存儲。
QString MyTableWidget::toCsv(const QString &plainText)
{
QString result = plainText;
result.replace("\\", "\\\\");
result.replace("\"", "\\\"");
result.replace("\t", "\", \"");
result.replace("\n", "\"\n\"");
result.prepend("\"");
result.append("\"");
return result;
}
QString MyTableWidget::toHtml(const QString &plainText)
{
QString result = Qt::escape(plainText);
result.replace("\t", "<td>");
result.replace("\n", "\n<tr><td>");
result.prepend("<table>\n<tr><td>");
result.append("\n</table>");
return result;
}
toCsv() 和 toHtml() 函數(shù)將數(shù)據(jù)取出并轉(zhuǎn)換成我們需要的 csv 和 html類型的數(shù)據(jù)。例如,下面的數(shù)據(jù)
Red Green Blue
Cyan Yellow Magenta
轉(zhuǎn)換成 csv 格式為:
"Red", "Green", "Blue"
"Cyan", "Yellow", "Magenta"
轉(zhuǎn)換成 html 格式為:
Red | Green | Blue |
Cyan | Yellow | Magenta |
在放置的函數(shù)中我們像以前一樣使用:
void MyTableWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
// ...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
// ...
event->acceptProposedAction();
}
}
雖然我們接受三種數(shù)據(jù)類型,但是在這個函數(shù)中我們只接受兩種類型。至于 html 類型,我們希望如果用戶將 QTableWidget 的數(shù)據(jù)拖到一個 HTML 編輯器,那么它就會自動轉(zhuǎn)換成 html 代碼,但是我們不計劃支持將外部的 html 代碼拖放到 QTableWidget 上。為了讓這段代碼能夠工作,我們需要在構造函數(shù)中設置 setAcceptDrops(true) 和 setSelectionMode(ContiguousSelection)。
好了,上面就是我們所說的第一種方式的實現(xiàn)。這里并沒有給出完整的實現(xiàn)代碼,大家可以根據(jù)需要自己實現(xiàn)一下試試。下面我們將按照第二種方法重新實現(xiàn)這個需求。
class TableMimeData : public QMimeData
{
Q_OBJECT
public:
TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range);
const QTableWidget *tableWidget() const { return myTableWidget; }
QTableWidgetSelectionRange range() const { return myRange; }
QStringList formats() const;
protected:
QVariant retrieveData(const QString &format,
QVariant::Type preferredType) const;
private:
static QString toHtml(const QString &plainText);
static QString toCsv(const QString &plainText);
QString text(int row, int column) const;
QString rangeAsPlainText() const;
const QTableWidget *myTableWidget;
QTableWidgetSelectionRange myRange;
QStringList myFormats;
};
為了避免存儲具體的數(shù)據(jù),我們存儲 table 和選擇區(qū)域的坐標的指針。
TableMimeData::TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range)
{
myTableWidget = tableWidget;
myRange = range;
myFormats << "text/csv" << "text/html" << "text/plain";
}
QStringList TableMimeData::formats() const
{
return myFormats;
}
構造函數(shù)中,我們對私有變量進行初始化。formats() 函數(shù)返回的是被 MIME 數(shù)據(jù)對象支持的數(shù)據(jù)類型列表。這個列表是沒有先后順序的,但是最佳實踐是將“最適合”的類型放在第一位。對于支持多種類型的應用程序而言,有時候會直接選用第一個符合的類型存儲。
QVariant TableMimeData::retrieveData(const QString &format,
QVariant::Type preferredType) const
{
if (format == "text/plain") {
return rangeAsPlainText();
} else if (format == "text/csv") {
return toCsv(rangeAsPlainText());
} else if (format == "text/html") {
return toHtml(rangeAsPlainText());
} else {
return QMimeData::retrieveData(format, preferredType);
}
}
函數(shù) retrieveData() 將給出的 MIME 類型作為 QVariant 返回。參數(shù) format 的值通常是 formats() 函數(shù)返回值之一,但是我們并不能假定一定是這個值之一,因為并不是所有的應用程序都會通過 formats() 函數(shù)檢查 MIME 類型。一些返回函數(shù),比如 text(), html(), urls(), imageData(), colorData() 和 data() 實際上都是在 QMimeData 的 retrieveData() 函數(shù)中實現(xiàn)的。第二個參數(shù) preferredType 給出我們應該在 QVariant 中存儲哪種類型的數(shù)據(jù)。在這里,我們簡單的將其忽略了,并且在 else 語句中,我們假定 QMimeData 會自動將其轉(zhuǎn)換成所需要的類型。
void MyTableWidget::dropEvent(QDropEvent *event)
{
const TableMimeData *tableData =
qobject_cast<const TableMimeData *>(event->mimeData());
if (tableData) {
const QTableWidget *otherTable = tableData->tableWidget();
QTableWidgetSelectionRange otherRange = tableData->range();
// ...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
// ...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
// ...
event->acceptProposedAction();
}
QTableWidget::mouseMoveEvent(event);
}
在放置的函數(shù)中,我們需要按照我們自己定義的數(shù)據(jù)類型進行選擇。我們使用 qobject_cast 宏進行類型轉(zhuǎn)換。如果成功,說明數(shù)據(jù)來自同一應用程序,因此我們直接設置 QTableWidget 相關 數(shù)據(jù),如果轉(zhuǎn)換失敗,我們則使用一般的處理方式。
本文出自 “豆子空間” 博客,請務必保留此出處 http://devbean.blog.51cto.com/448512/193918
更多建議: