I am working on PyQt5
where I have a QTableWidget
. It has a header column which I want to word wrap. Below is how the table looks like:
As we can see that the header label like Maximum Variation Coefficient
has 3 words, thus its taking too much column width. How can wrap the words in the header.
Below is the code:
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_())
I tried adding this self.tableWidget.setWordWrap(True)
but this doesnt make any change. Can anyone give some good solution. Please help. Thanks
EDIT:
Also tried this :
self.tableWidget.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignHCenter | Qt.Alignment(QtCore.Qt.TextWordWrap))
But it didnt worked
Advertisement
Answer
In order to achieve what you need, you must set your own header and proceed with the following two assumptions:
- the header must provide the correct size hint height according to the section contents in case the width of the column is not sufficient;
- the text alignment must include the
QtCore.Qt.TextWordWrap
flag, so that the painter knows that it can wrap text;
Do note that, while the second aspect might be enough in some situations (as headers are normally tall enough to fit at least two lines), the first point is mandatory as the text might require more vertical space, otherwise some text would be cut out.
The first point requires to subclass QHeaderView and reimplement 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)) # ...
Then, to set the word wrap flag, there are two options:
- set the alignment flag on the underlying model with
setHeaderData()
for each existing column:
# ... 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)
- Use a QProxyStyle to override the painting of the header, by applying the flag on the option:
# ... 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_())
Finally, consider that:
using
setSectionResizeMode
withResizeToContents
orStretch
, along withsetStretchLastSection
, will always cause the table trying to use as much space as required by the headers upon showing the first time;by default, QHeaderView sections are not clickable (which is a mandatory requirement for sorting) and the
highlightSections
property is alsoFalse
; both QTableView and QTableWidget create their headers with those values asTrue
, so when a new header is set you must explicitly change those aspects if sorting and highlighting are required:self.tableWidget.setHorizontalHeader( WrapHeader(QtCore.Qt.Horizontal, self.tableWidget)) self.tableWidget.horizontalHeader().setSectionsClickable(True) self.tableWidget.horizontalHeader().setHighlightSections(True)
both sorting and section highlighting can create some issues, as the sort indicator requires further horizontal space and highlighted sections are normally shown with a bold font (but are shown normally while the mouse is pressed); all this might create some flickering and odd behavior; unfortunately, there’s no obvious solution for these problems, but when using the QProxyStyle it’s possible to avoid some flickering by overriding the font style:
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)