Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QWidget in QTreeWidgetItem disappearing after reordering the QTreeWidgetItem

I have subclassed QTreeWidget (called it ToolsSelectorWidget) and enabled reordering in it by overriding QTreeWidget::dropEvent()

void ToolsSelectorWidget::dropEvent(QDropEvent *event) {
    QModelIndex droppedIndex = indexAt(event->pos());

    if( !droppedIndex.isValid() || droppedIndex.parent().isValid()) {
        return;
    }
   QTreeWidget::dropEvent(event);
}

Also, I am adding QWidgets (QPushButton, QLineEdit) to top level items of QTreeWidget:

ToolsSelectorWidget::ToolsSelectorWidget(QWidget *parent) : QTreeWidget(parent) {
    header()->hide();
    setSelectionMode(QAbstractItemView::SingleSelection);
    setDragEnabled(true);
    viewport()->setAcceptDrops(true);
    setDropIndicatorShown(true);
    setDragDropMode(QAbstractItemView::InternalMove);

    for(int i=0; i<4; ++i) {
        QTreeWidgetItem *part = new QTreeWidgetItem(this);
        part->setFlags(part->flags() & Qt::ItemFlag((~Qt::ItemIsDropEnabled)));
        setItemWidget(part, 0, new QLabel("Part" + QString::number(i) + " Preview", this));
        setItemWidget(part, 1, new QLineEdit("Part" + QString::number(i) + " Name", this));
        setItemWidget(part, 2, new QCheckBox("Part" + QString::number(i) + " Visible", this));
        setItemWidget(part, 3, new QCheckBox("Part" + QString::number(i) + " Locked", this));

    }
}

So now I have 4 top level items each containing 4 QWidgets. It's populating them fine, but when I rearrange them by drag and drop the QWidgets disappear and I end up having an empty row. What should I do to preserve them?

Before:

enter image description here

After Part2 has been moved and is under Part4, it's children have been preserved, but it's conents, which are QWidgets, are gone:

enter image description here

like image 604
aalimian Avatar asked May 18 '18 22:05

aalimian


1 Answers

Why are widgets deleted?

When the drag and drop is performed, the data of the selected items is coded (roles and associated values) and saved in a QMimeData. When the drop is accepted, the source items are deleted and new items are created with the information stored in the QMimeData, inside the saved information there is no widgets information since this does not have relation with the model. And since the items are deleted, their widgets are also deleted.

To check it we can use the following example

#include <QApplication>
#include <QLabel>
#include <QTreeWidget>
#include <QDebug>

static void on_destroyed(){
    qDebug()<<"destroyed";
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTreeWidget w;
    w.setSelectionMode(QAbstractItemView::SingleSelection);
    w.setDragEnabled(true);
    w.viewport()->setAcceptDrops(true);
    w.setDropIndicatorShown(true);
    w.setDragDropMode(QAbstractItemView::InternalMove);

    for(int i=0; i< 5; i++){
        QTreeWidgetItem *it = new QTreeWidgetItem(&w);
        QLabel *lbl = new QLabel(QString::number(i));
        QObject::connect(lbl, &QObject::destroyed, on_destroyed);
        w.setItemWidget(it, 0, lbl);
    }
    w.show();
    return a.exec();
}

It shows that the widgets will emit the signal they destroy when you drag and drop the items.

Possible workaround:

One possible solution is to remove the widgets before accepting the drop and set them in the new items which I have not implemented.

I have explored another solution, it is to change the QTreeWidget for a QTreeView + QStandardItemModel. In the case of the QCheckBox, the checkboxes with the Qt::ItemIsUserCheckable flag are enabled, in the case of the QLineEdit a delegate will be used and to always be shown, the openPersistentEditor() method must be used.

#include <QApplication>
#include <QStandardItemModel>
#include <QTreeView>
#include <QHeaderView>
#include <QDropEvent>
#include <QStyledItemDelegate>
#include <QLineEdit>

class ToolsSelectorDelegate: public QStyledItemDelegate{
public:
    using QStyledItemDelegate::QStyledItemDelegate;
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const{
        QLineEdit *le = new QLineEdit(parent);
        return le;
    }
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const{
        QRect r(option.rect);
        r.adjust(2, 2, -2, -2);
        editor->setGeometry(r);
    }
};

class ToolsSelectorWidget: public QTreeView{
    QStandardItemModel model;
public:
    ToolsSelectorWidget(QWidget *parent=nullptr): QTreeView(parent){
        setItemDelegate(new ToolsSelectorDelegate(this));
        setModel(&model);
        header()->hide();
        setSelectionMode(QAbstractItemView::SingleSelection);
        setDragEnabled(true);
        viewport()->setAcceptDrops(true);
        setDropIndicatorShown(true);
        setDragDropMode(QAbstractItemView::InternalMove);

        for(int i=0; i<4; ++i) {
            QList<QStandardItem *> items;
            for(const QString & text: {"Preview", "Name", "Visible", "Locked"}){
                QStandardItem *it = new QStandardItem(QString("Part%1 %2").arg(i).arg(text));
                it->setFlags(it->flags() & ~Qt::ItemIsDropEnabled & ~Qt::ItemIsEditable);
                items.append(it);
                if(text == "Visible" || text == "Locked"){
                    it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
                    it->setCheckState(Qt::Unchecked);
                }
                else if (text == "Name") {
                    it->setFlags(it->flags() | Qt::ItemIsEditable);
                }
            }
            for(const QString & children: {"The", "quick", "Brown", "fox", "jump...", "over", "the", "lazy", "dog"})
                items.first()->appendRow(new QStandardItem(children));

            model.invisibleRootItem()->appendRow(items);
            for( int i = 0; i < model.rowCount(); ++i )
                openPersistentEditor(model.index(i, 1));
        }
    }
protected:
    void dropEvent(QDropEvent *event) {
        QModelIndex droppedIndex = indexAt(event->pos());
        if( !droppedIndex.isValid() || droppedIndex.parent().isValid())
            return;
        QTreeView::dropEvent(event);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ToolsSelectorWidget w;
    w.show();
    return a.exec();
}
like image 136
eyllanesc Avatar answered Nov 15 '22 11:11

eyllanesc