QTableView单行文本行高过大问题排查与解决求助
解决QTableView单行文本行高过大的问题
问题概述
使用PyQt5的QTableView展示日志时,多行文本行高能自动适配换行后的高度,但单行文本行高始终偏大(默认24px)。尝试修改sizeHintForRow返回值、设置垂直表头setMinimumSectionSize均无效,且模型的SizeHintRole从未触发,操作系统为Windows 10。
最小可复现代码
import sys, logging, datetime from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5.Qt import QVBoxLayout class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.__class__.instance = self self.resize(1200, 1600) # w, h main_splitter = QtWidgets.QSplitter(self) main_splitter.setOrientation(QtCore.Qt.Vertical) self.setCentralWidget(main_splitter) self.top_frame = QtWidgets.QFrame() main_splitter.addWidget(self.top_frame) self.bottom_frame = BottomFrame() self.bottom_frame.setMaximumHeight(350) self.bottom_frame.setMinimumHeight(100) main_splitter.addWidget(self.bottom_frame) main_splitter.setCollapsible(1, False) self.bottom_frame.construct() class BottomFrame(QtWidgets.QFrame): def construct(self): layout = QtWidgets.QVBoxLayout(self) # 去掉默认的10px边框 layout.setContentsMargins(1, 1, 1, 1) self.setLayout(layout) self.messages_table = LogTableView() layout.addWidget(self.messages_table) self.messages_table.visual_log('hello world') self.messages_table.visual_log('message 2 qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd ') self.messages_table.visual_log('message 3') self.messages_table.visual_log('message 4', logging.ERROR) self.messages_table.visual_log('message 5') self.messages_table.visual_log('message 6 qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd qunaomdd ') self.messages_table.visual_log('message 7') class LogTableView(QtWidgets.QTableView): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setModel(QtGui.QStandardItemModel()) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().hide() self.setVerticalHeader(VerticalHeader(self)) self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) self.verticalHeader().hide() self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.setAlternatingRowColors(True) def sizeHintForRow(self, row ): hint = super().sizeHintForRow(row) if hint < 25 and hint > 10: hint = 10 return hint def visual_log(self, msg: str, log_level: int=logging.INFO): model = self.model() i_new_row = model.rowCount() model.insertRow(i_new_row) datetime_stamp_str = datetime.datetime.now().strftime('%a %H:%M:%S.%f')[:-3] model.setItem(i_new_row, 0, QtGui.QStandardItem(datetime_stamp_str)) model.setItem(i_new_row, 1, QtGui.QStandardItem(str(log_level))) self.setColumnWidth(0, 160) self.setColumnWidth(1, 100) model.setItem(i_new_row, 2, QtGui.QStandardItem(msg)) QtCore.QTimer.singleShot(0, self.resizeRowsToContents) QtCore.QTimer.singleShot(10, self.scrollToBottom) class VerticalHeader(QtWidgets.QHeaderView): def __init__(self, parent): super().__init__(QtCore.Qt.Vertical, parent) def sectionSizeHint(self, logical_index): hint = super().sectionSizeHint(logical_index) print(f'vh index {logical_index} hint {hint}') return hint def main(): app_instance = QtWidgets.QApplication(sys.argv) MainWindow().show() sys.exit(app_instance.exec()) if __name__ == '__main__': main()
问题根源
QTableView的行高计算默认依赖**项委托(Item Delegate)**的sizeHint方法,默认委托会使用字体的lineSpacing(行间距)计算高度,这是单行文本行高偏大的核心原因。此外,resizeRowsToContents会直接使用委托计算的尺寸,覆盖sizeHintForRow的修改值;而QStandardItemModel默认不会返回SizeHintRole的数据,导致该角色从未触发。
解决方法
方法1:自定义项委托,精确计算行高
重写QStyledItemDelegate的sizeHint方法,根据文本实际行数调整高度,单行时直接使用字体高度而非行间距:
class LogItemDelegate(QtWidgets.QStyledItemDelegate): def sizeHint(self, option, index): hint = super().sizeHint(option, index) text = index.data(QtCore.Qt.DisplayRole) if not text: return hint metrics = QtGui.QFontMetrics(option.font) text_rect = metrics.boundingRect(option.rect, QtCore.Qt.TextWordWrap, text) # 单行文本直接使用字体高度,去除多余行间距 if text_rect.height() <= metrics.height(): hint.setHeight(metrics.height()) return hint
在LogTableView的__init__方法中设置该委托:
self.setItemDelegate(LogItemDelegate(self))
方法2:给QStandardItem设置SizeHintRole
添加日志项时,直接为单元格指定SizeHintRole数据,精准控制行高:
修改visual_log方法中设置消息列的代码:
msg_item = QtGui.QStandardItem(msg) metrics = QtGui.QFontMetrics(self.font()) single_line_height = metrics.height() # 计算文本在当前列宽下的实际高度 text_rect = metrics.boundingRect(QtCore.QRect(0,0,self.columnWidth(2), 0), QtCore.Qt.TextWordWrap, msg) row_height = max(single_line_height, text_rect.height()) msg_item.setData(QtCore.QSize(0, row_height), QtCore.Qt.SizeHintRole) model.setItem(i_new_row, 2, msg_item)
额外优化
- 移除
sizeHintForRow的重写逻辑,自定义委托或SizeHintRole已能正确控制行高 - 可将字体高度计算提前,避免重复计算
效果验证
应用上述任一方法后,单行文本行高会自动适配字体实际高度,多行文本仍会自动换行并调整行高,完全符合预期。
内容的提问来源于stack exchange,提问作者mike rodent
相关产品推荐
相关产品推荐

