This follows on directly from this question. Here is an MRE:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Get a grip of table view row height MRE')
self.setGeometry(QtCore.QRect(100, 100, 1000, 800))
layout = QtWidgets.QVBoxLayout()
central_widget = QtWidgets.QWidget( self )
central_widget.setLayout(layout)
self.table_view = SegmentsTableView(self)
self.setCentralWidget(central_widget)
layout.addWidget(self.table_view)
rows = [
['one potatoe two potatoe', 'one potatoe two potatoe'],
['Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque',
'Sed ut <b>perspiciatis, unde omnis <i>iste natus</b> error sit voluptatem</i> accusantium doloremque'],
['Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui do lorem ipsum, quia dolor sit amet consectetur adipiscing velit, sed quia non numquam do eius modi tempora incididunt, ut labore et dolore magnam aliquam quaerat voluptatem.',
'Nemo enim ipsam <i>voluptatem, quia voluptas sit, <b>aspernatur aut odit aut fugit, <u>sed quia</i> consequuntur</u> magni dolores eos</b>, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui do lorem ipsum, quia dolor sit amet consectetur adipiscing velit, sed quia non numquam do eius modi tempora incididunt, ut labore et dolore magnam aliquam quaerat voluptatem.'
],
['Ut enim ad minima veniam',
'Ut enim ad minima veniam'],
['Quis autem vel eum iure reprehenderit',
'Quis autem vel eum iure reprehenderit'],
['At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga.',
'At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga.'
]]
for n_row, row in enumerate(rows):
self.table_view.model().insertRow(n_row)
self.table_view.model().setItem(n_row, 0, QtGui.QStandardItem(row[0]))
self.table_view.model().setItem(n_row, 1, QtGui.QStandardItem(row[1]))
self.table_view.setColumnWidth(0, 400)
self.table_view.setColumnWidth(1, 400)
self.qle = QtWidgets.QLineEdit()
layout.addWidget(self.qle)
self._second_timer = QtCore.QTimer(self)
self._second_timer.timeout.connect(self.show_doc_size)
# every 1s
self._second_timer.start(1000)
def show_doc_size(self, *args):
if self.table_view.itemDelegate().editor == None:
self.qle.setText('no editor yet')
else:
self.qle.setText(f'self.table_view.itemDelegate().editor.document().size() {self.table_view.itemDelegate().editor.document().size()}')
class SegmentsTableView(QtWidgets.QTableView):
def __init__(self, parent):
super().__init__(parent)
self.setItemDelegate(SegmentsTableViewDelegate(self))
self.setModel(QtGui.QStandardItemModel())
v_header = self.verticalHeader()
#
v_header.setMinimumSectionSize(5)
v_header.sectionHandleDoubleClicked.disconnect()
v_header.sectionHandleDoubleClicked.connect(self.resizeRowToContents)
self.horizontalHeader().sectionResized.connect(self.resizeRowsToContents)
def resizeRowToContents(self, row):
print(f'row {row}')
super().resizeRowToContents(row)
self.verticalHeader().resizeSection(row, self.sizeHintForRow(row))
def resizeRowsToContents(self):
header = self.verticalHeader()
for row in range(self.model().rowCount()):
hint = self.sizeHintForRow(row)
header.resizeSection(row, hint)
def sizeHintForRow(self, row):
super_result = super().sizeHintForRow(row)
print(f'row {row} super_result {super_result}')
return super_result
class SegmentsTableViewDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, *args):
super().__init__(*args)
self.editor = None
def createEditor(self, parent, option, index):
class Editor(QtWidgets.QTextEdit):
def resizeEvent(self, event):
print(f'event {event}')
super().resizeEvent(event)
self.editor = Editor(parent)
# does not seem to solve things:
self.editor.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
class Document(QtGui.QTextDocument):
def __init__(self, *args):
super().__init__(*args)
self.contentsChange.connect(self.contents_change)
def drawContents(self, p, rect):
print(f'p {p} rect {rect}')
super().drawContents(p, rect)
def contents_change(self, position, chars_removed, chars_added):
# strangely, after a line break, this shows a higher rect NOT when the first character
# causes a line break... but after that!
print(f'contents change, size {self.size()}')
# parent.parent() is the table view
parent.parent().resizeRowToContents(index.row())
self.editor.setDocument(Document())
return self.editor
def paint(self, painter, option, index):
doc = QtGui.QTextDocument()
doc.setDocumentMargin(0)
doc.setDefaultFont(option.font)
self.initStyleOption(option, index)
painter.save()
doc.setTextWidth(option.rect.width())
doc.setHtml(option.text)
option.text = ""
option.widget.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)
painter.translate(option.rect.left(), option.rect.top())
clip = QtCore.QRectF(0, 0, option.rect.width(), option.rect.height())
painter.setClipRect(clip)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
ctx.clip = clip
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
self.initStyleOption(option, index)
doc = QtGui.QTextDocument()
if self.editor != None and index.row() == 0:
print(f'self.editor.size() {self.editor.size()}')
print(f'self.editor.document().size() {self.editor.document().size()}')
doc.setTextWidth(option.rect.width())
doc.setDefaultFont(option.font)
doc.setDocumentMargin(0)
doc.setHtml(option.text)
doc_height_int = int(doc.size().height())
if self.editor != None and index.row() == 0:
print(f'...row 0 doc_height_int {doc_height_int}')
return QtCore.QSize(int(doc.idealWidth()), doc_height_int)
app = QtWidgets.QApplication([])
app.setFont(default_font)
main_window = MainWindow()
main_window.show()
exec_return = app.exec()
sys.exit(exec_return)
If I start editing the right-hand cell in row 0 (F2 or double-click), and start typing, slowly at the end of the existing text, the words "four five six seven", I find that a line break (word-wrap) occurs when I type the "n" of seven. But the line which at that moment prints f'contents change, size {self.size()}'
shows that the document height is still only 32.0. It is only when I type another character that this increases to 56.0.
I want the row to expand in height as the editor (or its document?) grows in height.
There are a couple of other puzzles: when I type in this editor, the characters are currently jumping up and down a bit. Secondly, the line self.editor.document().size()
(in sizeHint
) is printed 4 times when I type each character. To me both phenomena suggest that I might be short-circuiting signals in some way, or in some way doing things in the wrong way.
As described, I have not been able to find any way of measuring the true height of the QTextDocument
(or its QTextEdit
editor) immediately after a line break, or indeed anything like a signal which is emitted when a line break occurs (in this connection I also looked at QTextCursor
, for example).
Edit
I've now changed the main window constructor a bit so a QLE can show the dimensions of the QTextDocument
in deferred fashion (NB can't use a button because clicking takes focus away and destroys the editor). So please try new version as above if intrigued.
What this shows is rather revealing: you will see that the next 1-second "tick" after the word-wrap occurs, the correct height for the document is given in the QLE. This suggests to me that there is some sort of deferred triggering going on here. But because I haven't been able to find a suitable method or signal which activates when the QTextDocument
changes size, I'm not sure how it is possible to respond to that.
PS it works the other way too: if you slowly delete characters after having provoked a word-wrap, until the text becomes one line again, the QLE shows the right height of 32.0 while contents_change
continues to show an incorrect height of 56.0.