QTableView 不会完全正确地显示单行文本

问题描述 投票:0回答:1

这是 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。不同的操作系统可能会产生不同的结果,谁知道呢?

python pyqt qtableview
1个回答
0
投票

问题是双重的,因为调整内容大小意味着标头查询其自己的大小要求和视图的:

  • 标题的
    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))

© www.soinside.com 2019 - 2024. All rights reserved.