2

I know this question has already been asked:

How to set row height of QTableView?

How to set row height of QTableView?

but the answer:

QHeaderView *verticalHeader = myTableView->verticalHeader();
verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
verticalHeader->setDefaultSectionSize(24);

still produces rows that are too high. If I set any number smaller than 24, like verticalHeader->setDefaultSectionSize(10) i get the same result, the rows are still too high.

Is there any way to have smaller row heights in QTableView like that's the case in QListView or QTreeView by default?

I don't understand why are rows in QTableView by default and by design so much (almost double) higher than rows in QListView and QTreeView (even with verticalHeader->setDefaultSectionSize(24)). It's so ugly.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
nenad
  • 164
  • 6
  • 17
  • You could try setting the maximum section size of the vertical header via `verticalHeader.setMaximumSectionSize()`. – Heike Aug 15 '19 at 12:12
  • Using setDefaultSectionSize is usually enough. Are you sure you're not using `QTableView.resizeRowsToContents()` somewhere after you set the default section size? – musicamante Aug 15 '19 at 14:43
  • @musicamante Well, the problem is exactly that `setDefaultSectionSize` won't allow resizing to less than the contents. So I suppose the real question is how the contents-size is determined. Probably it will partly depend on the style, because some styles draw a selection-rectangle when a cell is edited (amongst other things). – ekhumoro Aug 15 '19 at 21:25
  • @ekhumoro I think you're right, and I actually did a couple of small tests using my default style (oxygen) and on Linux only, but from my experience I tend to agree that in other OS's and styles this behavior can change, possibly according to OS specific metrics. But I still think that `resizeRow[s]ToContents` might be in play in here. If the OP won't answer about that, I'll try to do some tests in the next days, at least just for the sake of the argument - and to share some common knowledge :-) – musicamante Aug 16 '19 at 05:13
  • with or without ```QTableView.resizeRowsToContents()``` it's all the same, rows are too heigh – nenad Aug 16 '19 at 21:10
  • if ```verticalHeader->setDefaultSectionSize(24)``` is the smallest we could get, QTableView is almost useless.. is there any solution or alternative? – nenad Aug 16 '19 at 21:18
  • @nenad as said, setting the defaultSectionSize is usually enough, unless you're resizing contents and/or setSectionResizeMode; if that's not the case, please provide us a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), plus your OS and specific Python/PyQt versions you're using, as it *might* be a bug. – musicamante Aug 16 '19 at 23:19

2 Answers2

6

Call verticalHeader->setMinimumSectionSize() to set a minimum row height for the section. Call verticalHeader->resizeSection(row, height) to set the height of an individual row.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
rob2n
  • 78
  • 1
  • 5
  • 1
    Thanks, your solution completely solves my problem. I have `QTableViews` with `QSqlQueryModel` with tens of thousands of records and I thought that solutions where I will have to resize every row in a loop will be unacceptably slow, especially the ones in master-detail where I use `self.masterView.selectionModel().currentRowChanged.connect(self.refreshChildView)` in master and every time I change the row in master I have to refresh the child view with new records and also every time call your `verticalHeader.resizeSection()` in loop for all those newly appeared records... – nenad Oct 30 '19 at 22:56
5

In case anyone is interested, I created a small self-sustained demo program that uses the answer from @rob2n:

import sys

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtSql import *

class RowHeightSlider(QSlider):
    def __init__(self, parent=None):
        #QSlider.__init__(self, parent)
        super(RowHeightSlider, self).__init__(parent)
        self.setOrientation(Qt.Horizontal)
        self.setMinimum(4)
        self.setMaximum(72)
        self.setSingleStep(1)
        self.setPageStep(2)
        self.setTickPosition(QSlider.TicksAbove)
        self.setTickInterval(1)

class Window(QWidget):
    def __init__(self, parent=None):
        #QWidget.__init__(self, parent)
        super(Window, self).__init__(parent)
        self.parentModel = QSqlQueryModel(self)
        self.refreshParent()
        self.parentProxyModel = QSortFilterProxyModel()
        self.parentProxyModel.setSourceModel(self.parentModel)
        self.parentView = QTableView()
        self.parentView.setModel(self.parentProxyModel)
        self.parentView.setSelectionMode(QTableView.SingleSelection)
        self.parentView.setSelectionBehavior(QTableView.SelectRows)
        self.parentView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.parentView.horizontalHeader().setStretchLastSection(True)
        self.parentView.verticalHeader().setVisible(False)
        self.parentView.setSortingEnabled(True)
        self.parentView.horizontalHeader().setSortIndicator(0, Qt.AscendingOrder)
        self.parentView.setAlternatingRowColors(True)
        self.parentView.setShowGrid(False)
        #self.parentView.verticalHeader().setDefaultSectionSize(24)
        self.parentView.setStyleSheet("QTableView::item:selected:!active { selection-background-color:#BABABA; }")
        for i, header in enumerate(self.parentHeaders):
            self.parentModel.setHeaderData(i, Qt.Horizontal, self.parentHeaders[self.parentView.horizontalHeader().visualIndex(i)])
        self.parentView.resizeColumnsToContents()

        self.childModel = QSqlQueryModel(self)
        self.refreshChild()
        self.childProxyModel = QSortFilterProxyModel()
        self.childProxyModel.setSourceModel(self.childModel)
        self.childView = QTableView()
        self.childView.setModel(self.childProxyModel)
        self.childView.setSelectionMode(QTableView.SingleSelection)
        self.childView.setSelectionBehavior(QTableView.SelectRows)
        self.childView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.childView.horizontalHeader().setStretchLastSection(True)
        self.childView.verticalHeader().setVisible(False)
        self.childView.setSortingEnabled(True)
        self.childView.horizontalHeader().setSortIndicator(0, Qt.AscendingOrder)
        self.childView.setAlternatingRowColors(True)
        self.childView.setShowGrid(False)
        #self.childView.verticalHeader().setDefaultSectionSize(24)
        self.childView.setStyleSheet("QTableView::item:selected:!active { selection-background-color:#BABABA; }")
        for i, header in enumerate(self.childHeaders):
            self.childModel.setHeaderData(i, Qt.Horizontal, self.childHeaders[self.childView.horizontalHeader().visualIndex(i)])
        self.childView.resizeColumnsToContents()

        self.parentSlider = RowHeightSlider()
        self.childSlider = RowHeightSlider()

        self.parentRowHeightLabel = QLabel('Row height: 32')
        self.childRowHeightLabel = QLabel('Row height: 32')

        parentLayout = QVBoxLayout()
        parentLayout.addWidget(self.parentSlider)
        parentLayout.addWidget(self.parentRowHeightLabel)
        parentLayout.addWidget(self.parentView)

        childLayout = QVBoxLayout()
        childLayout.addWidget(self.childSlider)
        childLayout.addWidget(self.childRowHeightLabel)
        childLayout.addWidget(self.childView)

        layout = QHBoxLayout()
        layout.addLayout(parentLayout)
        layout.addLayout(childLayout)
        self.setLayout(layout)

        self.parentView.selectionModel().currentRowChanged.connect(self.parentChanged)
        self.parentSlider.valueChanged.connect(self.changeParentRowHeight)
        self.childSlider.valueChanged.connect(self.changeChildRowHeight)

        self.parentView.setCurrentIndex(self.parentView.model().index(0, 0))
        self.parentView.setFocus()

        self.parentSlider.setValue(36)
        self.childSlider.setValue(36)

    def refreshParent(self):
        self.parentHeaders = ['Parent']
        queryString = "SELECT parent.parent_name FROM parent"
        query = QSqlQuery()
        query.exec(queryString)
        self.parentModel.setQuery(query)
        while self.parentModel.canFetchMore():
            self.parentModel.fetchMore()

    def refreshChild(self, parent_name=''):
        self.childHeaders = ['Child']
        queryString = ("SELECT child.child_name FROM child "
            "WHERE child.parent_name = '{parent_name}'").format(parent_name = parent_name)
        query = QSqlQuery()
        query.exec(queryString)
        self.childModel.setQuery(query)
        while self.childModel.canFetchMore():
            self.childModel.fetchMore()

    def parentChanged(self, index):
        if index.isValid():
            index = self.parentProxyModel.mapToSource(index)
            record = self.parentModel.record(index.row())
            self.refreshChild(record.value("parent.parent_name"))
            #self.childView.scrollToBottom() # if needed

    def changeParentRowHeight(self, rowHeight):
        parentVerticalHeader = self.parentView.verticalHeader()

        # (any)one of these two rows (or both) has to be uncommented
        parentVerticalHeader.setMinimumSectionSize(rowHeight)
        #parentVerticalHeader.setMaximumSectionSize(rowHeight)

        for section in range(parentVerticalHeader.count()):
            parentVerticalHeader.resizeSection(section, rowHeight)
        self.displayParentRowHeightLabel(rowHeight)

    def changeChildRowHeight(self, rowHeight):
        childVerticalHeader = self.childView.verticalHeader()

        # (any)one of these two rows (or both) has to be uncommented
        childVerticalHeader.setMinimumSectionSize(rowHeight)
        #childVerticalHeader.setMaximumSectionSize(rowHeight)

        for section in range(childVerticalHeader.count()):
            childVerticalHeader.resizeSection(section, rowHeight)
        self.displayChildRowHeightLabel(rowHeight)

    def displayParentRowHeightLabel(self, rowHeight):
        visibleRows = self.parentView.rowAt(self.parentView.height()) - self.parentView.rowAt(0)
        if self.parentView.rowAt(self.parentView.height()) == -1:
            visibleRowsString = str(self.parentView.model().rowCount()) + '+'
        else:
            visibleRowsString = str(visibleRows)
        self.parentRowHeightLabel.setText('Row height: ' + str(rowHeight) + ', Visible rows: ' + visibleRowsString)

    def displayChildRowHeightLabel(self, rowHeight):
        visibleRows = self.childView.rowAt(self.childView.height()) - self.childView.rowAt(0)
        if self.childView.rowAt(self.childView.height()) == -1:
            visibleRowsString = str(self.childView.model().rowCount()) + '+'
        else:
            visibleRowsString = str(visibleRows)
        self.childRowHeightLabel.setText('Row height: ' + str(rowHeight) + ', Visible rows: ' + visibleRowsString)

    def resizeEvent(self, event):
        # make it resize-friendly
        self.displayParentRowHeightLabel(self.parentSlider.value())
        self.displayChildRowHeightLabel(self.childSlider.value())

def createFakeData():
    parent_names = []
    #import random
    query = QSqlQuery()
    query.exec("CREATE TABLE parent(parent_name TEXT)")
    for i in range(1, 101):
        parent_num = str(i).zfill(3)
        parent_name = 'parent_name_' + parent_num
        parent_names.append((parent_name, parent_num))
        query.prepare("INSERT INTO parent (parent_name) VALUES(:parent_name)")
        query.bindValue(":parent_name", parent_name)
        query.exec()
    query.exec("CREATE TABLE child(parent_name TEXT, child_name TEXT)")
    counter = 1
    for parent_name, parent_num in parent_names:
        for i in range(1, 101):
            child_name = 'child_name_' + parent_num + '_' + str(counter).zfill(5)
            counter += 1
            query.prepare("INSERT INTO child (parent_name, child_name) VALUES(:parent_name, :child_name)")
            query.bindValue(":parent_name", parent_name)
            query.bindValue(":child_name", child_name)
            query.exec()

def createConnection():
    db = QSqlDatabase.addDatabase("QSQLITE")
    #db.setDatabaseName("test04.db")
    db.setDatabaseName(":memory:")
    db.open()
    createFakeData()

app = QApplication(sys.argv)
createConnection()
window = Window()
window.resize(800, 600)
#window.show()
window.showMaximized()
app.exec()

The sliders can be used to change the row height of QTableViews in "live" mode. I tested it with PostgreSQL via OpenVpn with 10,000 rows in one QTableView and also with 100 rows in parent view and on average 1,000 rows in child view for every parent row and it works like a charm.

enter image description here

William Miller
  • 9,839
  • 3
  • 25
  • 46
nenad
  • 164
  • 6
  • 17