PDA

View Full Version : How to make QListView consume the space when rows decrease in height on h-resize?



JmpNop
20th December 2014, 14:46
I have a QListView working with a custom model and a custom delegate, mainly for displaying two strings of word-wrapped text vertically on top of each other as one item.
I've reimplemented QStyledItemDelegate::sizeHint() and everything seems to work fine.

Except:
1) When I resize the QListView horizontally so its width increases, the height of the individual items should shrink (due to word-wrapping). I know my delegate reports decreasing heights via its sizeHint().
2) But somehow the view doesn't seem to "get" the items' new heights internally, which leads to gaps between the rows/items i. e. the next item is drawn further down than necessary.


3) When I decrease the width of the widget my QListView is in the individual items' height increases. No problem with that.
4) It only happens on horizontal resize. As soon as I vertically resize the window/widget QListView is in, all rows just "snap" into place.
5) Similarly, as soon as my model emits a dataChanged() signal for a row with a height too large, it just snaps into place.
6) I do call QListView::setResizeMode(QListView::Adjust);
7) I'm using Qt 4.8.1.

Am I missing something? Do I need to hack around this?

wysota
23rd December 2014, 08:45
1) When I resize the QListView horizontally so its width increases, the height of the individual items should shrink (due to word-wrapping). I know my delegate reports decreasing heights via its sizeHint().
Do you emit sizeHintChanged() signal to inform about the change? Without it the view will not know it needs to read the sizeHint again.

JmpNop
26th December 2014, 18:05
Ah, thank you! No, I don't.
How is the delegate supposed to know about changed sizes when sizeHint() isn't even called though?
Where is it supposed to emit this signal? :confused:

wysota
26th December 2014, 18:17
How is the delegate supposed to know about changed sizes when sizeHint() isn't even called though?
Where is it supposed to emit this signal? :confused:

You need to implement it yourself. You can install an event filter on the view's viewport to be notified when it is resized. Then the delegate can emit signals for items that need updating.

JmpNop
27th December 2014, 00:17
What I'd have to do after connecting the view's resize event to the delegate is basically to emit sizeHintChanged signals for the whole of my model. That just doesn't appear to be the "right" way to do it, does it?
After all, the cause for the update is completely "external", not something in the data itself (which is what I think that the delegate's sizeHintChanged signal is probably intended for).

Isn't there a way to just tell the view to recalculate its items' sizes? It works for 'growing' items already... Maybe I could try to simulate a model-reset with saving/restoring the view's selection-state if there isn't a "best practice way"? All of this seems to be rather hackish though. It thought there has to be a "clean" way to do it...

wysota
27th December 2014, 01:32
What I'd have to do after connecting the view's resize event to the delegate is basically to emit sizeHintChanged signals for the whole of my model.
No, just for the visible items and only if their height changes. If an item already fits a single line, increasing the width of the item will not influence its height. Same goes the other way -- it seems logical to limit the maximum height of an item.


After all, the cause for the update is completely "external", not something in the data itself
The cause for the update is the way you want each item displayed which is the responsibility of the delegate (or the view, depending how you look at it). If data was changing, you'd emit dataChanged().


Isn't there a way to just tell the view to recalculate its items' sizes?
Yes. Emit sizeHintChanged() from the delegate. Alternatively reimplement visualRect() in the view and force an update of the whole viewport.

JmpNop
27th December 2014, 15:10
No, just for the visible items and only if their height changes.
I'd have to keep track of all the items' heights in the delegate then...

I've found a simpler, but hackish solution:
qlistview.html#spacing-prop

Setting this property when the view is visible will cause the items to be laid out again.
The event filter object that catches resize events for the view's viewport is connected to a slot that calls setSpacing(0) on the view.
Now it 'works' ...

wysota
27th December 2014, 15:37
I'd have to keep track of all the items' heights in the delegate then...
In the resize event you have access to both the old and the new size. Using those values it should be easy to decide whether the span changes or not.


I've found a simpler, but hackish solution:
qlistview.html#spacing-prop
Kind of not-so-very-performant solution.

JmpNop
30th December 2014, 18:24
You are right of course.
Since I need to display some richtext im my items, I'm now creating a QTextDocument on the stack for every sizeHint and paint of my delegate. That's really ugly.
How would I go about caching the visible items (rows)?

My solution would be to get a list from the view via indexAt and visualRect that is updated on every view-scrollbar-change. Than I could cache the data in the delegate and provide sizeHints and sizeHintChanged signals from there. Maybe with a hashtable row -> struct CacheData or something like that... Data for items that cease to be visible would be destroyed after some period.
Do you have any suggestions or comments?
Thank you for your help so far.

wysota
30th December 2014, 22:43
When using linear data in general QContignousCache is your friend. If you want a cache of QTextDocuments (or whatever else you need) for your items in the delegate, go ahead. I never tried this and I'm very interested about the resulting performance of such approach. It might not be a bad idea. When I was implementing a rich text delegate, I was reusing a single document for different items.

JmpNop
1st January 2015, 20:12
A single QTextDocument is probably not an option, since I suspect all the HTML /CSS parsing to be quite slow. I didn't measure anyhing yet, though. Still busy with other stuff. Richtext is not my friend. ^^
Have a happy new year :)

JmpNop
3rd January 2015, 20:31
Since I had the same problem (rows' heights) with a QTreeView now, I did some digging through Qt's source code.
Look what I've found:


.../qtbase/src/widgets/itemviews$ grep -n sizeHintChanged *
qabstractitemdelegate.cpp:163: \fn void QAbstractItemDelegate::sizeHintChanged(const QModelIndex &index)
qabstractitemdelegate.h:120: void sizeHintChanged(const QModelIndex &);
qabstractitemview.cpp:834: disconnect(d->itemDelegate, SIGNAL(sizeHintChanged(QModelIndex)), this, SLOT(doItemsLayout()));
qabstractitemview.cpp:843: connect(delegate, SIGNAL(sizeHintChanged(QModelIndex)), this, SLOT(doItemsLayout()), Qt::QueuedConnection);

That's all. See how the QModelIndex that is passed to sizeHintChanged is ignored? Any sizeHintChanged signal just triggers a relayout.
Even more interesting is the doItemsLayout() slot. It is public and available in all of Qt's item view widgets, but it is not documented. That's exactly what I was looking for :)
Someone asked if he could rely on that (http://www.qtforum.org/article/37325/using-qtreeview-doitemslayout.html) on another forum a few years ago. So I'm going to repeat the question here. :)

wysota
3rd January 2015, 21:05
If it is undocumented and not a virtual method then you cannot expect the method to work in future Qt releases. Of course it is unlikely that it goes away until Qt 6.0.

JmpNop
3rd January 2015, 21:41
It's virtual. From qabstractitemview.h:


public Q_SLOTS:
virtual void reset();
virtual void setRootIndex(const QModelIndex &index);
virtual void doItemsLayout();
virtual void selectAll();