拖放 QTreeWidgetItem 不能正常工作

Drag and drop of QTreeWidgetItem does not work properly

我将 QTreeWidget 子类化,以便重新实现 dragdropprotected 功能,以便拖放 parent 和 child仁一QTreeWidget.

它几乎可以工作,问题是,一旦我尝试拖放 children 或 parents,它们就会被删除我一放下它们。 如果您需要查看正在发生的事情,可以找到源代码 here

此外,如果我尝试 drag-drop parent,不幸的是它没有移动,也没有任何反应。

见下面children的奇怪效果:

我将 child“原始”拖放到“示例”下方,此过程似乎工作正常:

删除“原始”后,您可以看到索引似乎存在,但似乎被部分删除:

我不确定到底发生了什么,为什么 children 似乎没有完全正确地放下,为什么 parents 确实没有移动。

下面是最小可验证示例的代码:

mainwindow.h

#include <QMainWindow>

class QTreeWidget;
class QTreeWidgetItem;


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    QTreeWidgetItem *createItem(const QString &name, const QString &iconPath);
    
private:
    Ui::MainWindow *ui;
    QTreeWidget *widget;
    QString m_Name;
    QString m_Path;

    };
#endif // MAINWINDOW_H

mainwindow.cpp

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->treeWidget->setDragEnabled(true);
    ui->treeWidget->viewport()->setAcceptDrops(true);
    ui->treeWidget->setDropIndicatorShown(true);
    ui->treeWidget->setDragDropMode(QAbstractItemView::InternalMove);

    auto *top1 = createItem("Images", "/home/ui/qrc/laserscan.png");
    auto *top2 = createItem("Path", "/home/ui/qrc/laserscan.png");

    top1->addChild(createItem("Original", "/home/ui/qrc/laserscan.png"));
    top1->addChild(createItem("Sample", "/home/ui/qrc/laserscan.png"));

    top2->addChild(createItem("Left Side", "/home/ui/qrc/laserscan.png"));
    top2->addChild(createItem("Right Side", "/home/ui/qrc/laserscan.png"));

    ui->treeWidget->addTopLevelItems({ top1, top2 });

   // Below I am assigning to each parent/children a checkbox
    const int n_tops = ui->treeWidget->topLevelItemCount();
    for(int a = 0; a<n_tops; ++a) {
        const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
        qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
        for(int b = 0; b<n_children; ++b) {
            qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
        }
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

QTreeWidgetItem *MainWindow::createItem(const QString &name, const QString &iconPath)
{
    auto *item = new QTreeWidgetItem(QStringList{name});
    item->setIcon(0, QIcon(iconPath));
    return item;
}

maphelpers.h

#include <QTreeWidget>
#include <QTreeWidgetItem>

void qtreewidgetitem_assign_qcheckbox(QTreeWidget *tree_widget, QTreeWidgetItem *tree_item);

#endif // MAPHELPERS_H

maphelpers.cpp

#include <QTreeWidget>

void qtreewidgetitem_assign_qcheckbox(QTreeWidget *tree_widget, QTreeWidgetItem *tree_item)
{
    MyCheckBoxMap *myCheckBoxMap = new MyCheckBoxMap(tree_item);
    tree_widget->setItemWidget(tree_item, MAP_COLUMN, myCheckBoxMap);
}

下面是我如何子类化 QTreeWidget:

customtreewidget.h

#include <QTreeWidget>

class CustomTreeWidget : public QTreeWidget
{
public:
    CustomTreeWidget(QWidget *parent = Q_NULLPTR);
protected:
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dropEvent(QDropEvent *event) override;
private:
    QTreeWidgetItem *draggedItem;
};

#endif // CUSTOMTREEWIDGET_H

customtreewidget.cpp

CustomTreeWidget::CustomTreeWidget(QWidget *parent)
{
    QTreeWidgetItem* parentItem = new QTreeWidgetItem();
    parentItem->setText(0, "Test");
    parentItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled);

    int num_rows = topLevelItemCount();

    for(int i = 0; i < num_rows; ++i)
    {
      int nChildren = topLevelItem(i)->childCount();
      QTreeWidgetItem* pItem = new QTreeWidgetItem(parentItem);
      for(int j = 0; j < nChildren; ++j) {
          pItem->setText(0, QString("Number %1").arg(i) );
          pItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
          pItem->addChild(pItem);
          topLevelItem(i)->child(j);
      }
    }
}

void CustomTreeWidget::dragEnterEvent(QDragEnterEvent *event)
{
    draggedItem = currentItem();
    QTreeWidget::dragEnterEvent(event);
}

void CustomTreeWidget::dropEvent(QDropEvent *event)
{
    QModelIndex droppedIndex = indexAt(event->pos());
    if(!droppedIndex.isValid()) {
        return;
    }

    if(draggedItem) {
        QTreeWidgetItem *mParent = draggedItem->parent();
        if(mParent) {
            if(itemFromIndex(droppedIndex.parent()) != mParent)
                return;
                mParent->removeChild(draggedItem);
                mParent->insertChild(droppedIndex.row(), draggedItem);
        }
    }
}

到目前为止我做了什么以及我尝试了什么:

1) 根据官方文档,可以使用 QTreeWidgetItemIterator Class 我尝试这样做,事实上你可以在下面看到我最初的实现想法,但这并没有真正带我到任何地方:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    auto *top1 = createItem("Images", "/home/ui/qrc/laserscan.png");
    auto *top2 = createItem("Path", "/home/ui/qrc/laserscan.png");
    
    top1->addChild(createItem("Original", "/home/ui/qrc/laserscan.png"));
    top1->addChild(createItem("Sample", "/home/ui/qrc/laserscan.png"));
    
    top2->addChild(createItem("Left Side", "/home/ui/qrc/laserscan.png"));
    top2->addChild(createItem("Right Side", "/home/ui/qrc/laserscan.png"));
    
    ui->treeWidget->addTopLevelItems({ top1, top2 });

    const int n_tops = ui->treeWidget->topLevelItemCount();

    // Below I am assigning to each parent/children a checkbox
    for(int a = 0; a<n_tops; ++a) {
        const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
        qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
        for(int b = 0; b<n_children; ++b) {
            qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
        }
    }

    QTreeWidgetItem *parentItem = Q_NULLPTR;
    QTreeWidgetItem *childItem = Q_NULLPTR;

    QTreeWidgetItemIterator it(ui->treeWidget);
       while (*it) {
           parentItem = new QTreeWidgetItem(ui->treeWidget);
           //parentItem->setText(0, it.key());
           foreach (const auto &str, it) {
               childItem = new QTreeWidgetItem;
               childItem->setText(0, str);
               parentItem->addChild(childItem);
           }
       }
}

2) 在对官方文档进行进一步研究后,我决定应用 QMapIterator Class 来解决这个问题,我想保留最初构造的构造函数并认为通过一个QMap意味着用不同的方式重写构造函数,基本上是下面的实现思路,但不想那样通过:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    auto *top1 = createItem("Images", "/home/ui/qrc/laserscan.png");
    auto *top2 = createItem("Path", "/home/ui/qrc/laserscan.png");
    
    top1->addChild(createItem("Original", "/home/ui/qrc/laserscan.png"));
    top1->addChild(createItem("Sample", "/home/ui/qrc/laserscan.png"));
    
    top2->addChild(createItem("Left Side", "/home/ui/qrc/laserscan.png"));
    top2->addChild(createItem("Right Side", "/home/ui/qrc/laserscan.png"));
    
    ui->treeWidget->addTopLevelItems({ top1, top2 });
    ui->treeWidget->addTopLevelItems({ top1, top2 });

    const int n_tops = ui->treeWidget->topLevelItemCount();

    // Below I am assigning to each parent/children a checkbox
    for(int a = 0; a<n_tops; ++a) {
        const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
        qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
        for(int b = 0; b<n_children; ++b) {
            qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
        }
    }

    QTreeWidgetItem *parentItem = Q_NULLPTR;
    QTreeWidgetItem *childItem = Q_NULLPTR;

    QMapIterator<QString, QStringList> i(treeMap);
    while (i.hasNext()) {
        i.next();
        parentItem = new QTreeWidgetItem(widget);
        parentItem->setText(0, i.key());
        foreach (const auto &str, i.value()) {
            childItem = new QTreeWidgetItem;
            childItem->setText(0, str);
            parentItem->addChild(childItem);
        }
    }
}

3) 经过更多的研究,我发现了 , and this other source 作为实现子类化的指南很有用(尤其是最后一个 post) QTreeWidget.

现在我被困住了,因为我试图弄清楚为什么,尽管我尝试了所有的事情,但我实现了 90% 的工作应用程序,因此,它缺少什么?

感谢您提供有关如何解决此问题的指导。

我认为复选框正在处理鼠标事件并且不让事件传递到项目。要获得所需的行为,您应该删除此部分:

const int n_tops = ui->treeWidget->topLevelItemCount();

for(int a = 0; a<n_tops; ++a) {
    const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
    qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
    for(int b = 0; b<n_children; ++b) {
        qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
    }
}

并且在 createItem() 函数中:

QTreeWidgetItem *MainWindow::createItem(const QString &name, const QString &iconPath)
{
    auto *item = new QTreeWidgetItem(QStringList{name});
    item->setCheckState(0, Qt::Unchecked);
    item->setIcon(0, QIcon(iconPath));
    item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
    return item;
}