qtqtableviewqt6qitemdelegate

How to update geometry of item delegate


Qt 6.8.0, Ubuntu 24.10. I wrote a simple delegate for a QSqlTableModel to be shown in a QTableView. It formats a floating-point numbers with a given number of decimals.

Issue When the table adjusts the columns' width to the content it ignores the new content size (large than the initial one, due to the new decimals added).

Working and complete MRE

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSqlRelationalTableModel>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QSqlRelationalTableModel *_model = nullptr;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "delegatereal.h"

#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
#include <QSqlRelationalDelegate>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "mydb");
    db.setDatabaseName("mydb.sql");
    db.open();

    QSqlQuery query("DROP TABLE IF EXISTS foo", db);
    query.exec();

    QString sql = "CREATE TABLE foo \
        (name TEXT NOT NULL, \
         v FLOAT NOT NULL, \
         PRIMARY KEY (name));";

    query.prepare(sql);
    query.exec();

    _model = new QSqlRelationalTableModel(this, db);
    _model->setTable("foo");
    _model->setEditStrategy(QSqlRelationalTableModel::OnRowChange);

    ui->table->setSortingEnabled(false);
    ui->table->setModel(_model);
    ui->table->setItemDelegate(new QSqlRelationalDelegate(this));
    ui->table->setItemDelegateForColumn(1, new DelegateReal(5));
    ui->table->setFocusPolicy(Qt::TabFocus);

    query.exec("INSERT INTO foo (name, v) VALUES ('aaa', 1.234);");
    query.exec("INSERT INTO foo (name, v) VALUES ('bbb', 2.345);");
    query.exec("INSERT INTO foo (name, v) VALUES ('ccc', 3.465);");
    _model->select();

    ui->table->resizeColumnsToContents();
}

MainWindow::~MainWindow()
{
    delete ui;
}

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0" rowspan="2">
     <widget class="QTableView" name="table"/>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

delegatereal.h

#ifndef DELEGATEREAL_H
#define DELEGATEREAL_H

#include <QStyledItemDelegate>
#include <QDoubleSpinBox>
#include <QApplication>
#include <QDebug>

class DelegateReal : public QStyledItemDelegate
{
    Q_OBJECT

public:
    DelegateReal(int decimals, QObject *parent = nullptr) : QStyledItemDelegate(parent), _decimals(decimals)
    {

    }

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
        editor->setFrame(false);
        editor->setDecimals(_decimals);

        return editor;
    }

    void setEditorData(QWidget *editor, const QModelIndex &index) const override
    {
        int value = index.model()->data(index, Qt::EditRole).toInt();

        QDoubleSpinBox *w = static_cast<QDoubleSpinBox*>(editor);
        w->setValue(value);
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
    {
        QDoubleSpinBox *w = static_cast<QDoubleSpinBox*>(editor);
        w->interpretText();
        double value = w->value();

        model->setData(index, value, Qt::EditRole);
    }

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        editor->setGeometry(option.rect);
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);
        QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
        opt.text = opt.locale.toString(opt.locale.toFloat(opt.text), 'f', _decimals);
        style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
    }

private:
    int _decimals;
};

#endif // DELEGATEREAL_H

Results

results

Expected behavior

expected

Trials

I played with the sizes of the QRect in the updateEditorGeometry(), but this affects the editor only, of course (that is when I edit the cell):

void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
    QRect rect(option.rect);
    rect.setWidth(100);
    editor->setGeometry(rect);
}

I also tried to set the width inside the paint() function:

void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);
    QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
    opt.text = opt.locale.toString(opt.locale.toFloat(opt.text), 'f', _decimals);
    opt.rect.setWidth(100);
    style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
}

It actually enlarges the painted area, but still the columns are resized wrongly:

paint

Question

What I need to change in order to update the geometry of the content?


Solution

  • Assuming the point of the delegate is to be able to set the number of decimals (or otherwise alter the displayed text), you could reimplement QStyledItemDelegate::displayText() instead of paint(). This in turn is called from the default initStyleOption() implementation. In turn, initStyleOption() is called by both paint() and sizeHint() methods.

    If displayText() doesn't cover the needs, it may still be simpler to reimplement initStyleOption() and change the text value there instead of in paint(), for same reason that the sizeHint() would pick that up automatcially.

    https://codebrowser.dev/qt6/qtbase/src/widgets/itemviews/qstyleditemdelegate.cpp.html#_ZNK19QStyledItemDelegate11displayTextERK8QVariantRK7QLocale

    For further reference, here's how QTableView calculates the size hint for a column, depending on if editor is open or not.

    https://codebrowser.dev/qt6/qtbase/src/widgets/itemviews/qtableview.cpp.html#_ZNK17QTableViewPrivate17widthHintForIndexERK11QModelIndexiRK20QStyleOptionViewItem

    PS. If changing style options still isn't enough and paint() does need to be reimplemented then the delegate will also need to return an appropriate custom sizeHint(). Example of this can be seen in Qt's "Star Delegate Example".