Joseph Quinsey
Joseph Quinsey

Reputation: 9962

How to determine the correct size of a QTableWidget?

Is there any way to set the "correct" size of a QTableWidget? (I'm a newbie) This test code is only 25 lines long, in two files, with the file Test.h:

#include <QtGui>
class Test : public QMainWindow {
   Q_OBJECT
public:
   Test();
};

and the file Test.cpp:

#include "Test.h"
Test::Test() : QMainWindow() {
   QVBoxLayout *vbox = new QVBoxLayout;
   QPushButton  *btn = new QPushButton("Hello World etc etc etc etc etc");
   QTableWidget *tbl = new QTableWidget(2, 2);
   vbox->addWidget(btn);
   vbox->addWidget(tbl);
   QWidget *w = new QWidget;
   setCentralWidget(w);
   w->setLayout(vbox);
   resize(1, 1);
}

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);
   Test test;
   test.show();
   app.exec();
}

Then the command:

   qmake -project && qmake && make && ./Test

gives the window:

Unwanted window

But what we want of course is something more like:

Wanted window

Using tbl->width() seems to be useless, as it gives a default of 640 before test.show(), and the unwanted value of 195 after. I've looked at the Qt Size Hints and Policies until my head spun, and I've tried setResizeMode(QHeaderView::Fixed) and setStretchLastSection(false). Maybe I'm missing something obvious? This is with Qt 4.7.4 on CentOS 5, if this matters. Thank you for any help.

Edit: In response to DK, if the line resize(1, 1); is not present, there is the equal and opposite problem: the window is too large.

And in response to Donotalo, adding:

tbl->setMaximumWidth(222);
tbl->setMinimumWidth(222);
tbl->setMaximumHeight(88);
tbl->setMinimumHeight(88); 

will give the desired window size (at least on my machine), but not in the desired way. How should we calculate the 'constants' 222 and 88?

And Ton's answer to Qt: How to force a hidden widget to calculate its layout? doesn't seem to work here: the addition of tbl->setAttribute(Qt::WA_DontShowOnScreen); tbl->show(); left the value of tbl->width() unchanged at 640.

Upvotes: 40

Views: 50529

Answers (10)

Shawn Chang
Shawn Chang

Reputation: 51

Here's a simpler solution I happened to make it work. It doesn't need complex size calculation, you just need to:

  1. Set QTableWidget::sizePolicy to Minimum on both directions
  2. Set QTableWidget::sizeAdjustPolicy to AdjustToContents
  3. Call this->adjustSize() to shrink the main window to contents

The table will always show the full content, without any scrollbar, even after some rows are added later. To get the table size, you can use tbl->sizeHint().

Since it always grows height when rows are added, you should watch out when the height may exceed screen height. The simple way to avoid it is to wrap the table with another scroll area and set the height of the scroll area, show the scrollbars when needed.

Full C++ code that can be reproduced with Qt Creator C++ project:

#include "mainwindow.h"

#include <QAbstractScrollArea>
#include <QDebug>
#include <QPushButton>
#include <QSizePolicy>
#include <QTableWidget>
#include <QVBoxLayout>

#include "ui_mainwindow.h"

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

  QVBoxLayout *vbox = new QVBoxLayout(ui->centralwidget);
  QPushButton *btn = new QPushButton("Add row", ui->centralwidget);
  QTableWidget *tbl = new QTableWidget(2, 2, ui->centralwidget);
  vbox->addWidget(btn);
  vbox->addWidget(tbl);
  connect(btn, &QPushButton::clicked, this, [=]() {
    tbl->insertRow(tbl->rowCount());
    qDebug().nospace() << "tbl size: " << tbl->sizeHint();
  });

  // Always show the full content
  QSizePolicy policy(QSizePolicy::Minimum, QSizePolicy::Minimum);
  tbl->setSizePolicy(policy);
  tbl->setSizeAdjustPolicy(
      QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents);

  qDebug().nospace() << "tbl size: " << tbl->sizeHint();

  // Shrink the window to fit
  this->adjustSize();

  // Disable resizing
  this->setFixedSize(this->sizeHint());
  this->setWindowFlag(Qt::WindowType::MSWindowsFixedSizeDialogHint, true);
}

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

For Python (standalone code):

import sys

# from PyQt5 import QtCore, QtWidgets
from PySide2 import QtCore, QtWidgets


class TestWidget(QtWidgets.QMainWindow):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        vbox = QtWidgets.QVBoxLayout(central_widget)
        btn = QtWidgets.QPushButton("Add row", central_widget)
        tbl = QtWidgets.QTableWidget(2, 2, central_widget)
        vbox.addWidget(btn)
        vbox.addWidget(tbl)
        btn.clicked.connect(self.add_row)

        # Always show the full content
        policy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Policy.Minimum,
            QtWidgets.QSizePolicy.Policy.Minimum,
        )
        tbl.setSizePolicy(policy)
        tbl.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents
        )

        print(f"tbl size: {tbl.sizeHint()}")

        # Shrink the window to fit
        self.adjustSize()

        # Disable resizing
        self.setFixedSize(self.sizeHint())
        self.setWindowFlag(QtCore.Qt.MSWindowsFixedSizeDialogHint, True)

        self.tbl = tbl

    def add_row(self) -> None:
        self.tbl.insertRow(self.tbl.rowCount())

        print(f"tbl size: {self.tbl.sizeHint()}")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    test_widget = TestWidget()
    test_widget.show()
    sys.exit(app.exec_())

If you run the above code (either C++ or Python), you should see the following messages in console indicating the size of the table widget from the beginning to the moment I added another 4 rows:

tbl size: QSize(292, 127)
tbl size: QSize(292, 164)
tbl size: QSize(292, 201)
tbl size: QSize(292, 238)
tbl size: QSize(292, 275)
  • Qt version for C++: 5.15.2 (MSVC 2019 64-bit)
  • Qt version for Python: PySide2==5.15.2, PyQt==5.15.6

Upvotes: 1

Raven
Raven

Reputation: 3516

This is not an answer to the question as stated in the title but I think a neat answer to the underlying problem.

In order to display a QTableWidget at its full size, without scrollbars, you have to set its sizeAdjustPolicy to QAbstractScrollArea::AdjustToContents (I assume QAbstractScrollArea::AdjustToContentsOnFirstShow would work as well).

Additionally you might want to disable the scrollbars for that widget (setting its scroll bar policies for vertical and horizontal scroll bars) as otherwise there will be extra space around the widget allocated for when scrollbars would be shown.

Refs:

Upvotes: 1

user6419667
user6419667

Reputation: 229

Here is my (pythonic) solution to this problem:

table.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)

table.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff)
table.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff)

table.resizeColumnsToContents()
table.setFixedSize(table.horizontalHeader()->length() + 
                   table.verticalHeader()->width(),
                   table.verticalHeader()->length() + 
                   table.horizontalHeader()->height())

This gives me a table with a size perfectly wrapping all rows and columns.

Upvotes: 22

Agustin Durand
Agustin Durand

Reputation: 11

Here's my solution (I'm hiding the vertical and horizontal header):

m_table->resizeColumnsToContents();
m_table->resizeRowsToContents();

int w = 0; // verticalHeader()->width()
int h = 0; // horizontalHeader()->height()
for ( int i = 0; i < m_table->rowCount(); i++ )
{
    h += m_table->rowHeight( i );
}
for ( int j = 0; j < m_table->columnCount(); j++ )
{
    w += m_table->columnWidth( j );
}
m_table->setFixedSize( w, h );

Upvotes: 1

Zbyněk Winkler
Zbyněk Winkler

Reputation: 1465

To get the width of the table sum the width of the vertical and horizontal headers and twice the frame width. The same goes for the height:

w = verticalHeader()->width() + horizontalHeader()->length() + frameWidth()*2
h = horizontalHeader()->height() + verticalHeader()->length() + frameWidth()*2

There are multiple ways how to set them depending on your circumstances. The preferred probably would be to override sizeHint() to return your values. This way the table stays resizable but absent other requirements will be your size.

The key to get this working is using the length() method at the right places instead of just width() or height(). I am not exactly sure why but here it does the right thing ;-)

Upvotes: 5

fabio
fabio

Reputation: 11

apparently there is --some bug maybe?-- in the horizontalHeader()->width() function; this solution worked for me:

    //given a QtableWidget* called x
    int previous_width= x->horizontalHeader()->width();//get the width
    //the width is relating to all the columns including the vertical headers 
    int previous_length=x->horizontalHeader()->length();//and the length
    // the length is relating to all the columns excluding the size of the vertical headers
    int vertical_headers_width=(previous_width-previous_length);
    x->resizeColumnsToContents();//now we resize
    x->resizeRowsToContents();
    //if we call again x->horizontalHeader()->width();
    // we get a number that looks to me not correct (maybe is my ignorance here)
    x->setFixedWidth(x->horizontalHeader()->length()+vertical_headers_width);

   //by adding to the length of the data columns the old width_of_verticla_headers_column we get the correct size;

Upvotes: 1

TimeS
TimeS

Reputation: 111

I cannot comment to Joseph Qunesey's / Celdor's post, so I write it in here: To retrieve true value of QStyle::PM_TitleBarHeight, you should do it as follows:

QStyle *style;
if(tv->style())
{
    style = tv->style();
}
else
{
    style = QApplication::style();
}
int titleBarHeight = style->pixelMetric(QStyle::PM_TitleBarHeight));

That is because the enum does not have assigned exact values. I think, your misunderstanding of how it works was due to misleading Qt documentation where it looks like the enum directly represents the values, while it of course does not (as it is style dependent).

Upvotes: 2

Joseph Quinsey
Joseph Quinsey

Reputation: 9962

The thread How to set a precise size of QTableWidget to prevent from having scroll bars? (Qt-interest Archive, June 2007) between Lingfa Yang and Susan Macchia seems to resolve my question. I will post more details shortly, if my testing works.

Update #1: My test now generates the nice-looking window:

successful test window

The complete test code for this, with Test.h unchanged, is:

#include "Test.h"

static QSize myGetQTableWidgetSize(QTableWidget *t) {
   int w = t->verticalHeader()->width() + 4; // +4 seems to be needed
   for (int i = 0; i < t->columnCount(); i++)
      w += t->columnWidth(i); // seems to include gridline (on my machine)
   int h = t->horizontalHeader()->height() + 4;
   for (int i = 0; i < t->rowCount(); i++)
      h += t->rowHeight(i);
   return QSize(w, h);
}

static void myRedoGeometry(QWidget *w) {
   const bool vis = w->isVisible();
   const QPoint pos = w->pos();
   w->hide();
   w->show();
   w->setVisible(vis);
   if (vis && !pos.isNull())
      w->move(pos);
}

Test::Test() : QMainWindow() {
   QVBoxLayout *vbox = new QVBoxLayout;
   QPushButton *btn  = new QPushButton("Hello World etc etc etc etc etc");
   QTableWidget *tbl = new QTableWidget(2, 2);
   vbox->addWidget(btn);
   vbox->addWidget(tbl);
   setCentralWidget(new QWidget);
   centralWidget()->setLayout(vbox);
   layout()->setSizeConstraint(QLayout::SetMinimumSize); // or SetFixedSize

   tbl->setVerticalHeaderItem(1, new QTableWidgetItem("two")); // change size
   myRedoGeometry(this);
   tbl->setMaximumSize(myGetQTableWidgetSize(tbl));
   tbl->setMinimumSize(tbl->maximumSize()); // optional
}

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);
   Test test;
   test.show();
   app.exec();
}

Some notes:

  • The above thread's inclusion of verticalScrollBar()->width() seems to be wrong. And, in my testing, this was always either a default value of 100, or the value 15, if the scrollbar had been displayed.

  • Applying the show(); hide(); sequence just to the QTableWidget was not sufficient to force Qt to recalculate the geometry, in this test. I needed to apply it to the whole window.

  • Any suggestions for improvements would be welome. (I'll wait a bit before accepting my own answer, in case there are better solutions.)

Update #2:

  • The Qt-interest thread may be wrong (or, at least, it disagrees with my version of Qt running my machine) regarding details on how to calculate the size: the +1 for each gridline is unnecessary, but an overall +4 is needed.

  • I'm still working through layout()->invalidate() vs. e.g. QT: How to preview sizes of widgets in layout BEFORE a show().

Update #3:

  • This is my final version--but I'm not very happy with it. Any improvements would be very welcome.

  • Things like adjustSize() and layout()->invalidate() and Qt::WA_DontShowOnScreen don't seem to help.

  • The blog Shrinking Qt widgets to minimum needed size is interesting, but using setSizeConstraint() is just as good.

  • The setSizeConstraint() and move() methods are needed for subsequent changes to the table, not shown here.

  • There was an odd thing in my testing, done on CentOS 5 with Qt 4.6.3 and 4.7.4. For the hide(); show(); sequence, the window position is saved/restored. But about 25% of the time (the patterning was irregular) the restored position would be 24 pixels higher on the screen, presumably the height of the window title. And about 10% of the time the value returned by pos() would be null. The Qt site says for X11 it needs nifty heuristics and clever code for this, but something seems to be broken somewhere.

Upvotes: 27

Ali
Ali

Reputation: 451

I was dealing with the same issue, and I tweaked previous answers to get a correct working code.

First, we get contents margins from QTableWidget. If you just want to adjust its width, you only need to disable horizontal scroll bar.:

int w = 0, h = 0;
ui->tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

w += ui->tableWidget->contentsMargins().left()
        + ui->tableWidget->contentsMargins().right();
h += ui->tableWidget->contentsMargins().top()
        + ui->tableWidget->contentsMargins().bottom();

Then, we append width of vertical header to w. The vertical header is a special column that contains rows indexes, and is enabled by default. we also append height of horizontal header to h. The horizontal header is a special row that contains column titles.

w += ui->tableWidget->verticalHeader()->width();
h += ui->tableWidget->horizontalHeader()->height();

Then, we append width of each column to w, and height of each row to h.

for (int i=0; i<ui->tableWidget->columnCount(); ++i)
    w += ui->tableWidget->columnWidth(i);
for (int i=0; i<ui->tableWidget->rowCount(); ++i)
    h += ui->tableWidget->rowHeight(i);

Now, if w or h are too large to fit on window, we reduce them to fit in ui->centralWidget->contentsRect().

if (w > ui->centralWidget->contentsRect().width())
    w = ui->centralWidget->contentsRect().width();
if (h > ui->centralWidget->contentsRect().height())
    h = ui->centralWidget->contentsRect().height();

Unfortunately, I don't know a better way of applying w and h to set width and height of QTableWidget. If I use:

ui->tableWidget->setMinimumWidth(w);
ui->tableWidget->setMaximumWidth(w);
ui->tableWidget->setMinimumHeight(h);
ui->tableWidget->setMaximumHeight(h);

Then the user will not be able to resize the widget at all. I think we need to use some sort of manual layouting.

It might seem a little off topic, but if you want your table to have more appealing view, use this command to resize columns to fit their data:

ui->tableWidget->resizeColumnsToContents();

Upvotes: 6

Celdor
Celdor

Reputation: 2607

Recently, I found the same problem. I tested tens of functions found by Google in Qt Center, Qt Project but none of them gave me proper size. I made that function which I believe calculates the closest table size. The function gives a proper vertical but a bit greater horizontal size--it looks like it still cover for a vertical Scroll Bar. Also, I checked whether the table dimensions exceed a desktop size:

const QRect MyApp::adjustTableSize(
    QTableView* tv,
    const QVBoxLayout* lay,
    const int& maxRows,
    const int& maxCols) {
// get Desktop size
using std::size_t;
QDesktopWidget desktop;
size_t desW = desktop.screen()->width();
size_t desH = desktop.screen()->height();

int leftM,rightM,topM,bottomM;
lay->getContentsMargins(&leftM,&topM,&rightM,&bottomM);

size_t extraTopHeight = topM + tv->frameWidth();
size_t extraBottomHeight = bottomM + tv->frameWidth();
size_t extraLeftWidth = leftM + tv->frameWidth();
size_t extraRightWidth = rightM + tv->frameWidth();
size_t w = tv->verticalHeader()->width() + extraLeftWidth + extraRightWidth;
size_t h = tv->horizontalHeader()->height() + extraTopHeight + extraBottomHeight;
for(size_t col = 0; col < maxCols; ++col) {
    w += tv->columnWidth(col);
}
for(size_t row = 0; row < maxRows; ++row ) {
    h += tv->rowHeight(row);
}

std::size_t x,y;
if((w - extraLeftWidth - extraRightWidth) > desW) {
    x = 0;
    w = desW - extraLeftWidth - extraRightWidth;
} else
    x = (desW - w)/2;
if(h - extraTopHeight - extraBottomHeight - QStyle::PM_TitleBarHeight > desH) {
    y = extraTopHeight + QStyle::PM_TitleBarHeight;
    h = desH - (extraTopHeight + QStyle::PM_TitleBarHeight + extraBottomHeight);
} else
    y = (desH - h)/2;
return QRect(x,y,w,h);
}

A few side notes:

QTableView.
I used QTableView but I think it can also work with QTableWidget.

QVBoxLayout.
I am pretty positive that any of the layouts available in Qt can be used in this function instead.

Hope, this function can help anybody.

Upvotes: 6

Related Questions