<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                #(53):自定義拖放數據 上一章中,我們的例子使用系統提供的拖放對象`QMimeData`進行拖放數據的存儲。比如使用`QMimeData::setText()`創建文本,使用`QMimeData::urls()`創建 URL 對象等。但是,如果你希望使用一些自定義的對象作為拖放數據,比如自定義類等等,單純使用`QMimeData`可能就沒有那么容易了。為了實現這種操作,我們可以從下面三種實現方式中選擇一個: 1. 將自定義數據作為`QByteArray`對象,使用`QMimeData::setData()`函數作為二進制數據存儲到`QMimeData`中,然后使用`QMimeData::data()`讀取 2. 繼承`QMimeData`,重寫其中的`formats()`和`retrieveData()`函數操作自定義數據 3. 如果拖放操作僅僅發生在同一個應用程序,可以直接繼承`QMimeData`,然后使用任意合適的數據結構進行存儲 這三種選擇各有千秋:第一種方法不需要繼承任何類,但是有一些局限:即是拖放不會發生,我們也必須將自定義的數據對象轉換成`QByteArray`對象,在一定程度上,這會降低程序性能;另外,如果你希望支持很多種拖放的數據,那么每種類型的數據都必須使用一個`QMimeData`類,這可能會導致類爆炸。后兩種實現方式則不會有這些問題,或者說是能夠減小這種問題,并且能夠讓我們有完全的控制權。 下面我們使用第一種方法來實現一個表格。這個表格允許我們選擇一部分數據,然后拖放到另外的一個空白表格中。在數據拖動過程中,我們使用 CSV 格式對數據進行存儲。 首先來看頭文件: ~~~ class DataTableWidget : public QTableWidget { Q_OBJECT public: DataTableWidget(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(); QString selectionText() const; QString toHtml(const QString &plainText) const; QString toCsv(const QString &plainText) const; void fromCsv(const QString &csvText); QPoint startPos; }; ~~~ 這里,我們的表格繼承自`QTableWidget`。雖然這是一個簡化的`QTableView`,但對于我們的演示程序已經綽綽有余。 ~~~ DataTableWidget::DataTableWidget(QWidget *parent) : QTableWidget(parent) { setAcceptDrops(true); setSelectionMode(ContiguousSelection); setColumnCount(3); setRowCount(5); } void DataTableWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { startPos = event->pos(); } QTableWidget::mousePressEvent(event); } void DataTableWidget::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) { int distance = (event->pos() - startPos).manhattanLength(); if (distance >= QApplication::startDragDistance()) { performDrag(); } } } void DataTableWidget::dragEnterEvent(QDragEnterEvent *event) { DataTableWidget *source = qobject_cast<DataTableWidget *>(event->source()); if (source && source != this) { event->setDropAction(Qt::MoveAction); event->accept(); } } void DataTableWidget::dragMoveEvent(QDragMoveEvent *event) { DataTableWidget *source = qobject_cast<DataTableWidget *>(event->source()); if (source && source != this) { event->setDropAction(Qt::MoveAction); event->accept(); } } ~~~ 構造函數中,由于我們要針對兩個表格進行相互拖拽,所以我們設置了`setAcceptDrops()`函數。選擇模式設置為連續,這是為了方便后面我們的算法簡單。`mousePressEvent()`,`mouseMoveEvent()`,`dragEnterEvent()`以及`dragMoveEvent()`四個事件響應函數與前面幾乎一摸一樣,這里不再贅述。注意,這幾個函數中有一些并沒有調用父類的同名函數。關于這一點我們在前面的章節中曾反復強調,但這里我們不希望父類的實現被執行,因此完全屏蔽了父類實現。下面我們來看`performDrag()`函數: ~~~ void DataTableWidget::performDrag() { QString selectedString = selectionText(); if (selectedString.isEmpty()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setHtml(toHtml(selectedString)); mimeData->setData("text/csv", toCsv(selectedString).toUtf8()); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); if (drag->exec(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction) { selectionModel()->clearSelection(); } } ~~~ 首先我們獲取選擇的文本(`selectionText()`函數),如果為空則直接返回。然后創建一個`QMimeData`對象,設置了兩個數據:HTML 格式和 CSV 格式。我們的 CSV 格式是以`QByteArray`形式存儲的。之后我們創建了`QDrag`對象,將這個`QMimeData`作為拖動時所需要的數據,執行其`exec()`函數。`exec()`函數指明,這里的拖動操作接受兩種類型:復制和移動。當執行的是移動時,我們將已選區域清除。 需要注意一點,`QMimeData`在創建時并沒有提供 parent 屬性,這意味著我們必須手動調用 delete 將其釋放。但是,`setMimeData()`函數會將其所有權轉移到`QDrag`名下,也就是會將其 parent 屬性設置為這個`QDrag`。這意味著,當`QDrag`被釋放時,其名下的所有`QMimeData`對象都會被釋放,所以結論是,我們實際是無需,也不能手動 delete 這個`QMimeData`對象。 ~~~ void DataTableWidget::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat("text/csv")) { QByteArray csvData = event->mimeData()->data("text/csv"); QString csvText = QString::fromUtf8(csvData); fromCsv(csvText); event->acceptProposedAction(); } } ~~~ `dropEvent()`函數也很簡單:如果是 CSV 類型,我們取出數據,轉換成字符串形式,調用了`fromCsv()`函數生成新的數據項。 幾個輔助函數的實現比較簡單: ~~~ QString DataTableWidget::selectionText() const { QString selectionString; QString headerString; QAbstractItemModel *itemModel = model(); QTableWidgetSelectionRange selection = selectedRanges().at(0); for (int row = selection.topRow(), firstRow = row; row <= selection.bottomRow(); row++) { for (int col = selection.leftColumn(); col <= selection.rightColumn(); col++) { if (row == firstRow) { headerString.append(horizontalHeaderItem(col)->text()).append("\t"); } QModelIndex index = itemModel->index(row, col); selectionString.append(index.data().toString()).append("\t"); } selectionString = selectionString.trimmed(); selectionString.append("\n"); } return headerString.trimmed() + "\n" + selectionString.trimmed(); } QString DataTableWidget::toHtml(const QString &plainText) const { #if QT_VERSION >= 0x050000 QString result = plainText.toHtmlEscaped(); #else QString result = Qt::escape(plainText); #endif result.replace("\t", "<td>"); result.replace("\n", "\n<tr><td>"); result.prepend("<table>\n<tr><td>"); result.append("\n</table>"); return result; } QString DataTableWidget::toCsv(const QString &plainText) const { QString result = plainText; result.replace("\\", "\\\\"); result.replace("\"", "\\\""); result.replace("\t", "\", \""); result.replace("\n", "\"\n\""); result.prepend("\""); result.append("\""); return result; } void DataTableWidget::fromCsv(const QString &csvText) { QStringList rows = csvText.split("\n"); QStringList headers = rows.at(0).split(", "); for (int h = 0; h < headers.size(); ++h) { QString header = headers.at(0); headers.replace(h, header.replace('"', "")); } setHorizontalHeaderLabels(headers); for (int r = 1; r < rows.size(); ++r) { QStringList row = rows.at(r).split(", "); setItem(r - 1, 0, new QTableWidgetItem(row.at(0).trimmed().replace('"', ""))); setItem(r - 1, 1, new QTableWidgetItem(row.at(1).trimmed().replace('"', ""))); } } ~~~ 雖然看起來很長,但是這幾個函數都是純粹算法,而且算法都比較簡單。注意`toHtml()`中我們使用條件編譯語句區分了一個 Qt4 與 Qt5 的不同函數。這也是讓同一代碼能夠同時應用于 Qt4 和 Qt5 的技巧。fromCsv() 函數中,我們直接將下面表格的前面幾列設置為拖動過來的數據,注意這里有一些格式上面的變化,主要用于更友好地顯示。 最后是`MainWindow`的一個簡單實現: ~~~ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { topTable = new DataTableWidget(this); QStringList headers; headers << "ID" << "Name" << "Age"; topTable->setHorizontalHeaderLabels(headers); topTable->setItem(0, 0, new QTableWidgetItem(QString("0001"))); topTable->setItem(0, 1, new QTableWidgetItem(QString("Anna"))); topTable->setItem(0, 2, new QTableWidgetItem(QString("20"))); topTable->setItem(1, 0, new QTableWidgetItem(QString("0002"))); topTable->setItem(1, 1, new QTableWidgetItem(QString("Tommy"))); topTable->setItem(1, 2, new QTableWidgetItem(QString("21"))); topTable->setItem(2, 0, new QTableWidgetItem(QString("0003"))); topTable->setItem(2, 1, new QTableWidgetItem(QString("Jim"))); topTable->setItem(2, 2, new QTableWidgetItem(QString("21"))); topTable->setItem(3, 0, new QTableWidgetItem(QString("0004"))); topTable->setItem(3, 1, new QTableWidgetItem(QString("Dick"))); topTable->setItem(3, 2, new QTableWidgetItem(QString("24"))); topTable->setItem(4, 0, new QTableWidgetItem(QString("0005"))); topTable->setItem(4, 1, new QTableWidgetItem(QString("Tim"))); topTable->setItem(4, 2, new QTableWidgetItem(QString("22"))); bottomTable = new DataTableWidget(this); QWidget *content = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(content); layout->addWidget(topTable); layout->addWidget(bottomTable); setCentralWidget(content); setWindowTitle("Data Table"); } ~~~ 這段代碼沒有什么新鮮內容,我們直接將其跳過。最后編譯運行下程序,按下 shift 并點擊表格兩個單元格即可選中,然后拖放到另外的空白表格中來查看效果。 下面我們換用繼承`QMimeData`的方法來嘗試重新實現上面的功能。 ~~~ class TableMimeData : public QMimeData { Q_OBJECT public: TableMimeData(const QTableWidget *tableWidget, const QTableWidgetSelectionRange &range); const QTableWidget *tableWidget() const { return dataTableWidget; } QTableWidgetSelectionRange range() const { return selectionRange; } QStringList formats() const { return dataFormats; } 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 selectionText() const; const QTableWidget *dataTableWidget; QTableWidgetSelectionRange selectionRange; QStringList dataFormats; }; ~~~ 為了避免存儲具體的數據,我們存儲表格的指針和選擇區域的坐標的指針;dataFormats 指明這個數據對象所支持的數據格式。這個格式列表由`formats()`函數返回,意味著所有被 MIME 數據對象支持的數據類型。這個列表是沒有先后順序的,但是最佳實踐是將“最適合”的類型放在第一位。對于支持多種類型的應用程序而言,有時候會直接選用第一個符合的類型存儲。 ~~~ TableMimeData::TableMimeData(const QTableWidget *tableWidget, const QTableWidgetSelectionRange &range) { dataTableWidget = tableWidget; selectionRange = range; dataFormats << "text/csv" << "text/html"; } ~~~ 函數`retrieveData()`將給定的 MIME 類型作為`QVariant`返回。參數 format 的值通常是`formats()`函數返回值之一,但是我們并不能假定一定是這個值之一,因為并不是所有的應用程序都會通過`formats()`函數檢查 MIME 類型。一些返回函數,比如`text()`,html(),`urls()`,`imageData()`,`colorData()`和`data()`實際上都是在`QMimeData`的`retrieveData()`函數中實現的。第二個參數`preferredType`給出我們應該在`QVariant`中存儲哪種類型的數據。在這里,我們簡單的將其忽略了,并且在 else 語句中,我們假定`QMimeData`會自動將其轉換成所需要的類型: ~~~ QVariant TableMimeData::retrieveData(const QString &format, QVariant::Type preferredType) const { if (format == "text/csv") { return toCsv(selectionText()); } else if (format == "text/html") { return toHtml(selectionText()); } else { return QMimeData::retrieveData(format, preferredType); } } ~~~ 在組件的`dragEvent()`函數中,需要按照自己定義的數據類型進行選擇。我們使用`qobject_cast`宏進行類型轉換。如果成功,說明數據來自同一應用程序,因此我們直接設置`QTableWidget`相關數據,如果轉換失敗,我們則使用一般的處理方式。這也是這類程序通常的處理方式: ~~~ void DataTableWidget::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(); } QTableWidget::mouseMoveEvent(event); } ~~~ 由于這部分代碼與前面的相似,感興趣的童鞋可以根據前面的代碼補全這部分,所以這里不再給出完整代碼。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看