Search code examples
pythonpython-3.xpyqtpyqt5qtablewidget

Pyqt QTablewidget wordwrap lines


How can I set the width of a QTableWidgetcolumn and make the text in each cell start at a new line if its width is bigger then the set width ( kind of like in excel that the text is allowed to have a/be wordwraped). Right now my hole text per cell is written in one really long line, but i would like that it is nice to look at/readable in the destopapplication. Therefor i would love to keep the text in one cell but have a wordwrap/make the text have new lines in it.

Here is my code which contians the self.table.setWordWrap(True) command which will not do anything. Please note that the whole qtablewidget is been overwritten to do coloring of the text (i think this might be the problem why wordwrap might not work):

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

found_words_num =int ('100')
data_single = {'Position':['hallo i like to do this and it would be nice if there was a new line. othere lines and so one.','b'], 'LV-Text': ['c','d'], 'Fundwörter': ['e','f'], 'Hersteller': ['g','h']}
words_in_columns = ['a','b']

textMargins = 12
borderMargins = 10


class HighlightDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(HighlightDelegate, self).__init__(parent)
        self.doc = QtGui.QTextDocument(self)
        self._filters = []

    def paint(self, painter, option, index):
        painter.save()
        options = QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options, index)
        self.doc.setPlainText(options.text)
        self.apply_highlight()
        options.text = ""
        style = QtWidgets.QApplication.style() if options.widget is None \
            else options.widget.style()
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)

        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
        if option.state & QtWidgets.QStyle.State_Selected:
            ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
        else:
            ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                QtGui.QPalette.Active, QtGui.QPalette.Text))

        textRect = style.subElementRect(
            QtWidgets.QStyle.SE_ItemViewItemText, options)

        if index.column() != 0:
            textRect.adjust(5, 0, 0, 0)

        the_constant = 4
        margin = (option.rect.height() - options.fontMetrics.height()) // 2
        margin = margin - the_constant
        textRect.setTop(textRect.top() + margin)

        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        self.doc.documentLayout().draw(painter, ctx)

        painter.restore()

    def apply_highlight(self):
        cursor = QtGui.QTextCursor(self.doc)
        cursor.beginEditBlock()
        fmt = QtGui.QTextCharFormat()
        fmt.setForeground(QtCore.Qt.red)
        for f in self.filters():
            highlightCursor = QtGui.QTextCursor(self.doc)
            while not highlightCursor.isNull() and not highlightCursor.atEnd():
                highlightCursor = self.doc.find(f, highlightCursor)
                if not highlightCursor.isNull():
                    highlightCursor.mergeCharFormat(fmt)
        cursor.endEditBlock()

    @QtCore.pyqtSlot(list)
    def setFilters(self, filters):
        if self._filters == filters: return
        self._filters = filters

    def filters(self):
        return self._filters


class main_result_pos(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(main_result_pos, self).__init__(parent)

        self.setGeometry(50, 50, 1100, 850)  # Fenstergröße festlegen

        self.table = QTableWidget(self)
        self.table.setGeometry(QtCore.QRect(10, 10, 900, 600))
        self.table.setRowCount(found_words_num)
        self.table.setColumnCount(6)
        self.table.setSortingEnabled(True)
        self.table.setWordWrap(True)

        self._delegate = HighlightDelegate(self.table)  ##Klasse aufrufen
        self.table.setItemDelegate(self._delegate)
        le = QtWidgets.QLineEdit()
        le.textChanged.connect(self.on_textChanged)

        search_list = [word for column in words_in_columns for word in column]
        list_c_str = ' '.join(search_list)
        le.setText(list_c_str)

        horHeaders = []
        for col, key in enumerate(sorted(data_single.keys())):
            horHeaders.append(key)
            for row, item in enumerate(data_single[key]):
                newitem = QTableWidgetItem(item)
                newitem.setTextAlignment(QtCore.Qt.AlignCenter)
                self.table.setItem(row, col, newitem)

        self.table.setHorizontalHeaderLabels(['A', 'B','C', 'D' , 'E', 'F'])

        self.table.resizeRowsToContents()
        self.table.resizeColumnsToContents()

    @QtCore.pyqtSlot(str)
    def on_textChanged(self, text):
        self._delegate.setFilters(list(set(text.split())))
        self.table.viewport().update()


def main():
    app = QApplication(sys.argv)
    ex = main_result_pos()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Solution

  • You have to enable the wordwrap in the QTextDocument in addition to setting the sectionResizeMode of the vertical header to QHeaderView::ResizeToContents:

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class HighlightDelegate(QtWidgets.QStyledItemDelegate):
        def __init__(self, parent=None):
            super(HighlightDelegate, self).__init__(parent)
            self._filters = []
            self._wordwrap = False
            self.doc = QtGui.QTextDocument(self)
    
        def paint(self, painter, option, index):
            painter.save()
            options = QtWidgets.QStyleOptionViewItem(option)
            self.initStyleOption(options, index)
            self.doc.setPlainText(options.text)
            self.apply_highlight()
    
            if self._wordwrap:
                self.doc.setTextWidth(options.rect.width())
            options.text = ""
    
            style = QApplication.style() if options.widget is None else options.widget.style()
            style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
    
            if self._wordwrap:
                painter.translate(options.rect.left(), options.rect.top())
                clip = QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(options.rect.size()))
                self.doc.drawContents(painter, clip)
            else:
                ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
                if option.state & QtWidgets.QStyle.State_Selected:
                    ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                        QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
                else:
                    ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                        QtGui.QPalette.Active, QtGui.QPalette.Text))
                textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None)
                if index.column() != 0:
                    textRect.adjust(5, 0, 0, 0)
                constant = 4
                margin = (option.rect.height() - options.fontMetrics.height()) // 2
                margin = margin - constant
                textRect.setTop(textRect.top() + margin)
                painter.translate(textRect.topLeft())
                painter.setClipRect(textRect.translated(-textRect.topLeft()))
                self.doc.documentLayout().draw(painter, ctx)
    
            painter.restore()
            s = QtCore.QSize(self.doc.idealWidth(), self.doc.size().height())
            index.model().setData(index, s, QtCore.Qt.SizeHintRole)
    
        def apply_highlight(self):
            cursor = QtGui.QTextCursor(self.doc)
            cursor.beginEditBlock()
            fmt = QtGui.QTextCharFormat()
            fmt.setForeground(QtCore.Qt.red)
            for f in self.filters():
                highlightCursor = QtGui.QTextCursor(self.doc)
                while not highlightCursor.isNull() and not highlightCursor.atEnd():
                    highlightCursor = self.doc.find(f, highlightCursor)
                    if not highlightCursor.isNull():
                        highlightCursor.mergeCharFormat(fmt)
            cursor.endEditBlock()
    
        @QtCore.pyqtSlot(list)
        def setFilters(self, filters):
            if self._filters == filters: return
            self._filters = filters
            self.parent().viewport().update()
    
        def filters(self):
            return self._filters
    
        def setWordWrap(self, on):
            self._wordwrap = on
            mode = QtGui.QTextOption.WordWrap if on else QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere
    
            textOption = QtGui.QTextOption(self.doc.defaultTextOption())
            textOption.setWrapMode(mode)
            self.doc.setDefaultTextOption(textOption)
            self.parent().viewport().update()
    
    data_single = {'Position':['hallo i like to do this and it would be nice if there was a new line. othere lines and so one.','b'], 'LV-Text': ['c','d'], 'Fundwörter': ['e','f'], 'Hersteller': ['g','h']}
    words_in_columns = ['a','b']
    
    class main_result_pos(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(main_result_pos, self).__init__(parent)
            self.table = QtWidgets.QTableWidget(100, 6)
            self.table.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
            self.table.setSortingEnabled(True)
    
            self._delegate = HighlightDelegate(self.table)
            self.table.setItemDelegate(self._delegate)
            le = QtWidgets.QLineEdit(textChanged=self.on_textChanged)
    
            search_list = [word for column in words_in_columns for word in column]
            list_c_str = ' '.join(search_list)
            le.setText(list_c_str)
    
            horHeaders = []
            for col, key in enumerate(sorted(data_single.keys())):
                horHeaders.append(key)
                for row, item in enumerate(data_single[key]):
                    newitem = QtWidgets.QTableWidgetItem(item)
                    self.table.setItem(row, col, newitem)
            self.table.setHorizontalHeaderLabels(horHeaders)
    
            self._delegate.setWordWrap(True)
            self.resize(640, 480)
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(le)
            lay.addWidget(self.table)
    
        @QtCore.pyqtSlot(str)
        def on_textChanged(self, text):
            self._delegate.setFilters(list(set(text.split())))
    
    def main():
        app = QtWidgets.QApplication(sys.argv)
        ex = main_result_pos()
        ex.show()
        sys.exit(app.exec_())
    
    if __name__ == '__main__':
        main()
    

    enter image description here