这是 MRE:
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)
# without this you get the default 10 px border all round the table: too much
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)
# this doesn't seem to have any effect
# self.verticalHeader().setMinimumSectionSize(1)
def sizeHintForRow(self, row ):
hint = super().sizeHintForRow(row)
# print(f'size hint for row {row}: {hint}')
# this doesn't seem to have any effect!
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()
就行高而言,它几乎正在做我想要的事情:尝试调整主窗口的大小:表格行根据文本的实际高度调整其高度(根据需要换行)。
但是...虽然如果您有一段需要多行的文本,并且将行高调整得恰到好处,那么这种方法是有效的,但单行行总是占据稍微过多的高度。
sizeHintForRow()
的 QTableView
方法似乎总是从超类返回 24(像素)...但即使我干涉它并粗暴地说“不,让它变得更小提示”,似乎它被覆盖了稍后再做。
垂直标题上的setMinimumSectionSize
似乎也没有效果。
我还认为表模型的
data()
方法可能是罪魁祸首,但角色 13“SizeHintRole”似乎从未被解雇。
源代码
我尝试查看源代码。这里感兴趣的方法似乎在l上。 3523,
QHeaderView
的resizeSections
方法的几个版本之一。代码自然是相当令人畏惧的,但我确实发现了,例如,l 上的invalidateCachedSizeHint()
。 3261,这也许可以解释为什么尺寸提示被忽略......任何对 QHeaderView
功能有特别复杂了解的人吗? 稍后更新Musicamante建议使用setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContent)
(好主意)后,我现在不知道涉及到哪部分源代码。
...单文本行的行稍微太高了。如果您看到不同的内容,例如完全紧密的单文本行,请告诉我。我的是W10。不同的操作系统可能会产生不同的结果,谁知道呢?
问题是双重的,因为调整内容大小意味着标头查询其自己的大小要求和视图的:
defaultSectionSize()
、minimumSectionSize()
和 sectionSizeFromContents()
;sizeHintForRow()
(或水平标题的 sizeHintForColumn()
),最终查询委托的 sizeHint()
函数;当视图仅显示一行时,样式(即查询上方的标题功能)返回默认高度,该高度可能在显示的文本之上和之外具有更大的垂直边距。
当启用自动换行并且显示多行时,显示的文本通常需要比标题最终使用的更多的垂直空间,并且该空间会按默认边距增加,这可能会导致视觉边距比单行更小模式。
为了实现一致的大小调整,您必须实现一个自定义标头,该标头始终从
sectionSizeFromContents
返回非常小的尺寸,并且还应该为 defaultSectionSize
和 minimumSectionSize
设置任意小尺寸。这样,视图将始终考虑显示文本的实际内容,并且边距将始终保持一致。
class VerticalHeader(QHeaderView):
def __init__(self, parent):
super().__init__(Qt.Vertical, parent)
# we can set everything in here
self.setSectionResizeMode(self.ResizeToContents)
self.setDefaultSectionSize(1)
self.setMinimumSectionSize(1)
self.hide()
def sectionSizeFromContents(self, index):
size = super().sectionSizeFromContents(index)
size.setHeight(1)
return size
请注意,
sectionSizeFromContents()
也用于获取垂直标题的宽度,但由于您不显示垂直标题,因此更简单的return QSize()
就足够了。
最后,你只需要正确设置标题即可;您不再需要覆盖表中的
sizeHintForRow
,但应考虑进一步的更改:
class LogTableView(QTableView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# IMPORTANT! set the column count, so that we can automatically set
# the column widths
self.setModel(QStandardItemModel(0, 3))
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.setAlternatingRowColors(True)
self.horizontalHeader().setStretchLastSection(True)
self.horizontalHeader().hide()
self.setVerticalHeader(VerticalHeader(self))
self.setColumnWidth(0, 160)
self.setColumnWidth(1, 100)
...
作为一个不相关的说明,如果程序逻辑需要使用数值,则应避免将数字转换为字符串。不要使用
QStandardItem(str(log_level))
,而是使用 QStandardItem.setData()
;委托将在必要时自动将值转换为字符串。
日期/时间值也是如此,因为它们可能需要适当的逻辑实现(例如排序或过滤):您可以使用适当的委托来根据您的格式显示日期。
class DateDelegate(QStyledItemDelegate):
def displayText(self, value, locale):
if isinstance(value, QDateTime):
return value.toString('ddd HH:mm:ss.zzz')
return super().displayText(value, locale)
class LogTableView(QTableView):
def __init__(self, *args, **kwargs):
... # as above
self.setItemDelegateForColumn(0, DateDelegate(self))
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_item = QStandardItem()
datetime_item.setData(QDateTime.currentDateTime(), Qt.DisplayRole)
model.setItem(i_new_row, 0, datetime_item)
log_item = QStandardItem()
log_item.setData(log_level, Qt.DisplayRole)
model.setItem(i_new_row, 1, log_item)
model.setItem(i_new_row, 2, QStandardItem(msg))