Как в PyQt6 в QTableWidget текст в horizontalHeader расположить вертикально?

Рейтинг: 1Ответов: 3Опубликовано: 10.03.2023

Есть большая таблица, названия колонок длинные, получаю их из БД.
Суть в том, что бы названия колонок отображались вертикально, а не горизонтально как по умолчанию.


Как есть:

введите сюда описание изображения


Как хочу, что бы выгладили заголовки:

введите сюда описание изображения

Минимальный код для примера:

from PyQt6 import QtWidgets, QtGui, QtCore
from PyQt6.QtCore import Qt    
        
self.tableWidget.setRowCount(5)
self.tableWidget.setColumnCount(7)
self.columnLables = ['', '', 'Кф. товара', 'Выкладка', 'Код блюда', 'Блюдо', 'Категория блюда']
self.tableWidget.setHorizontalHeaderLabels(self.columnLables)
self.font = QtGui.QFont("Times", 10, QFont.Weight.Bold)
self.tableWidget.horizontalHeader().setFont(self.font)

Искал ответ, очень долго искал. Общался с ChatGPT. Решения так и не нашел. Пробовал:

  1. self.tableWidget.horizontalHeader().setOrientation(QtCore.Qt.Vertical)

  2. self.tableWidget.horizontalHeader().setOrientation(Qt.Vertical)

  3. self.tableWidget.horizontalHeader().setRotation(90)

  4. self.tableWidget.horizontalHeader().setStyleSheet( 'QHeaderView::section {''writing-mode: vertical-lr;}')

  5. self.tableWidget.horizontalHeader().setStyleSheet( 'QHeaderView::section {''transform: rotate(90deg);}')

Ни одно из решений не помогло.

Ответы

▲ 2Принят

Если ничего не путаю, ориентацию текста в Qt6 так и не добавили, поэтому видимо придется так же как раньше в Qt5 отрисовывать хэдер самому

import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore


class MyHeaderView(QHeaderView):

    def __init__(self, parent=None):

        super().__init__(QtCore.Qt.Horizontal, parent)
        self._font = QtGui.QFont("helvetica", 15)
        self._metrics = QtGui.QFontMetrics(self._font)
        self._descent = self._metrics.descent()
        self._margin = 10

    def paintSection(self, painter, rect, index):
        # Если надо рамочку, то рисовать тут же
        data = self._get_data(index)
        painter.rotate(-90)
        painter.setFont(self._font)
        painter.drawText(- rect.height() + self._margin,
                         rect.left() + (rect.width() + self._descent) / 2, data)

    def sizeHint(self):
        return QtCore.QSize(0, self._get_text_width() + 2 * self._margin)

    def _get_text_width(self):
        return max([self._metrics.width(self._get_data(i))
                    for i in range(0, self.model().columnCount())])

    def _get_data(self, index):
        return self.model().headerData(index, self.orientation())

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()        
        self.central_widget = QWidget(self)
        self.setCentralWidget(self.central_widget)
        self.layout_main_window = QVBoxLayout()
        self.central_widget.setLayout(self.layout_main_window)
        self.layout_table = QHBoxLayout()
        self.table = QTableWidget()
        self.table.setColumnCount(3)
        #  заменяем хэдер
        self.headerView = MyHeaderView()
        self.table.setHorizontalHeader(self.headerView)
        # ^^^^^^^^^^^^^^^
        self.table.setHorizontalHeaderLabels(['первый', 'второй', 'третий'])
        self.layout_table.addWidget(self.table)
        self.layout_main_window.addLayout(self.layout_table)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())
▲ 2

Как вариант:

import sys
from math import sqrt, sin, acos, hypot, degrees, radians
from PyQt5 import QtCore, QtGui, QtWidgets


class AngledHeader(QtWidgets.QHeaderView):
    borderPen = QtGui.QColor(0, 190, 255)
    labelBrush = QtGui.QColor(255, 212, 0)
    def __init__(self, parent=None):
        QtWidgets.QHeaderView.__init__(self, QtCore.Qt.Horizontal, parent)
        self.setSectionResizeMode(self.Fixed)
        self.setDefaultSectionSize(sqrt((self.fontMetrics().height() + 4)** 2 *2))
        self.setSectionsClickable(True)

    def sizeHint(self):
        fm = self.fontMetrics()
        width = minSize = self.defaultSectionSize()
        count = self.count()
        for s in range(count):
            if self.isSectionHidden(s):
                continue
            rect = fm.boundingRect(str(self.model().headerData(s, QtCore.Qt.Horizontal)) + '    ')
            diag = hypot(rect.width(), rect.height())
            angle = degrees(acos((diag ** 2 + rect.width() ** 2 - rect.height() ** 2) / (2. * diag * rect.width())))
            minSize = max(minSize, sin(radians(angle + 45)) * diag)
        hint = QtCore.QSize(width * count + 2000, minSize)
        return hint

    def mousePressEvent(self, event):
        width = self.defaultSectionSize()
        first = self.sectionViewportPosition(0)
        rect = QtCore.QRect(0, 0, width, -self.height())
        transform = QtGui.QTransform().translate(0, self.height()).shear(-1, 0)
        for s in range(self.count()):
            if self.isSectionHidden(s):
                continue
            if transform.mapToPolygon(rect.translated(s * width + first, 0)).containsPoint(event.pos(), QtCore.Qt.WindingFill):
                self.sectionPressed.emit(s)
                return

    def paintEvent(self, event):
        qp = QtGui.QPainter(self.viewport())
        qp.setRenderHints(qp.Antialiasing)
        width = self.defaultSectionSize()
        delta = self.height()
        qp.translate(self.sectionViewportPosition(0) - .5, -.5)
        fmDelta = (self.fontMetrics().height() - self.fontMetrics().descent()) * .5
        rect = QtCore.QRectF(0, 0, width, -delta)
        for s in range(self.count()):
            if self.isSectionHidden(s):
                continue
            qp.save()
            qp.save()
            qp.setPen(self.borderPen)
            qp.setTransform(qp.transform().translate(s * width, delta).shear(-1, 0))
            qp.drawRect(rect)
            qp.setPen(QtCore.Qt.NoPen)
            qp.setBrush(self.labelBrush)
            qp.drawRect(rect.adjusted(2, -2, -2, 2))
            qp.restore()
            qp.translate(s * width + width, delta)
            qp.rotate(-45)
            qp.drawText(0, -fmDelta, str(self.model().headerData(s, QtCore.Qt.Horizontal)))
            qp.restore()


class AngledTable(QtWidgets.QTableView):
    def __init__(self, *args, **kwargs):
        QtWidgets.QTableView.__init__(self, *args, **kwargs)
        self.setHorizontalHeader(AngledHeader(self))
        self.fixLock = False

    def setModel(self, model):
        if self.model():
            self.model().headerDataChanged.disconnect(self.fixViewport)
        QtWidgets.QTableView.setModel(self, model)
        model.headerDataChanged.connect(self.fixViewport)

    def fixViewport(self):
        if self.fixLock:
            return
        self.fixLock = True
        QtCore.QTimer.singleShot(0, self.delayedFixViewport)

    def delayedFixViewport(self):
        QtWidgets.QApplication.processEvents()
        header = self.horizontalHeader()
        bar = self.horizontalScrollBar()
        bar.blockSignals(True)
        step = bar.singleStep() * (header.height() / header.defaultSectionSize())
        bar.setMaximum(bar.maximum() + step)
        bar.blockSignals(False)
        self.fixLock = False

    def resizeEvent(self, event):
        QtWidgets.QTableView.resizeEvent(self, event)
        self.fixViewport()


class TestWidget(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        l = QtWidgets.QGridLayout()
        self.setLayout(l)
        self.table = AngledTable()
        l.addWidget(self.table)
        model = QtGui.QStandardItemModel(5, 7)
        self.table.setModel(model)
        self.table.setHorizontalScrollMode(self.table.ScrollPerPixel)
        model.setVerticalHeaderLabels(
            ['Позиция 1', 'Позиция 2', 'Позиция 3', 'Позиция 4', 'Позиция 5'])
        model.setHorizontalHeaderLabels(
            ['', '', 'Кф. товара', 'Выкладка', 'Код блюда', 'Блюдо', 
            'Категория блюда']   
        )


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = TestWidget()
    w.resize(400, 300)
    w.show()
    sys.exit(app.exec_())

введите сюда описание изображения

▲ 0
class MyHeaderView(QHeaderView):

    def __init__(self, parent=None):

        super().__init__(QtCore.Qt.Orientation.Horizontal, parent)
        self._font = QtGui.QFont("helvetica", 15)
        self._metrics = QtGui.QFontMetrics(self._font)
        self._descent = self._metrics.descent()
        self._margin = 10

    def paintSection(self, painter, rect, index):
        # Если надо рамочку, то рисовать тут же
        data = self._get_data(index)
        painter.rotate(-90)
        painter.setFont(self._font)
        painter.drawText(
            QtCore.QPointF(- rect.height() + self._margin, rect.left() + (rect.width() + self._descent) / 2), data)

    def sizeHint(self):
        return QtCore.QSize(0, self._get_text_width() + 2 * self._margin)

    def _get_text_width(self):
        return max([self._metrics.boundingRect(self._get_data(i)).width() for i in range(0, self.model().columnCount())])

    def _get_data(self, index):
        return self.model().headerData(index, self.orientation())

Вот так вот заработало!