如何在 PyQt5 Python 中自动换行 QTableWidget 的 header 内容

How to word wrap the header contents of QTableWidget in PyQt5 Python

我正在 PyQt5 工作,我有一个 QTableWidget。它有一个 header 列,我想自动换行。下面是 table 的样子:

我们可以看到 header 标签像 Maximum Variation Coefficient 有 3 个词,因此它占用了太多的列宽。怎样才能把header.

中的单词换行

代码如下:

import sys

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *


# Main Window
class App(QWidget):
    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 - QTableWidget'
        self.left = 0
        self.top = 0
        self.width = 300
        self.height = 200

        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.createTable()

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.tableWidget)
        self.setLayout(self.layout)

        # Show window
        self.show()

    # Create table
    def createTable(self):
        self.tableWidget = QTableWidget()

        # Row count
        self.tableWidget.setRowCount(3)

        # Column count
        self.tableWidget.setColumnCount(2)

        self.tableWidget.setHorizontalHeaderLabels(["Maximum Variation Coefficient", "Maximum Variation Coefficient"])
        self.tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)

        self.tableWidget.setItem(0, 0, QTableWidgetItem("3.44"))
        self.tableWidget.setItem(0, 1, QTableWidgetItem("5.3"))
        self.tableWidget.setItem(1, 0, QTableWidgetItem("4.6"))
        self.tableWidget.setItem(1, 1, QTableWidgetItem("1.2"))
        self.tableWidget.setItem(2, 0, QTableWidgetItem("2.2"))
        self.tableWidget.setItem(2, 1, QTableWidgetItem("4.4"))

        # Table will fit the screen horizontally
        self.tableWidget.horizontalHeader().setStretchLastSection(True)
        self.tableWidget.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

我尝试添加这个 self.tableWidget.setWordWrap(True) 但这没有做任何改变。任何人都可以提供一些好的解决方案。请帮忙。谢谢

编辑:

也试过这个:

self.tableWidget.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignHCenter | Qt.Alignment(QtCore.Qt.TextWordWrap))

但是没用

为了实现你所需要的,你必须自己设置header并进行以下两个假设:

  • header 必须根据节内容提供正确的尺寸提示 高度,以防列的 宽度不够;
  • 文字对齐必须包含QtCore.Qt.TextWordWrap标志,让画家知道它可以换行;

请注意,虽然第二个方面在某些情况下可能就足够了(因为 header 通常足够高以适合至少两行),但第一个方面是强制性的,因为文本可能需要更多垂直space,否则部分文字会被截掉。


第一点需要继承QHeaderView并重新实现sectionSizeFromContents():

class WrapHeader(QtWidgets.QHeaderView):
    def sectionSizeFromContents(self, logicalIndex):
        # get the size returned by the default implementation
        size = super().sectionSizeFromContents(logicalIndex)
        if self.model():
            if size.width() > self.sectionSize(logicalIndex):
                text = self.model().headerData(logicalIndex, 
                    self.orientation(), QtCore.Qt.DisplayRole)
                if not text:
                    return size
                # in case the display role is numeric (for example, when header 
                # labels are not defined yet), convert it to a string; 
                text = str(text)

                option = QtWidgets.QStyleOptionHeader()
                self.initStyleOption(option)
                alignment = self.model().headerData(logicalIndex, 
                    self.orientation(), QtCore.Qt.TextAlignmentRole)
                if alignment is None:
                    alignment = option.textAlignment

                # get the default style margin for header text and create a 
                # possible rectangle using the current section size, then use
                # QFontMetrics to get the required rectangle for the wrapped text
                margin = self.style().pixelMetric(
                    QtWidgets.QStyle.PM_HeaderMargin, option, self)
                maxWidth = self.sectionSize(logicalIndex) - margin * 2
                rect = option.fontMetrics.boundingRect(
                    QtCore.QRect(0, 0, maxWidth, 10000), 
                    alignment | QtCore.Qt.TextWordWrap, 
                    text)

                # add vertical margins to the resulting height
                height = rect.height() + margin * 2
                if height >= size.height():
                    # if the height is bigger than the one provided by the base
                    # implementation, return a new size based on the text rect
                    return QtCore.QSize(rect.width(), height)
        return size


class App(QWidget):
    # ...
    def createTable(self):
        self.tableWidget = QTableWidget()
        self.tableWidget.setHorizontalHeader(
            WrapHeader(QtCore.Qt.Horizontal, self.tableWidget))
        # ...

然后,要设置自动换行标志,有两种选择:

  1. 在基础模型上为每个现有列设置对齐标志 setHeaderData()
        # ...
        model = self.tableWidget.model()
        default = self.tableWidget.horizontalHeader().defaultAlignment()
        default |= QtCore.Qt.TextWordWrap
        for col in range(self.tableWidget.columnCount()):
            alignment = model.headerData(
                col, QtCore.Qt.Horizontal, QtCore.Qt.TextAlignmentRole)
            if alignment:
                alignment |= QtCore.Qt.TextWordWrap
            else:
                alignment = default
            model.setHeaderData(
                col, QtCore.Qt.Horizontal, alignment, QtCore.Qt.TextAlignmentRole)
  1. 通过在选项上应用标志,使用 QProxyStyle 覆盖 header 的绘制:
# ...

class ProxyStyle(QtWidgets.QProxyStyle):
    def drawControl(self, control, option, painter, widget=None):
        if control in (self.CE_Header, self.CE_HeaderLabel):
            option.textAlignment |= QtCore.Qt.TextWordWrap
        super().drawControl(control, option, painter, widget)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle(ProxyStyle())
    ex = App()
    sys.exit(app.exec_())

最后,考虑一下:

  • setSectionResizeModeResizeToContentsStretch 以及 setStretchLastSection 一起使用将始终导致 table 尝试用作很多 space 是 header 第一次展示时所要求的;

  • 默认情况下,QHeaderView 部分 不可点击(这是排序的强制要求)并且 highlightSections 属性 是还有False; QTableView 和 QTableWidget 都使用这些值创建它们的 headers True,因此当设置新的 header 时,如果需要排序和突出显示,您必须显式更改这些方面:

      self.tableWidget.setHorizontalHeader(
          WrapHeader(QtCore.Qt.Horizontal, self.tableWidget))
      self.tableWidget.horizontalHeader().setSectionsClickable(True)
      self.tableWidget.horizontalHeader().setHighlightSections(True)
    
  • 排序和部分突出显示都会产生一些问题,因为排序指示器需要进一步水平 space 并且突出显示的部分通常显示为 粗体 字体(但在按下鼠标时正常显示);所有这些都可能会产生一些闪烁和奇怪的行为;不幸的是,这些问题没有明显的解决方案,但是当使用 QProxyStyle 时,可以通过覆盖字体样式来避免一些闪烁:

      def drawControl(self, control, option, painter, widget=None):
          if control in (self.CE_Header, self.CE_HeaderLabel):
              option.textAlignment |= QtCore.Qt.TextWordWrap
              if option.state & self.State_Sunken:
                  option.state |= self.State_On
          super().drawControl(control, option, painter, widget)