3
votes

I'm developing a Qt application with QTreeWidget on Qt Designer form. User can press add-new-item button and new item will appear with default name, after that user must enter item's name.

So this is my code:

void MyFormClass::on_addNewItemButton_clicked()
{
    auto newItem = new QTreeWidgetItem({ _defaultName });
    newItem->setFlags(newItem->flags() | Qt::ItemIsEditable);
    ui->tree->addTopLevelItem(newItem);
    ui->tree->editItem(newItem);
}

In MyFormClass i also catch itemChanged signal to do some operations with newly created item and it's name:

void MyFormClass::on_tree_itemChanged(QTreeWidgetItem *item, int column)
{
    if (item->text(0).isEmpty()) {
        ...
    } else {
        ...
    }
}

And everything works fine except case when user doesn't change anything and just press Enter/left-click somewhere. This case QTreeWidget [probably] check that item has not been actually changed and do not emits proper signal.

So any ideas how i can create new item, then allow user to edit it immediately and finally catch any edit result (i.e. same with default)? Maybe using QTreeView will help?

Qt 5.4.2, C++11, Linux/Windows platforms

Bonus questions: why Qt designed this way? I mean, isn't it my affair to check whether item has changed or not? OK, signal is called itemChanged, but why there is no editFinished signal or something?

2

2 Answers

3
votes

I guess the right scope to apply the approach would be QTreeWidget. So, we can inherit QTreeWidget by MyTreeWidget.

 ui->tree = new MyTreeWidget(this); // or how exactly it is initialized

There a million mysteries in our work field. We can try and trap the culprit when the thing is not working as we want it to work. When I am running in Qt widget behaving not the way I expect it, I trace what it does:

bool MyTreeWidget::event(QEvent* pEvent)
{
    qDebug() << pEvent;
    // parent class method call
    return QTreeWidget::event(pEvent);
}

And then with the log recorded for that specific case I either try to overload specific event (better and safer) or do it immediately in overloaded generic event handler:

bool MyTreeWidget::event(QEvent* pEvent)
{
    // suppose this to be a 'culprit' event
    if (pEvent->type() == QEvent::WindowDeactivate)
        doTheRightThingHere(); // what expected to be done

    // parent class method call
    return QTreeWidget::event(pEvent);
}

The same approach can be applied with Event Filter.

3
votes

First i've followed @AlexanderVX advice and catched events. After user done editing in any way, QEvent::ChildRemoved will come in all cases. I wrote some code to catch this event end handle it.

But after few days of researching i've learned some information about item delegates, which can handle user editing, so this is my solution:

class MyEditingDelegate : public QItemDelegate
{
    Q_OBJECT

public:
    MyEditingDelegate(QObject *parent = nullptr) : QItemDelegate(parent) {}
    virtual void setModelData(QWidget *editor, 
                              QAbstractItemModel *model, 
                              const QModelIndex &index) const
    {
        QItemDelegate::setModelData(editor, model, index);
        if (index.column() == 0) {
            emit editingFinished(index);
        }
    }

signals:
    void editingFinished(const QModelIndex &) const;
};

class MyTreeWidget : public QTreeWidget
{
public:
    MyTreeWidget(QWidget *parent = nullptr) : QTreeWidget(parent) {}
    QTreeWidgetItem *getItemFromIndex(const QModelIndex &index) const 
    {
        return itemFromIndex(index);
    }
};

MyFormClass::MyFormClass()
{
    ...

    auto editDelegate = new MyEditingDelegate(this);
    ui->tree->setItemDelegate(editDelegate);
    connect(editDelegate, 
            &MyEditingDelegate::editingFinished, 
            [this] (const QModelIndex &index) {
        auto item = ui->tree->getItemFromIndex(index);
        onItemEditingFinished(item); // this is my handler
    });

    ...
}