最近在做项目时,需要实现一个功能:
在一个QTreeWidget中,随意移动父节点或子节点的位置,但父节点和子节点不能互调。
用图来举例的话,大概是这个样子:
父节点[114514,114517]可以用鼠标拖拽。
比如将114514拖拽到114516后,那么114514就跑到了114516后面。
然后针对子节点[191981,191983]:
可以像父节点一样调换位置,也可以拖拽到其他父节点下面,成为其他父节点的子节点。
但父节点无法成为子节点,子节点也无法成为父节点。
本人首先学习了一下QDrag,然后没学懂,
于是就自己重写QTreeWidget的MousePressEvent,MouseMoveEvent以及MouseReleaseEvent来实现了。
大概的思路就是:
①在MousePressEvent中获取要拖拽的项目,记为pSource;
②在MouseMoveEvent中显示一个Label,用来更加显性的表示拖拽;
③在MouseReleaseEvent中获取拖拽到的项目,记为pTarget,然后将pSource插入到pTarget周围。
代码如下(在每个代码块后面会有简短的说明帮助理解)
class DragTreeWidget : public QTreeWidget { Q_OBJECT public: DragTreeWidget(QWidget* parent = nullptr); private: //初始化函数 void initTreeWidget(); void mousePressEvent(QMouseEvent* ev) override; void mouseMoveEvent(QMouseEvent* ev) override; void mouseReleaseEvent(QMouseEvent* ev) override; private: //用来显示label,显性表示拖拽 QLabel label; //表示被拖拽的item QTreeWidgetItem* pSource; //表示被拖拽的item的父节点 QTreeWidgetItem* pParent; //表示被拖拽的item在父节点中的索引 int originIndex; //用于判断在mouseMoveEvent中是否已经移除了组件 bool isJudged; };
头文件,没什么好说的。
void DragTreeWidget::initTreeWidget() { for(int i=0;i<10;i++){ QTreeWidgetItem* pTopItem = new QTreeWidgetItem(this); pTopItem->setText(0,QString::number(114514+i)); addTopLevelItem(pTopItem); } for(int i=0;i<10;i++){ QTreeWidgetItem* pChildItem = new QTreeWidgetItem(topLevelItem(0)); pChildItem->setText(0,QString::number(191981+i)); topLevelItem(0)->addChild(pChildItem); } }
用来插入一些item用来进行实验。
DragTreeWidget::DragTreeWidget(QWidget *parent) :QTreeWidget(parent), label(this), pSource(nullptr) { initTreeWidget(); setHeaderHidden(true); label.resize(100,30); label.setText(""); label.hide(); }
类构造函数,隐藏了QTreeWidget的头部,设置label大小并隐藏label。
void DragTreeWidget::mousePressEvent(QMouseEvent *ev) { qDebug()<<"in mousePressEvent"<<endl; pSource = itemAt(ev->pos()); qDebug()<<pSource->text(0)<<endl; if(pSource->parent()){ qDebug()<<"source parent is "<<pSource->parent()->text(0)<<endl; } label.setText(pSource->text(0)); isJudged = false; QTreeWidget::mousePressEvent(ev); }
在mousePressEvent之中,主要是确定被拖拽的目标。
因为如果这个时候就将item从列表中take出来的话,那么将无法实现将列表展开的功能。
所以具体将列表take出来的行为,放入到mouseMoveEvent之中进行。
然后,这就导致了mouseMoveEvent中的下一个坑(悲
void DragTreeWidget::mouseMoveEvent(QMouseEvent *ev) { if(pSource==nullptr){ QTreeWidget::mouseMoveEvent(ev); return; } qDebug()<<"in mouseMoveEvent"<<endl; label.show(); label.move(ev->pos()); if(isJudged==false){ isJudged = true; if(pSource->parent()){ qDebug()<<"isSubItem"<<endl; pParent = pSource->parent(); originIndex = pParent->indexOfChild(pSource); pParent->takeChild(originIndex); } else{ qDebug()<<"isMainItem"<<endl; pParent = nullptr; takeTopLevelItem(indexOfTopLevelItem(pSource)); } qDebug()<<"source = "<<pSource->text(0)<<endl; } }
因为需要在mouseMoveEvent之中将被拖拽的目标给take出来,所以会出现一个问题:
mouseMoveEvent并不是只执行一次,而是每隔一段时间就会执行一次,也就是执行多次。
将item从列表中take出来后,其parent会为nullptr。这样的话就无法判断出被拖拽的item究竟是父节点还是子节点了。
所以这里需要使用一个变量,判断是否有被判断过。
那么为什么不在mousePressEvent中进行判别呢?
。。。有道理,回头实践一下。
void DragTreeWidget::mouseReleaseEvent(QMouseEvent *ev) { qDebug()<<"in mouseReleaseEvent"<<endl; label.hide(); QTreeWidgetItem* pTarget = itemAt(ev->pos()); if(pTarget==nullptr){ if(pParent){ pParent->insertChild(originIndex,pSource); } else{ addTopLevelItem(pSource); } setCurrentItem(pSource); return; } // qDebug()<<"is child = "<<isChild<<endl; if(pTarget->parent()){ qDebug()<<"PTarget parent = "<<pTarget->parent()->text(0)<<endl; } //如果是父节点 if(pTarget->parent()==nullptr){ if(pParent){ pTarget->addChild(pSource); } else{ insertTopLevelItem(indexOfTopLevelItem(pTarget)+1,pSource); } } //如果是子节点 else{ if(pParent){ QTreeWidgetItem* parent = pTarget->parent(); parent->insertChild(parent->indexOfChild(pTarget)+1,pSource); } else{ } } setCurrentItem(pSource); }
mouseReleaseEvent写的比较臃肿冗长,因为其实按道理这地方应该分为几个函数分开写的,不过因为是demo,所以不管那么多了。
mouseReleaseEvent中大致做了如下几件事:
①判断被拖拽的item(pSource)、拖拽到的item(pTarget)是否有效
②判断pSource、pTarget的类型
③根据类型(父类节点被拖拽到父类节点、子类节点被拖拽到子类节点、子类节点被拖拽到父类节点。。。)来执行对应的行为
以后也许会补全一下注释,让其更加容易理解。
整个源文件代码如下:
DragTreeWidget::DragTreeWidget(QWidget *parent) :QTreeWidget(parent), label(this), pSource(nullptr) { initTreeWidget(); setHeaderHidden(true); label.resize(100,30); label.setText(""); label.hide(); } void DragTreeWidget::initTreeWidget() { for(int i=0;i<10;i++){ QTreeWidgetItem* pTopItem = new QTreeWidgetItem(this); pTopItem->setText(0,QString::number(114514+i)); addTopLevelItem(pTopItem); } for(int i=0;i<10;i++){ QTreeWidgetItem* pChildItem = new QTreeWidgetItem(topLevelItem(0)); pChildItem->setText(0,QString::number(191981+i)); topLevelItem(0)->addChild(pChildItem); } } void DragTreeWidget::mousePressEvent(QMouseEvent *ev) { qDebug()<<"in mousePressEvent"<<endl; pSource = itemAt(ev->pos()); qDebug()<<pSource->text(0)<<endl; if(pSource->parent()){ qDebug()<<"source parent is "<<pSource->parent()->text(0)<<endl; } label.setText(pSource->text(0)); isJudged = false; QTreeWidget::mousePressEvent(ev); } void DragTreeWidget::mouseMoveEvent(QMouseEvent *ev) { if(pSource==nullptr){ QTreeWidget::mouseMoveEvent(ev); return; } qDebug()<<"in mouseMoveEvent"<<endl; label.show(); label.move(ev->pos()); if(isJudged==false){ isJudged = true; if(pSource->parent()){ qDebug()<<"isSubItem"<<endl; pParent = pSource->parent(); originIndex = pParent->indexOfChild(pSource); pParent->takeChild(originIndex); } else{ qDebug()<<"isMainItem"<<endl; pParent = nullptr; takeTopLevelItem(indexOfTopLevelItem(pSource)); } qDebug()<<"source = "<<pSource->text(0)<<endl; } } void DragTreeWidget::mouseReleaseEvent(QMouseEvent *ev) { qDebug()<<"in mouseReleaseEvent"<<endl; label.hide(); QTreeWidgetItem* pTarget = itemAt(ev->pos()); if(pTarget==nullptr){ if(pParent){ pParent->insertChild(originIndex,pSource); } else{ addTopLevelItem(pSource); } setCurrentItem(pSource); return; } // qDebug()<<"is child = "<<isChild<<endl; if(pTarget->parent()){ qDebug()<<"PTarget parent = "<<pTarget->parent()->text(0)<<endl; } //如果是父节点 if(pTarget->parent()==nullptr){ if(pParent){ pTarget->addChild(pSource); } else{ insertTopLevelItem(indexOfTopLevelItem(pTarget)+1,pSource); } } //如果是子节点 else{ if(pParent){ QTreeWidgetItem* parent = pTarget->parent(); parent->insertChild(parent->indexOfChild(pTarget)+1,pSource); } else{ } } setCurrentItem(pSource); }