Qt 自定義拖放數(shù)據(jù)對(duì)象

2018-10-08 09:57 更新

自定義拖放數(shù)據(jù)對(duì)象

前面的例子都是使用的系統(tǒng)提供的拖放對(duì)象 QMimeData 進(jìn)行拖放數(shù)據(jù)的存儲(chǔ),比如使用 QMimeData::setText() 創(chuàng)建文本,使用 QMimeData::urls() 創(chuàng)建 URL 對(duì)象。但是,如果你希望使用一些自定義的對(duì)象作為拖放數(shù)據(jù),比如自定義類等等,單純使用 QMimeData 可能就沒(méi)有那么容易了。為了實(shí)現(xiàn)這種操作,我們可以從下面三種實(shí)現(xiàn)方式中選擇一個(gè):

  • 將自定義數(shù)據(jù)作為 QByteArray 對(duì)象,使用 QMimeData::setData() 函數(shù)作為二進(jìn)制數(shù)據(jù)存儲(chǔ)到 QMimeData 中,然后使用 QMimeData::Data() 讀取;
  • 繼承 QMimeData,重寫其中的 formats() 和 retrieveData() 函數(shù)操作自定義數(shù)據(jù);
  • 如果拖放操作僅僅發(fā)生在同一個(gè)應(yīng)用程序,可以直接繼承 QMimeData,然后使用任意合適的數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲(chǔ)。

第一種方法不需要繼承任何類,但是有一些局限:即是拖放不會(huì)發(fā)生,我們也必須將自定義的數(shù)據(jù)對(duì)象轉(zhuǎn)換成 QByteArray 對(duì)象;如果你希望支持很多種拖放的數(shù)據(jù),那么每種類型的數(shù)據(jù)都必須使用一個(gè) QMimeData 類,這可能會(huì)導(dǎo)致類爆炸;如果數(shù)據(jù)很大的話,這種方式可能會(huì)降低系統(tǒng)的可維護(hù)性。然而,后兩種實(shí)現(xiàn)方式就不會(huì)有這些問(wèn)題,或者說(shuō)是能夠減小這種問(wèn)題,并且能夠讓我們有完全控制權(quán)。我們先來(lái)看一個(gè)應(yīng)用,使用 QTableWidget 來(lái)進(jìn)行拖放操作,拖放的類型包括 plain/text,plain/html 和 plain/csv。如果使用第一種實(shí)現(xiàn)方法,我們的代碼將會(huì)如下所示:


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();  
}  

對(duì)于這段代碼,我們應(yīng)該已經(jīng)很容易的理解:在 performDrag() 函數(shù)中,我們調(diào)用 QMimeData 的 setText() 和 setHTML() 函數(shù)存儲(chǔ) plain/text 和 plain/html 數(shù)據(jù),使用 setData() 將 text/csv 類型的數(shù)據(jù)作為二進(jìn)制 QByteArray 類型存儲(chǔ)。


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 格式為:

RedGreenBlue
CyanYellowMagenta

在放置的函數(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ù)類型,但是在這個(gè)函數(shù)中我們只接受兩種類型。至于 html 類型,我們希望如果用戶將 QTableWidget 的數(shù)據(jù)拖到一個(gè) HTML 編輯器,那么它就會(huì)自動(dòng)轉(zhuǎn)換成 html 代碼,但是我們不計(jì)劃支持將外部的 html 代碼拖放到 QTableWidget 上。為了讓這段代碼能夠工作,我們需要在構(gòu)造函數(shù)中設(shè)置 setAcceptDrops(true) 和 setSelectionMode(ContiguousSelection)。

好了,上面就是我們所說(shuō)的第一種方式的實(shí)現(xiàn)。這里并沒(méi)有給出完整的實(shí)現(xiàn)代碼,大家可以根據(jù)需要自己實(shí)現(xiàn)一下試試。下面我們將按照第二種方法重新實(shí)現(xiàn)這個(gè)需求。


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;  
}; 

為了避免存儲(chǔ)具體的數(shù)據(jù),我們存儲(chǔ) table 和選擇區(qū)域的坐標(biāo)的指針。


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;  
} 

構(gòu)造函數(shù)中,我們對(duì)私有變量進(jìn)行初始化。formats() 函數(shù)返回的是被 MIME 數(shù)據(jù)對(duì)象支持的數(shù)據(jù)類型列表。這個(gè)列表是沒(méi)有先后順序的,但是最佳實(shí)踐是將“最適合”的類型放在第一位。對(duì)于支持多種類型的應(yīng)用程序而言,有時(shí)候會(huì)直接選用第一個(gè)符合的類型存儲(chǔ)。


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ù)返回值之一,但是我們并不能假定一定是這個(gè)值之一,因?yàn)椴⒉皇撬械膽?yīng)用程序都會(huì)通過(guò) formats() 函數(shù)檢查 MIME 類型。一些返回函數(shù),比如 text(), html(), urls(), imageData(), colorData() 和 data() 實(shí)際上都是在 QMimeData 的 retrieveData() 函數(shù)中實(shí)現(xiàn)的。第二個(gè)參數(shù) preferredType 給出我們應(yīng)該在 QVariant 中存儲(chǔ)哪種類型的數(shù)據(jù)。在這里,我們簡(jiǎn)單的將其忽略了,并且在 else 語(yǔ)句中,我們假定 QMimeData 會(huì)自動(dòng)將其轉(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ù)類型進(jìn)行選擇。我們使用 qobject_cast 宏進(jìn)行類型轉(zhuǎn)換。如果成功,說(shuō)明數(shù)據(jù)來(lái)自同一應(yīng)用程序,因此我們直接設(shè)置 QTableWidget 相關(guān) 數(shù)據(jù),如果轉(zhuǎn)換失敗,我們則使用一般的處理方式。

本文出自 “豆子空間” 博客,請(qǐng)務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)