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