Adjust the height of QListView to fit the content

9.5k Views Asked by At

I want the height of a QListView based on an QAbstractListModel to fit the contents, if the amount of the items is smaller a given number N. If there are more than N items, it should show only N items. I read a lot of curious tips in the web, but most of them look like hacks. I guess this has something to do with sizeHint() but in the model view approach there is no ItemWidget in which I could override the sizeHint(). What is the correct way to achieve this behaviour?

Further, how does this correlate to the size policy of the parent app? This is a second constraint: The contents should not try to use the space they have in the parent widget, but the parent widget should resize to fit the QListView.

This is not a duplicate to this question, since I can't use QCompleter.

4

There are 4 best solutions below

2
On BEST ANSWER

sizeHint() has to be overridden in the QListView (respectively your subclass of it). The mentioned special behaviour can be implemented there. eg like this:

QSize ProposalListView::sizeHint() const
{
    if (model()->rowCount() == 0) return QSize(width(), 0);
    int nToShow = _nItemsToShow < model()->rowCount() ? _nItemsToShow : model()->rowCount();
    return QSize(width(), nToShow*sizeHintForRow(0));
}

This requires the size hint of the item delegate to be reasonable. In my case:

inline QSize sizeHint ( const QStyleOptionViewItem&, const QModelIndex& ) const  override { return QSize(200, 48); }

Now I just have to call updateGeometry() after changing the model.

0
On

For those who use ListView in QML with QtQuick Controls. I made the content fit with property anchors.bottomMargin.

anchors.bottomMargin: 20
0
On

I have faced the same problem. The solution marked as answer doesn't work correctly when you have scroll bars or you set a frame border. So here is my solution, which is up to date, that works for all QAbstractItemViews.

Since Qt 5.2 version QAbstractItemView has method setSizeAdjustPolicy, inherited from QAbstractScrollArea. If you set QAbstractScrollArea::AdjustToContents than the scroll area will always adjust to the viewport (content).

But the view by default can not be smaller than it's minimum size hint's. So here is what should be done to make an item view fully shrink when the model doesn't have any items:

  1. Set setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents)

  2. Override minimumSizeHint:

    QSize minimumSizeHint() const override {
        return QSize(0, 0);
    }
    
  3. Override viewportSizeHint (when model has no items, QTreeView and QListView has a default size hint for the viewport, so need to return (0,0) in those cases):

    QSize viewportSizeHint() const override {
        if (QAbstractItemView::sizeAdjustPolicy() != QAbstractScrollArea::AdjustToContents)
            return T::viewportSizeHint();
    
        // if QTableView is used, comment the block below
        if (model() == nullptr)
            return QSize(0, 0);
        if (model()->rowCount() == 0)
            return QSize(0, 0);
    
        // T is your view type (QTreeView, QTableView, QListView, etc.)
        return T::viewportSizeHint();
    }
    

NOTE:

  1. Don't forget to set sizePolicy or add stretch to the layout.
  2. Not hidden scroll bars increase viewport's minimum size.

Also QListView had problems (bug report) with AdjustToContents flag, that were fixed since Qt 6.2. So if you use a version below 6.2 override viewportSizeHint like this:

QSize viewportSizeHint() const override
{
    if (QAbstractItemView::sizeAdjustPolicy() != QAbstractScrollArea::AdjustToContents)
        return T::viewportSizeHint();

    if (std::is_same<T, QTreeView>::value || std::is_same<T, QListView>::value)
    {
        if (model() == nullptr)
            return QSize(0, 0);
        if (model()->rowCount() == 0 || model()->columnCount() == 0)
            return QSize(0, 0);
    }

    if (std::is_same<T, QListView>::value)
    {
        const int rowCount = model()->rowCount();
        int height = 0;
        for (int i = 0; i < rowCount; i++) {
            height += T::sizeHintForRow(i);
        }
        return QSize(T::viewportSizeHint().width(), height);
    }

    return T::viewportSizeHint();
}
1
On

There is no good way to do this. I use the following code.

Header:

class List_view_auto_height : public QObject {
  Q_OBJECT
public:
  explicit List_view_auto_height(QListView * target_list);
  void set_max_auto_height(int value);
  void set_min_height(int value);

private:
  QListView* list;
  QTimer timer;
  int _min_height;
  int _max_height;

  bool eventFilter(QObject* object, QEvent* event);

private slots:
  void update_height();
};

Source:

List_view_auto_height::List_view_auto_height(QListView *target_list) :
  QObject(target_list)
, list(target_list)
{
  _min_height = 0;
  _max_height = 250;
  connect(list->model(), &QAbstractItemModel::rowsInserted,
          this, &List_view_auto_height::update_height);
  connect(list->model(), &QAbstractItemModel::rowsRemoved,
          this, &List_view_auto_height::update_height);
  connect(list->model(), &QAbstractItemModel::layoutChanged,
          this, &List_view_auto_height::update_height);
  list->installEventFilter(this);
  update_height();
  connect(&timer, &QTimer::timeout, this, &List_view_auto_height::update_height);
  timer.start(500);
}

void List_view_auto_height::set_max_auto_height(int value) {
  _max_height = value;
  update_height();

}

void List_view_auto_height::set_min_height(int value) {
  _min_height = value;
  update_height();
}

bool List_view_auto_height::eventFilter(QObject *object, QEvent *event) {
  if (event->type() == QEvent::Show) {
    update_height();
  }
  return false;
}

void List_view_auto_height::update_height() {
  if (!list->isVisible()) { return; }
  int height = 0;
  if (list->model()->rowCount() > 0) {
    height = list->visualRect(list->model()->index(list->model()->rowCount() - 1, 0)).bottom() + 1;
    height -= list->visualRect(list->model()->index(0, 0)).top();
  }
  if (list->horizontalScrollBar()->isVisible()) {
    height += list->horizontalScrollBar()->height();
  }
  bool scrollbar_enabled = false;
  if (_max_height != 0 && height > _max_height) {
    height = _max_height;
    scrollbar_enabled = true;
  }
  if (height < _min_height) {
    height = _min_height;
  }
  list->setFixedHeight(height + 6);
}

Usage:

new List_widget_auto_height(list);

It's full of hacks and can work incorrectly in some cases. Feel free to improve it.

It sets height using setFixedHeight. This should provide correct behavior for parent widget's size hint.