處理 TreeView 的 Drag 與 Drop
前言
在先前的 TreeView 的基本應用 與 在 TreeView 使用右鍵選單 裡介紹了 TreeView 的常用功能,但少提到了一個常用的功能,就是 Drag 與 Drop ,節點的資料能夠直接在 Widget 上編輯是一個非常方便的功能,在此把學習的過程做個紀錄。
內容
先到 [ GitLab ] HelloQt 下載範例,這次應用的專案路徑
(HelloQt' directory)/TreeView/DragAndDrop,執行結果如下
範例的執行結果 |
範例會給予預設的節點,並可以在 Widget 上透過拖曳(Drag)來編輯節點。這次雖然只是處理 Drag 與 Drop 的事件,但不幸的是 Qt 並沒有提供透過 Connect 事件的方式來處理,這次使用的方式是透過"繼承"的方式來處理 Drag 與 Drop 的事件,這種透過繼承的方式可以參考 特製 Widget ,兩者的差別是該篇繼承的是 QWidget ,而本篇繼承的是 QTreeView,在新增 Class 的精靈介面可以參考下圖
新增繼承 QTreeView 的類別 |
精靈介面加完後的宣告程式碼並不完整,像是 include 的相關程式碼並不會自動被加入,可以比對範例的宣告來看差異是什麼,接著就直接看到宣告的部分,程式碼如下
#include <QTreeView> #include <QVector> #include <QDragEnterEvent> #include <QDragMoveEvent> #include <QDragLeaveEvent> #include <QDropEvent> #include <QStandardItemModel> class MyTreeView : public QTreeView { Q_OBJECT public: explicit MyTreeView(QWidget *parent = nullptr); // void CustomSetModel(QStandardItemModel* pModel); void HideColumnByList(); protected: void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; // private: QVector<int> m_cHideColumnList; };
處理 Drag 與 Drop 的事件有 dragEngterEvent() 、 dragMoveEvent() 、 dragLeaveEvent() 與 dropEvent() , CustomSetModel() 與 HideColumnByList() 是自己定義的 Function ,待會兒在實作的部分會進一步說明它們的功能,接下來就看到實作的部分,先看到自訂的 Function ,如下
void MyTreeView::CustomSetModel(QStandardItemModel *pModel) { this->setModel( pModel ); HideColumnByList(); } void MyTreeView::HideColumnByList() { for( auto i = 0 ; i < m_cHideColumnList.size() ; i++ ) { this->hideColumn( m_cHideColumnList[i] ); } }
HideColumnByList() 的部分會依據成員"m_cHideColumn"裡所存的要隱藏的 Column 來喚起 QTreeView::hideColumn() ,為什麼要提供這個 Function 呢?因為 QTreeView 在處理某些事件後會把隱藏的 Column 再次變成顯示 ,所以需要這個 Function 來恢復我們期望隱藏的 Column ,CustomSetModel() 會在喚起 QTreeView::setModel() 後喚起 HideColumnByList() 隱藏指定的 Column ,接著看到主程式的 MainWindow::MainWindow() ,如下
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // // QStandardItemModel* pModel = new QStandardItemModel( ui->treeView ); pModel->setHorizontalHeaderLabels( QStringList()<<QStringLiteral( "Name" )<<QStringLiteral( "Comment" ) ); QStandardItem* pItemRoot1 = new QStandardItem( QStringLiteral( "root1" ) ); //first pItemRoot1->setChild( 0 , 0 , new QStandardItem( QStringLiteral( "child" ) ) ); pItemRoot1->setChild( 0 , 1 , new QStandardItem( QStringLiteral( "I'm child" ) ) ); //second pItemRoot1->setChild( 1 , 0 , new QStandardItem( QStringLiteral( "child1" ) ) ); // pModel->appendRow( pItemRoot1 ); // QStandardItem* pItemRoot2 = new QStandardItem( QStringLiteral( "root2" ) ); pItemRoot2->setChild( 0 , 0 , new QStandardItem( QStringLiteral( "child" ) ) ); pItemRoot2->setChild( 0 , 1 , new QStandardItem( QStringLiteral( "I'm child" ) ) ); // pModel->appendRow( pItemRoot2 ); // ui->treeView->CustomSetModel( pModel ); }
前面就單純的加入預設節點不再贅述,可以參考 TreeView 的基本應用 ,重點在最後一行使用的是 MyTreeView::CustomSetModel() 而非 QTreeView::setModel() ,這是因為這樣可以隱藏指定的 Column 。接著看到 MyTreeView::MyTreeView() ,如下
MyTreeView::MyTreeView(QWidget *parent): QTreeView(parent) { this->setDragEnabled( true ); this->setDragDropMode( QAbstractItemView::InternalMove ); // this->setHeaderHidden( true ); this->setSelectionMode( QAbstractItemView::ExtendedSelection ); // m_cHideColumnList.push_back( 1 ); }
由於 QTreeView 預設是把 Drag 與 Drop 的功能關閉,所以透過 setDragEnabled() 與 setDragDropMode() 來開啟,接著透過 setHeaderHidden() 來關閉上方的欄位,然後透過 setSelectionMode() 來開啟可多選,最後在成員"m_cHideColumnList"加入要隱藏的 Column ,最後就直接看到 Drag 和 Drop 事件的實作,如下
void MyTreeView::dragEnterEvent(QDragEnterEvent *event) { qDebug( "dragEnterEvent" ); // QTreeView::dragEnterEvent( event ); } void MyTreeView::dragMoveEvent(QDragMoveEvent *event) { qDebug( "dragMoveEvent" ); // QTreeView::dragMoveEvent( event ); } void MyTreeView::dragLeaveEvent(QDragLeaveEvent *event) { qDebug( "dragLeaveEvent" ); // QTreeView::dragLeaveEvent( event ); } void MyTreeView::dropEvent(QDropEvent *event) { qDebug( "dropEvent" ); QModelIndex index = indexAt(event->pos() ); qDebug( "dropEvent node:%s" , index.data().toString().toLocal8Bit().data() ); DropIndicatorPosition dropIndicator = dropIndicatorPosition(); switch ( dropIndicator ) { case DropIndicatorPosition::OnItem: qDebug("onItem"); break; case DropIndicatorPosition::AboveItem: qDebug("AboveItem"); break; case DropIndicatorPosition::BelowItem: qDebug("BelowItem"); break; defadefault: qDebug("Unknown DropIndicatorPosition!"); break; } // for( auto i = 0 ; i < this->selectedIndexes().size() ; i++ ) { if( this->selectedIndexes().at(i).column() != 0 ) continue; // qDebug( "source node:%s" , this->selectedIndexes().at(i).data().toString().toLocal8Bit().data() ); } // HideColumnByList(); // QTreeView::dropEvent(event); }
dragEngterEvent() 、 dragMoveEvent() 與 dragLeaveEvent() 由於這次用不到,所以只列印偵錯訊息,這樣方便了解事件觸發的時機,所以範例重點會放在 dropEvent() ,這裡說明一下,範例希望得到的資訊是把那些節點丟到某個節點 的相關資訊,但不幸的是 QDropEvent 裡所提供的資訊並不完整,必須搭配 QTreeView 的相關 Funciton 才能達到目的,要得到被丟到的節點資訊 可以透過 QTreeEvent::Pos() 與 QTreeView::indexAt() 來的到資料,但丟到某個節點的想法有點瑕疵,如果丟到 root 那這個節點會是無效的嗎?答案是不會,因為 Qt 的這裡設計得不是很直覺, QTreeView 提供 DropIndicatorPoisition 來說明"丟到這個節點的裡面"、"丟到這個節點的前面"與"丟到這個節點的後面",來解說要丟到哪個節點的詳細狀況,接著來接解決如何取得要丟的節點,這個資訊會存在 QTreeView::selectedIndexs() ,其實就是最後選擇的節點,因為之前有開啟多選,所以這裡可能會有多個節點,最後透過 HideColumnByList() 來隱藏指定的 Column ,最後的 QTreeView::dropEvent() 是執行 DropEvent 的預設行為,但這個預設行為並不包含把節點的資料變成丟完後的資料,整個 dropEvent() 會發生在資料被修改完之前,所以要注意這個事件是發生在丟完資料之前,也就是 QTreeView 的樹節點資料都還沒改變時。
沒有留言:
張貼留言