Search code examples
pythonpyqt5resizeqtextedit

How to put a TextEdit inside a cell in QTableWidget - Pyqt5?


I managed to put a TextEdit inside a cell in my QTableWidget using this code:

for i in range(10):
    rowCount = self.tableWidget_3.rowCount()
    self.tableWidget_3.insertRow(rowCount)

    combo = QTextEdit(self)
    self.tableWidget_3.setCellWidget(i, 0, combo)
    self.tableWidget_3.cellWidget(i, 0).setText(str(per_picture[i]))
    
    #Adjusting the TextEdit
    docHeight = self.tableWidget_3.cellWidget(i, 0).document().size().height()
    self.tableWidget_3.cellWidget(i, 0).setMinimumHeight(docHeight)

All is working well until I to the part of adjusting the size of the TextEdit to match the size of the text. The 2 Text edit seem to overlap when adjusting the size like the one in this

picture of the overlapping.

How can I fix this 2 TextEdit overlapping? How can I resize a TextEdit inside a TableWidget?

These are the file needed to reproduce the problem:

MyApp.py:

from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit
from PyQt5 import uic
from PyQt5 import QtWidgets
import sys

text = """In order to have a complete sentence, the sentence must 
        have a minimum of three word types: a subject, a verb, 
        and an object. In most cases, the subject is a noun or 
        a pronoun."""

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi("MyApp.ui", self)
        self.tableWidget_3.verticalHeader().setVisible(False)
        self.tableWidget_3.horizontalHeader().setVisible(False)
        self.tableWidget_3.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)

        for i in range(10):
            rowCount = self.tableWidget_3.rowCount()
            self.tableWidget_3.insertRow(rowCount)
            combo = QTextEdit(self)
            self.tableWidget_3.setCellWidget(i, 0, combo)
            self.tableWidget_3.cellWidget(i, 0).setText(text)
            #self.tableWidget_3.cellWidget(i, 0).adjustSize()
            docHeight = self.tableWidget_3.cellWidget(i, 0).document().size().height()
            self.tableWidget_3.cellWidget(i, 0).setMinimumHeight(int(docHeight))

app = QApplication(sys.argv)
myApp = MyApp()
myApp.show()
try:
    sys.exit(app.exec())
except:
    print('Closing Window')

MyApp.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>561</width>
    <height>421</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Editor</string>
  </property>
  <widget class="QWidget" name="verticalLayoutWidget_2">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>561</width>
     <height>421</height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout_2">
    <property name="sizeConstraint">
     <enum>QLayout::SetNoConstraint</enum>
    </property>
    <item>
     <widget class="QTableWidget" name="tableWidget_3">
      <column>
       <property name="text">
        <string>New Column</string>
       </property>
      </column>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

Solution

  • The problem is that setting the minimum size of the cell widget does not set the minimum size of the section. By default, sections all have a standard item size (computed using the default font and system/style configuration), and since the minimum size of the widget is not taken into account, the result is that you only see part of the editor.

    Since you hidden the vertical header, I suppose you want the rows to resize automatically, and that can be done by properly implementing the sizeHint of the editor. Then, you can install an event filter on the table so that it will automatically try to resize the rows according to the contents:

    class TextEdit(QTextEdit):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.textChanged.connect(self.updateGeometry)
    
        def sizeHint(self):
            hint = super().sizeHint()
            if self.toPlainText():
                doc = self.document().clone()
                width = self.width() - self.frameWidth() * 2
                if self.verticalScrollBar().isVisible():
                    width -= self.verticalScrollBar().width()
                doc.setTextWidth(width)
                height = round(doc.size().height())
            else:
                height = self.fontMetrics().height()
                height += self.document().documentMargin() * 2
            height += self.frameWidth() * 2
            hint.setHeight(height)
            return hint
    
    
    class MyApp(QWidget):
        def __init__(self):
            super().__init__()
            uic.loadUi("MyApp.ui", self)
            self.tableWidget_3.verticalHeader().setVisible(False)
            self.tableWidget_3.horizontalHeader().setVisible(False)
            self.tableWidget_3.horizontalHeader().setSectionResizeMode(
                0, QtWidgets.QHeaderView.Stretch)
    
            for i in range(10):
                rowCount = self.tableWidget_3.rowCount()
                self.tableWidget_3.insertRow(rowCount)
                combo = TextEdit(self)
                self.tableWidget_3.setCellWidget(i, 0, combo)
                combo.setText(text)
                combo.textChanged.connect(
                    lambda i=i: self.tableWidget_3.resizeRowToContents(i))
            self.tableWidget_3.installEventFilter(self)
    
        def eventFilter(self, source, event):
            if event.type() == event.Resize:
                QTimer.singleShot(0, self.tableWidget_3.resizeRowsToContents)
            return super().eventFilter(source, event)
    

    Note: you didn't properly set the layout on the main window: you just added a "floating" layout that is completely ignored when the window is resized; move the table outside of it, remove that layout, right click on an empty area of the window, and select a vertical or horizontal layout from the "Lay out" submenu.
    Also, for future reference, if you only need a few widgets for your examples, you don't need to use a Designer file: just create a basic QWidget subclass, set a layout for it, and add the widgets you need to use; your example could have been reduced to just 3 more lines in your MyApp class without the need for the UI file:
    layout = QVBoxLayout(self)
    self.tableWidget = QTableWidget()
    layout.addWidget(self.tableWidget)
    This will make your example more simple and will also makes things easier for us, since we only need to copy/paste one file.