QTreeWidget自实现拖拽移动内容(不使用QDrag)


最近在做项目时,需要实现一个功能:

在一个QTreeWidget中,随意移动父节点或子节点的位置,但父节点和子节点不能互调。

用图来举例的话,大概是这个样子:

父节点[114514,114517]可以用鼠标拖拽。

比如将114514拖拽到114516后,那么114514就跑到了114516后面。

然后针对子节点[191981,191983]:

 

 可以像父节点一样调换位置,也可以拖拽到其他父节点下面,成为其他父节点的子节点。

但父节点无法成为子节点,子节点也无法成为父节点。

 

本人首先学习了一下QDrag,然后没学懂,

于是就自己重写QTreeWidget的MousePressEvent,MouseMoveEvent以及MouseReleaseEvent来实现了。

 

大概的思路就是:

①在MousePressEvent中获取要拖拽的项目,记为pSource;

②在MouseMoveEvent中显示一个Label,用来更加显性的表示拖拽;

③在MouseReleaseEvent中获取拖拽到的项目,记为pTarget,然后将pSource插入到pTarget周围。

 

整个Demo的代码已上传至github:

https://github.com/pinpeng/QtLittleObject.git

具体为其中的DragTreeWidget文件夹

 

下面是对整个代码进行的简单的说明。

 

代码如下(在每个代码块后面会有简短的说明帮助理解)

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)
{
  isJudged = false;

  pSource = itemAt(ev->pos());

  if(pSource!=nullptr){
      pParent = pSource->parent();
      label.setText(pSource->text(0));
    }

  QTreeWidget::mousePressEvent(ev);
}

在mousePressEvent之中,主要是确定被拖拽的目标。

因为如果这个时候就将item从列表中take出来的话,那么将无法实现将列表展开的功能。

所以具体将列表take出来的行为,放入到mouseMoveEvent之中进行。

然后,这就导致了mouseMoveEvent中的下一个坑(悲

 

void DragTreeWidget::mouseMoveEvent(QMouseEvent *ev)
{
  if(pSource==nullptr){
      QTreeWidget::mouseMoveEvent(ev);
      return;
    }

  label.show();
  label.move(ev->pos());

  if(isJudged==false){
      isJudged = true;
      //将item从parent中脱离出来
      //并且记录原始的位置,方便后面复原
      if(pParent){
          originIndex = pParent->indexOfChild(pSource);
          pParent->takeChild(originIndex);
        }
      else{
          originIndex = indexOfTopLevelItem(pSource);
          takeTopLevelItem(originIndex);
        }
    }
}

 因为需要在mouseMoveEvent之中将被拖拽的目标给take出来,所以会出现一个问题:

mouseMoveEvent并不是只执行一次,而是每隔一段时间就会执行一次,也就是执行多次。

为了可能可以节省的效率,所以这里用了isJudge来判断,避免每次调用时都执行一次takeChild/takeTopLevelItem函数。

 

void DragTreeWidget::mouseReleaseEvent(QMouseEvent *ev)
{
  //无论如何都先隐藏一下label
  label.hide();

  QTreeWidgetItem* pTarget = itemAt(ev->pos());

  //如果拖动到没有item的地方
  if(pTarget==nullptr){
      //如果是子节点,那么返回原位
      if(pParent){
          pParent->insertChild(originIndex,pSource);
        }
      //如果是父节点,那么移动到最下方
      else{
          addTopLevelItem(pSource);
        }
    }
  //如果拖动到子节点处
  else if(pTarget->parent()){
      //如果将子节点A拖动到子节点B处,那么将子节点A放置到子节点B的下方
      if(pParent){
          pTarget->parent()->insertChild(
                pTarget->parent()->indexOfChild(pTarget),pSource);
        }
      //如果将父节点A拖动到子节点B处,那么父节点A回归到原位
      else{
          insertTopLevelItem(originIndex,pSource);
        }
    }
  //如果拖动到父节点处
  else{
      //如果将子节点A拖动到父节点B处,那么将子节点A添加到父节点B中
      if(pParent){
          pTarget->addChild(pSource);
        }
      //如果将父节点A拖动到父节点B处,那么将父节点A放置到父节点B的下方
      else{
          insertTopLevelItem(indexOfTopLevelItem(pTarget),pSource);
        }
    }
  //设置当前的焦点为pSource
  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)
{
  isJudged = false;

  pSource = itemAt(ev->pos());

  if(pSource!=nullptr){
      pParent = pSource->parent();
      label.setText(pSource->text(0));
    }

  QTreeWidget::mousePressEvent(ev);
}

void DragTreeWidget::mouseMoveEvent(QMouseEvent *ev)
{
  if(pSource==nullptr){
      QTreeWidget::mouseMoveEvent(ev);
      return;
    }

  label.show();
  label.move(ev->pos());

  if(isJudged==false){
      isJudged = true;
      //将item从parent中脱离出来
      //并且记录原始的位置,方便后面复原
      if(pParent){
          originIndex = pParent->indexOfChild(pSource);
          pParent->takeChild(originIndex);
        }
      else{
          originIndex = indexOfTopLevelItem(pSource);
          takeTopLevelItem(originIndex);
        }
    }
}

void DragTreeWidget::mouseReleaseEvent(QMouseEvent *ev)
{
  //无论如何都先隐藏一下label
  label.hide();

  QTreeWidgetItem* pTarget = itemAt(ev->pos());

  //如果拖动到没有item的地方
  if(pTarget==nullptr){
      //如果是子节点,那么返回原位
      if(pParent){
          pParent->insertChild(originIndex,pSource);
        }
      //如果是父节点,那么移动到最下方
      else{
          addTopLevelItem(pSource);
        }
    }
  //如果拖动到子节点处
  else if(pTarget->parent()){
      //如果将子节点A拖动到子节点B处,那么将子节点A放置到子节点B的下方
      if(pParent){
          pTarget->parent()->insertChild(
                pTarget->parent()->indexOfChild(pTarget),pSource);
        }
      //如果将父节点A拖动到子节点B处,那么父节点A回归到原位
      else{
          insertTopLevelItem(originIndex,pSource);
        }
    }
  //如果拖动到父节点处
  else{
      //如果将子节点A拖动到父节点B处,那么将子节点A添加到父节点B中
      if(pParent){
          pTarget->addChild(pSource);
        }
      //如果将父节点A拖动到父节点B处,那么将父节点A放置到父节点B的下方
      else{
          insertTopLevelItem(indexOfTopLevelItem(pTarget),pSource);
        }
    }
  //设置当前的焦点为pSource
  setCurrentItem(pSource);
}

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM