Resizing QTableView section with custom editor

1.8k Views Asked by At

I have an application with a QTableView and a model derived from QAbstractItemModel: the first column of the table contains a text (a label for each row), while the second column shows a value that can be selected using a QComboBox created from a custom item delegate. The content of the table may change dynamically (number of rows, language...).

I'd like to resize columns so the second one fits the content and the first one stretches occupying the remaining space.

My first attempt was:

tblData->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
tblData->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);

Result:

result1

The problem is that when the QComboBox is selected it doesn't fit the section and it is clipped:

result2

I've managed to solve this issue by manually increasing the width:

tblData->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
tblData->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
tblData->resizeColumnToContents(1);
tblData->horizontalHeader()->resizeSection(1, tblData->horizontalHeader()->sectionSize(1) + 40);

result3

Now the issue here is that by using such constant (40 in this case) the width of the section will vary depending on values displayed rather than in all the possible values (if the largest ones are already displayed vs if only the shortest). Also, that constant will be dependant to the style used, since it is also related to the space consumed by the QComboBox.

I've thought about using the Qt::SizeHintRole to manually compute the section width, but it is completely ignored. Even if it was, I cannot compute the actual width of the text (using QFontMetrics::width) because I don't have any font information in the model.

Another approach I've tried is to set the adjust size policy of the QComboBox in the QItemDelegate::createEditor method:

QWidget* myItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const {
  auto comboBox = new QComboBox(parent);
  comboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
  // ...
}

But now the combo boxes are either clipped or shortened.

result4 result5


How can I solve set the section size based on the complete range of content instead of just visible data?


I'm self-answering the question with the best approach I've found so far, and the one I'm using in the project right now, but I'm not convinced with it (I detail reasons on the answer) so I'd like to know the correct way to do it. Thanks!

2

There are 2 best solutions below

1
On BEST ANSWER

The sizeHint in the delegate is the right way to go but, instead of creating an editor, fill a QStyleOptionComboBox struct and use qApp->style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, nullptr);, where sh is the size of the internal string. You can use QFontMetrics to calculate that or just call the base class QStyledItemDelegate::sizeHint(...).

3
On

The best option I've found so far is to re-implement the QItemDelegate::sizeHint: I have the font information from the QStyleOptionViewItem and the list of elements to be included in the QComboBox.

QSize myItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
  auto hint = QItemDelegate::sizeHint(option, index);

  QFontMetrics fm(option.font);

  std::unique_ptr<QWidget> editor(createEditor(nullptr, option, index));
  auto comboBox = qobject_cast<QComboBox*>(editor.get());
  if (comboBox != nullptr) {
    int width = 0;
    for (int ii = 0; ii < comboBox->count(); ++ii) {
      width = std::max(width, fm.width(comboBox->itemText(ii)) + 20);
    }
    hint.setWidth(std::max(hint.width(), width));
  }

  return hint;
}

Results:

result1 result2


Drawbacks of this solution are:

  • I don't have information regarding the additional space required by the QComboBox so it is not style-independant yet (as with the second approach on the question)
  • If new editors are added to the item delegate then I'd have to include them manually in the size hint computation too, which is not a terrible pain but feels like a bad design.

PS: using the QComboBox::sizeHint here doesn't work since size hint is computed using the QComboBox::sizeAdjustPolicy which, as highlighted in the question, doesn't adjust combo boxes correctly into the cell.


UPDATE

I've updated the solution following the indications from comments and accepted answer. Here is the complete code for future reference:

QStringList myItemDelegate::getPossibleValuesForIndex(const QModelIndex& index) const
{
  // returns list of all possible values for given index (the content of the combo box)
}

QSize myItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
  auto hint = QItemDelegate::sizeHint(option, index);

  QFontMetrics fm(option.font);    
  QStyleOptionComboBox comboOption;
  comboOption.rect  = option.rect;
  comboOption.state = option.state | QStyle::State_Enabled;

  Q_FOREACH (const auto& value, getPossibleValuesForIndex(index)) {
    hint = hint.expandedTo(qApp->style()->sizeFromContents(QStyle::CT_ComboBox,
                           &comboOption, QSize(fm.width(value), hint.height())));
  }

  return hint;
}