6

In the proprietary code base I'm currently working with, there is a custom list view (derived form QListView).

Currently it has an issue when lots of items (>10000) make the main thread freeze. Every item in the view is a custom widget designed in QtDesigner.

To render every row, we use setIndexWidget, which is called on QAbstractItemModel::rowsInserted signal. For every inserted row, from first to last custom widget is set for every index.

I tried to port this code to use QStyledItemDelegate, because disconnecting item widget from actual model seems to solve slow rendering.

Qt5 in that case can render items in view lazily, on demand. We will not need to create every widget for view before displaying the list.

I achieved initial results, using a class which is derived from QStyledItemDelegate. I create a list item widget in constructor and then override paint event like this.

    void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index) const override {
        auto baseWid = getBaseWidget(); // Get's list item widget pointer
        setSubEditorData(baseWid,index); // Set's it's state to display current item
        baseWid->resize(option.rect.size());
        QPixmap pixmap(option.rect.size());
        baseWid->render(&pixmap);
        painter->drawPixmap(option.rect, pixmap);
    }

This is sufficient for static content, but my widget has checkboxes and can be selected.

I don't really understand how to make it interactive, while preserving benefits delegates provide (rendering on demand and so on).

My question is how to make delegate handle user events? Like mouse clicks, selection changes.

Qt5 examples covering delegates are too simple, I don't understand how to draw with delegate a custom widget.

4
  • 1
    Why it inserts 10K widgets at once instead of fetching them on demand? I think you should fix rather the model instead of the delegate.
    – vahancho
    Commented Aug 19, 2020 at 7:26
  • @vahancho legacy code
    – Inline
    Commented Aug 19, 2020 at 7:35
  • Can you override the original model and replace it in the view?
    – vahancho
    Commented Aug 19, 2020 at 7:51
  • @vahancho yes, it's possible.
    – Inline
    Commented Aug 19, 2020 at 7:53

1 Answer 1

3

The best workaround I tried to use involves dynamic switching between the static rendered QPixmap and real widget when mouse moves or delegate receives event.

First I override QAbstractItemDelegate::editorEvent, so when delegate receives any event it switches to real widget for that QModelIndex.

Switching to real widget is done using QAbstractItemView::openPersistentEditor. After that call QAbstractItemDelegate::createEditor is automatically invoked to obtain widget.

bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
{
    auto view = qobject_cast<NyView*>(parent());
    view->openPersistentEditor(index);
    return Base::editorEvent(event, model, option, index);
}

In my view I also enabled every described way to open editor.

this->setEditTriggers(QAbstractItemView::EditTrigger::AllEditTriggers);
this->setMouseTracking(true);
connect(this, &MyView::entered, this, &MyView::openPersistentEditor);

QAbstractItemView::entered signal is emitted when mouse is hovered on widget.

At user perspective nothing changes, before they can interact with list item it's already dynamically replaces with real widget.

Some garbage collection strategy seems to be necessary too, because if user hovers over many widgets they remain in memory even if they didn't interact with it for long time. For deleted rows editors are automatically destroyed, but this may be insufficient.

Open source Qt5 based software (Telegram Desktop) renders large list without using widgets. They render list manually overriding QWidget::paint method and implementing virtualization (drawing only what is seen on screen).

1
  • I was actually going to recommend a similar solution to this one. Creating custom widgets when populating your container widget can be really expensive. I see you have connected MyView::entered() to MyView::openPersistenEditor(), but I think you should also call MyView::closePersistentEditor() in order to delete the widget. Otherwise you might have a memory leak. That's because createEditor() is creating the widget on the heap, and either QWidget::close() or QWidget::deleteLater() must be called on it in order to free the memory.
    – santahopar
    Commented Aug 21, 2020 at 18:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.