QTreeWidgetItem 的拖放不能正常工作

Posted

技术标签:

【中文标题】QTreeWidgetItem 的拖放不能正常工作【英文标题】:Drag and drop of QTreeWidgetItem does not work properly 【发布时间】:2021-01-27 00:19:51 【问题描述】:

我将QTreeWidget 子类化以重新实现dragdropprotected 功能,以便拖放QTreeWidget 的父母和子女。

它几乎可以工作,问题是,一旦我尝试拖放孩子或父母,一旦我放下它们,它们就会被删除。 源代码可以在here 找到,以防您需要查看发生了什么。

此外,如果我尝试拖放父级,不幸的是它不会移动,也没有任何反应。

看看下面对孩子们的奇怪效果:

我将子“Original”拖放到“Sample”下方,此过程似乎可以正常工作:

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

我不确定到底发生了什么,为什么孩子似乎没有完全正确地被丢弃,为什么父母真的不动。

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

ma​​inwindow.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

ma​​inwindow.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(QStringListname);
    item->setIcon(0, QIcon(iconPath));
    return item;

ma​​phelpers.h

#include <QTreeWidget>
#include <QTreeWidgetItem>

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

#endif // MAPHELPERS_H

ma​​phelpers.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 的问题以不同的方式重写构造函数,基本上是下面的实现思想,但不想通过那种方式:

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) 经过更多研究后,我发现了this source 和this other source,它们对实现QTreeWidget 的子类化很有用(尤其是最后一篇文章)。

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

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

【问题讨论】:

如果只需要拖放,为什么还要子类化? 在我看来,代码的分配复选框部分是罪魁祸首。如果注释掉该部分,则可以正常拖放项目。也许复选框正在处理鼠标事件。 @NgocMinhNguyen,感谢您过来阅读问题。确实!!罪魁祸首是复选框!如果我评论说 for 循环一切正常......但我不明白为什么......我也在问题中添加了那个类。您是否看到任何触发该行为的具体内容? 【参考方案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(QStringListname);
    item->setCheckState(0, Qt::Unchecked);
    item->setIcon(0, QIcon(iconPath));
    item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
    return item;

【讨论】:

我明白了!是的,现在它工作得非常好,完全符合我的要求!非常感谢您的编辑以及您花时间阅读和回答我的问题! :) :)

以上是关于QTreeWidgetItem 的拖放不能正常工作的主要内容,如果未能解决你的问题,请参考以下文章

被 SwiftUI 中的拖放所困扰

为啥 Qt 中的拖放停止工作?

SwiftUI - iPad 上的拖放重新排序是不是可以在没有 EditMode 的情况下工作?

使用纯javascript拖放并再次拖动

Javafx 可检索的拖放错误

qt: pyqt: QTreeView 内部拖放几乎可以正常工作...被拖动的项目消失了