如何确定QTableWidget的正确尺寸?

39

有没有任何一种方法可以设置QTableWidget的“正确”大小?(我是新手)这个测试代码只有25行,分为两个文件,其中一个是Test.h:

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

以及文件 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();
}

然后输入命令:
   qmake -project && qmake && make && ./Test

给出窗口:

Unwanted window

当然,我们想要的是更接近于以下内容:

Wanted window

使用tbl->width()似乎是无用的,因为在test.show()之前它会给出默认值640,在此之后则是不需要的195。我看了Qt大小提示和策略,头都晕了,我尝试过setResizeMode(QHeaderView::Fixed)setStretchLastSection(false)。也许我错过了一些明显的东西?这是在CentOS 5上使用Qt 4.7.4,如果有关系的话。谢谢任何帮助。

编辑:回应DK的问题,如果没有resize(1,1);这行代码,就会出现相反的问题:窗口太大了。

回应Donotalo,添加:

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

这将会给出所需的窗口大小(至少在我的机器上),但是不是以期望的方式。我们应该如何计算“常量”22288

Ton对Qt:如何强制隐藏的小部件计算其布局?的回答似乎在这里不起作用:添加了tbl->setAttribute(Qt :: WA_DontShowOnScreen); tbl->show();后,tbl->width()的值仍然保持在640。


我有一种感觉,那只是 resize(1, 1); 的问题。 - D K
您可以尝试使用 QWidget::setMinimumSize() 来设置您所需的大小。 - Donotalo
10个回答

26

这个帖子如何设置精确的QTableWidget大小以避免出现滚动条?(Qt-interest Archive, June 2007)里的对话解决了我的问题。如果我的测试成功,我会很快发布更多细节。

更新 #1: 我的测试现在生成了一个漂亮的窗口:

successful test window

完整的测试代码如下,Test.h文件不变:

#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();
}

一些注释:

  • 上述线程中包含的verticalScrollBar()->width()似乎是错误的。在我的测试中,这总是一个默认值100,或者如果滚动条已经显示,则为值15。

  • 仅将show(); hide();序列应用于QTableWidget不足以强制Qt重新计算几何形状,在这个测试中,我需要将其应用于整个窗口。

  • 欢迎提出改进建议。(如果有更好的解决方案,我会等一段时间再接受自己的答案。)

更新#2:

  • Qt-interest线程可能是错误的(或者至少与我的机器上运行的Qt版本不一致),关于如何计算大小的细节:每个网格线的+1是不必要的,但整体需要+4。

  • 我仍在研究layout()->invalidate()QT:如何在show()之前预览布局中小部件的大小等的区别。

更新#3

  • 这是我的最终版本,但我并不满意。如果有任何改进,都非常欢迎。

  • adjustSize()layout()->invalidate()Qt::WA_DontShowOnScreen这样的东西似乎没有帮助。

  • 博客Shrinking Qt widgets to minimum needed size很有趣,但使用setSizeConstraint()也同样好。

  • setSizeConstraint()move()方法需要用于表格的后续更改,此处未显示。

  • 在我的测试中(在CentOS 5上进行,使用Qt 4.6.3和4.7.4),有一件奇怪的事情。对于hide(); show();序列,窗口位置被保存/恢复。但大约25%的时间(模式不规则),恢复的位置会在屏幕上高出24个像素,可能是窗口标题的高度。而且大约10%的时间,pos()返回的值将为nullQt site表示对于X11,它需要nifty heuristics and clever code来解决这个问题,但某些地方似乎出了问题。


1
你引用的线程在相同的URL上已不再可用。:-( 嗯...也许这就是它:http://www.archivum.info/qt-interest@trolltech.com/2007-06/00512/How-to-set-a-precise-size-of-QTableWidget-to-prevent-from-having-scroll-bars.html - Jason S
@JasonS:谢谢。那个链接看起来正确。我很快会更新我的帖子。 - Joseph Quinsey
3
我认为你应该使用 t->frameWidth()*2 而不是 4。 - Simon
1
这段代码对我来说部分有效。在我的情况下,我的垂直标题比“1”或“two”更长。我的垂直标题是15或20个字符的字符串。为了避免滚动条,我必须使用标题的sizeHint来获取表格小部件的正确大小: int w = t->verticalHeader()->sizeHint().width() + 2; 和: int h = t->horizontalHeader()->sizeHint().height() + 2;将宽度和高度加4太多了:我只需要加2。也许标题的sizeHint不包括网格线。添加1会出现滚动条。 - pamplemousse_mk2
1
@JosephQuinsey:我从遥远的未来回到了2012年,为了寻找有关QTableWidget大小调整的答案,非常感谢你!(另外:在Qt中使用布局有时让我想念WPF。) - Rethunk

22

这是我(Pythonic)解决该问题的方案:

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())

这会给我一个大小完美地包裹所有行和列的表格。


你难道不应该考虑frameWidth或者margins吗? - Alexis
使用Ruby的qtbindings在Qt 4.8.6上完美运行,除了我将垂直固定大小增加了+2,因为如果不这样做,它会想要滚动一点。 - Jason

6

最近,我发现了同样的问题。在Qt Center、Qt Project中通过谷歌找到了数十个函数进行测试,但没有一个给出了合适的大小。我编写了一个我认为可以计算最接近表格大小的函数。该函数可以给出正确的垂直大小,但水平大小略大--看起来仍然覆盖了垂直滚动条。此外,我还检查了表格尺寸是否超过了桌面尺寸:

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);
}

一些辅助说明:

QTableView
我使用了QTableView,但是我认为它也可以与QTableWidget一起使用。

QVBoxLayout
我非常确定Qt中可用的任何布局都可以在此函数中使用。

希望这个函数能帮助任何人。


6

我遇到了同样的问题,我修改了之前的答案以得到正确的工作代码。

首先,我们从 QTableWidget 获取内容边距。如果你只想调整它的宽度,你只需要禁用水平滚动条:

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();

然后,我们将垂直表头的宽度添加到w中。垂直表头是一个特殊的列,包含行索引,并默认启用。我们还将水平表头的高度添加到h中。水平表头是一个特殊的行,包含列标题。

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

然后,我们将每一列的宽度添加到w中,将每一行的高度添加到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);

现在,如果宽度 w 或高度 h 太大以至于无法适应整个窗口,我们会缩小它们以适应 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();

很遗憾,我不知道更好的应用wh来设置QTableWidget宽度和高度的方法。如果我使用:

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

那么用户将无法调整小部件的大小。我认为我们需要使用某种手动布局。

这可能有点跑题,但如果您希望表格具有更吸引人的视图,请使用此命令调整列的大小以适应其数据:

ui->tableWidget->resizeColumnsToContents();

5

要获取表格的宽度,需要将竖直和水平标头的宽度加起来,再加上框架宽度的两倍。高度同理:

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

根据您的情况,有多种设置它们的方法。最好的方法可能是重写 sizeHint() 方法来返回您的值。这样,表格保持可调整大小,但在没有其他要求的情况下将是您设置的大小。

使其正常工作的关键是在正确的位置使用 length() 方法,而不仅仅是 width() 或 height()。我不确定为什么,但在此处它做了正确的事情 ;-)


1
这个解决方案对我有用,但需要进行一些调整(Qt4.8):我必须使用length而不是width/height(令人惊讶的是它们并不相同...)。所以我最终得到了w = verticalHeader()->width() + horizontalHeader()->length() + frameWidth()*2h = horizontalHeader()->height() + verticalHeader()->length() + frameWidth()*2 - Alexis
1
@Alexis 你说得对。我已经仔细检查了我的当前代码,发现我正在使用相同的代码。我会相应地更新我的答案。谢谢! - Zbyněk Winkler

2

我无法在Joseph Qunesey / Celdor的帖子中发表评论,因此我在这里写下来:

要检索QStyle :: PM_TitleBarHeight的真实值,您应该按照以下步骤执行:

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

那是因为枚举没有指定确切的值。我认为,你对它的工作方式的误解是由于具有误导性的Qt文档,其中看起来枚举直接表示值,而它当然不是(因为它是样式相关的)。

1

虽然这不是对标题中所述问题的答案,但我认为这是解决潜在问题的好方法。

为了显示一个QTableWidget的完整尺寸,而没有滚动条,您需要将其sizeAdjustPolicy设置为QAbstractScrollArea::AdjustToContents(我假设QAbstractScrollArea::AdjustToContentsOnFirstShow也可以工作)。

此外,您可能希望禁用该窗口小部件的滚动条(设置其垂直和水平滚动条的滚动条策略),否则会为显示滚动条时分配额外的空间。

参考文献:


1

显然horizontalHeader()->width()函数存在一些错误,也许是bug?这个解决方案对我起作用:

    //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;

有一个 bug,但你的解决方案对我没有用。 - panofish

1
这是我的解决方案(我隐藏了垂直和水平标题):
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 );

0

这里有一个更简单的解决方案,我刚好让它工作了。它不需要复杂的大小计算,你只需要:

  1. 在两个方向上将 QTableWidget::sizePolicy 设置为 Minimum
  2. QTableWidget::sizeAdjustPolicy 设置为 AdjustToContents
  3. 调用 this->adjustSize() 来缩小主窗口到内容大小

表格将始终显示完整的内容,没有任何滚动条,即使以后添加了一些行。要获取表格大小,可以使用 tbl->sizeHint()

由于当添加行时它总是增加高度,因此您应该注意高度可能超过屏幕高度的情况。避免这种情况的简单方法是将表格包装在另一个滚动区域中,并设置滚动区域的高度,在需要时显示滚动条。

可以使用 Qt Creator C++ 项目复制的完整 C++ 代码:

#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; }

对于 Python(独立代码):

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_())

如果您运行上述代码(无论是C++还是Python),您应该在控制台中看到以下消息,指示表格小部件的大小从开始到我添加另外4行的时刻:
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)
  • C++的Qt版本:5.15.2 (MSVC 2019 64位)
  • Python的Qt版本:PySide2==5.15.2PyQt==5.15.6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接