Перетаскивание QTreeWidgetItem не работает должным образом

Я создал подкласс QTreeWidget, чтобы повторно реализовать protected функциональные возможности drag и drop, чтобы перетаскивать родительские и дочерние элементы QTreeWidget.

Это почти работает, и проблема в том, что как только я пытаюсь перетащить детей или родителей, они стираются, как только я их бросаю. Исходный код можно найти здесь, если вам нужно посмотреть, что происходит.

Также, если я пытаюсь перетащить родителя, он, к сожалению, не перемещается и ничего не происходит.

Смотрите ниже странный эффект для детей:

Я перетаскиваю дочерний оригинал, чтобы поместить его прямо под образец, и эта процедура работает правильно:

ddrop

После удаления оригинала, как вы можете видеть, индекс, кажется, есть, но он кажется частично стертым:

ddrop2

Я точно не знаю, что происходит и почему дети, кажется, не совсем правильно отбрасываются и почему родители буквально не двигаются.

Ниже код минимального проверяемого примера:

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 Я хотел сохранить конструктор в том виде, в каком я его изначально структурировал, и понял, что передача через 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) После дополнительных исследований я наткнулся на этот источник и другой источник, который был полезен (в частности, последний пост) в качестве руководства для реализация подкласса QTreeWidget.

Теперь я застрял, пытаясь понять, почему, несмотря на все, что я пробовал, я добился 90% рабочего приложения и, следовательно, чего в нем не хватает?

Спасибо за подсказку, как решить эту проблему.


person Emanuele    schedule 12.10.2020    source источник
comment
Если вам просто нужно перетаскивать, зачем вам подкласс?   -  person Ngoc Minh Nguyen    schedule 12.10.2020
comment
Мне кажется, что виновником является раздел назначения флажка вашего кода. Если вы закомментируете эту часть, элементы можно будет перетаскивать как обычно. Возможно, флажки обрабатывают события мыши.   -  person Ngoc Minh Nguyen    schedule 12.10.2020
comment
@NgocMinhNguyen, спасибо, что заглянули и прочитали вопрос. Конечно!! Виновником являются флажки!! Если я прокомментирую, что для цикла все работает нормально... но я не понимаю, почему... Я тоже добавил этот класс в вопрос. Вы видите что-то конкретное, что вызывает такое поведение?   -  person Emanuele    schedule 12.10.2020


Ответы (1)


Я думаю, что флажки обрабатывают события мыши и не позволяют событиям проходить к элементу. Чтобы иметь желаемое поведение, вы должны удалить эту часть:

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;
}
person Ngoc Minh Nguyen    schedule 12.10.2020
comment
Я понимаю! Да, теперь он работает очень хорошо именно так, как я искал! Большое спасибо за ваши правки и за потраченное время на чтение и ответы на мой вопрос! :) :) - person Emanuele; 12.10.2020