QWidget in QTreeWidgetItem disappearing after reordering the QTreeWidgetItem
Asked Answered
O

1

7

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

Ointment answered 18/5, 2018 at 22:6 Comment(10)
why do you use part->setFlags(part->flags() & Qt::ItemFlag((~Qt::ItemIsDropEnabled)));Pasteurize
@eyllanesc, because I don't want the top level item to be dropped into another top level item. I don't want also the child elements to be able to be moved around, I only want the user to be able to change the order of the top level nodes.Ointment
I just have time and I'm going to work on this answer. In summary you wish that the parents can only move at the same level and the children can interchange with other children of other parents. Also you want to have a QPushButton that when pressed change your background image. I am right?Pasteurize
@eyllanesc, exactly. I am also going to subclass the QPushButton, so it can connect to my application model and change its appearance based on the model. However, you don't worry about that part. I understand that QTreeView has its own model, so you can just base everything on that. Thank you:)Ointment
Okay, I recommend extending the bounty, so you will attract other answers :)Pasteurize
@eyllanesc, I'm looking into it, it won't let me... how do I extend it?Ointment
I think that when you finish you can do it again.Pasteurize
@eyllanesc, sounds good!Ointment
@eyllanesc, I am going to accept your answer though, because so far no one has even attempted to solve the issue. I hope you don't mind.Ointment
No problem, I'll be waiting for the bounty when I post my updated response :)Pasteurize
P
3

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();
}
Pasteurize answered 21/5, 2018 at 3:23 Comment(12)
thanks a lot for the detailed answer. I was actually exploring similar ways, but this is great help! One question though, if I decide to have a QPushButton instead of QLineEdit, do I handle it the same way, or it has to be handled differently?Ointment
@armanali with QLineEdit can you edit the stored text, in the case of QPushButton you want to do when the button is pressed?Pasteurize
my plan is to subclass QPushButton, make it checkable, and overlay it with an image, so when checked and unchecked it will show a different image. It's pretty much for holding the state, but I don't want to have a QCheckBox there. Also this button will contain the whole application model, and connect to its signals, so that if something pertaining to it in the model changes, the QPushButton will change too.Ointment
I also see methods like setEditorData, setModelData, am I supposed to be using them too at all?Ointment
@armanali Yes, I will show you an example, in the case of QLineEdit do not modify them since the default delegate is a widget that inherits from QLineEdit that has no borders and uses the functions of QLineEdit, so it only subclassifies it to show the borders.Pasteurize
@ellynesc, and in the case of QPushButton, do I have to modify them? How about in the case of QLabel?Ointment
@armanali I'm working on the QPushButton case. What do you want to happen in the case you want a QLabel ?, I ask you because a QLabel serves to show something and does not serve to edit information.Pasteurize
for QLabel nothing really, it's going to be static and it may not necessarily be a top level item, it may also be a few levels down. I just don't want it to lose its image, that's all.Ointment
@armanali Although using widgets is many times easier since you have many things done, in your case I would recommend not doing it but creating them using QPainter and the methods of QStyledItemDelegate, Qt offers an example of how to create them: doc.qt.io/qt-5/qtwidgets-itemviews-stardelegate-example.htmlPasteurize
I have run that example, and upon reordering the stars were getting lost... which is why I decided to resort to QTreeWidget. But now it seems like with that example and your sample code I can get it all to work. Is that so?Ointment
@armanali Let me review, I just saw that my example of the button also has that problem, where I live is already late and I have to rest, tomorrow I try it and I improve my example.Pasteurize
Let us continue this discussion in chat.Ointment

© 2022 - 2024 — McMap. All rights reserved.