1
votes

it's not the first time that I want a scroll area which behaves like the following (imagine a log or chat window, but too complex to use a simple QTextBrowser):

  • I want to add multiple widgets, which appear one below the other (like when placed in a QVBoxLayout)
  • Each widget within this layout should have a fixed size or a height-for-width (like a simple label)
  • The scroll area should auto-scroll to the most recently added one
  • (optional) When there is space left (scroll bar not yet enabled), the contents should be aligned to bottom

Using QScrollArea:

My attempt in the past was using a QScrollArea using a QVBoxLayout inside. But this seems to be not as simple as I thought: Whenever I add a widget to the layout, the layout doesn't resize the scroll area content widget immediately, resulting in a delayed adjustment of the contents. For one short moment, the widgets contained in the layout are resized so that the total size equals the total size before the add operation, resulting in a too small size per widget. Also, scrolling to the newly added widget is thus not possible until the layout corrected its size to the new total size of widgets, so even a QTimer::singleShot(0, ...) doesn't help here. Even with a timeout of 20 or so, there are situations in which the layout needs more time to resize. It's not deterministic, and thus far away from a nice solution.

In order to get the bottom alignment behaviour, I initially place a spacer item in the layout. It won't require any space as soon as there is no space left and scrolling gets enabled.

Using QListView:

As my items are too complex, they need to be QWidgets. They can't have the focus, aren't selectable, so an item-based solution seems to be just "the wrong way". Also, this solution sounds too "heavy".

I just can't believe that there is no easy way, so I think I just haven't seen it yet!

1

1 Answers

1
votes

QListView should be fine. You claim that your items are static, there's no interaction with them: no focus, no selection. It'd seem that a QWidget is an overkill for such items. You only need something that has a fixed size and can draw itself. That is precisely what delegates in the Qt's model-view system are for. Just implement one or more QAbstractItemDelegates for your items, and provide an implementation of a model for the data they will be rendering. The QAbstractItemView is is already a QAbstractScrollArea!

If you want to paint HTML within a delegate, it's easy to do -- again, QWidget is an overkill for a static display! There is a very food reason why it's "hard" to use QWidget for this -- the API guides you to the correct solution. Assuming your model contains html for each item, here's how you can paint it. You can go fancy with the sizeHint, of course, and should be caching the text document, ideally storing it in the model I'd think.

void MyDelegate::paint(QPainter* p, const QStyleOptionViewItem & opt, const QModelIndex & index) const
{
   QTextDocument doc;
   doc.setHtml(index.data().toString());
   doc.drawContents(p, QRect(QPoint(0,0), sizeHint(opt, index)));
}

QSize MyDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
{
   return QSize(100, 200);
}