How to add a table in a QGroupBox, PyQt5

142 Views Asked by At

I'm pretty new to PyQt5 and still struggle. I tried the whole day adding rows and columns in a QGroupBox. The only solution I found was adding a QTableWidget, the problem with that is the widget size.

For now I only wanna have the header of this table, the user can then add rows to this table. [ID, Name]

I want to have the cells, but not the 'window' that comes with it. If I try to align it in the center of the QGroupBox it aligns the whole thing in the center, not the columns. (the columns are in the top left corner of the widget, see picture)

How it currently looks

That's the code snippet which creates the table:

Create the box

    self.groupbox1 = QGroupBox('Data')
    self.groupbox1.setMaximumWidth(350)
    self.groupbox1.setMaximumHeight(400)
    self.layout = QVBoxLayout()
    self.groupbox1.setLayout(self.layout)
    self.layout.addWidget(self.groupbox1)

Create a QTableWidget

    self.table_widget = QTableWidget()
    self.table_widget.setColumnCount(2)
    self.table_widget.setHorizontalHeaderLabels(['ID', 'Data'])
    self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
    self.table_widget.resizeColumnsToContents()
    self.table_widget.setMaximumSize(200, 200)
    self.upper_layout.setAlignment(Qt.AlignCenter)

    self.upper_layout.addWidget(self.table_widget)

The only thing I found was .resizeColumnsToContents and .setSectionResizeMode which is only adjusting the columns depending on what will be in there.

By setting the maximum size of the table widget I made it smaller but that's not quite what I want. If there is more data added to the table the user has to scroll because the widget isn't big enough. Isn't there anything that is adjusting that automatically?

Or is there another solution to the problem? The button where the user can choose which data to be displayed (which row will be added) should be directly under the rows in the center and move automatically up/down if rows are deleted/added. This is also not possible if I have to have a fixed size of the QTableWidget.

Hope I explained it well enough. I'm thankful for every answer that could bring me closer to the solution. :) (Yes, I had a look at all related posts on Stack Overflow already and probably every other page, didn't solve my problem unfortunately)

2

There are 2 best solutions below

10
mahkitah On BEST ANSWER

This is a modified version of Francesca Mazzeo answer. It uses a subclassed QTableWidget with a reimplemented resizeEvent() method to get the "grow with added rows" behaviour.

There's one weird thing that I don't understand: Adding the first row doesn't trigger resizeEvent(). Subsequent rows do.

I'm posting this any way in the hope someone knows how to fix this.

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QTableWidget, QPushButton, QTableWidgetItem, QHeaderView, QMessageBox


# class ExpandingTableWidget(QTableWidget):
#    def resizeEvent(self, event):
#        super().resizeEvent(event)
#        height = self.horizontalHeader().height()
#        row_count = self.verticalHeader().count()
#        for i in range(row_count):
#            height += self.verticalHeader().sectionSize(i)
#
#        self.setMaximumHeight(height + 2)


class TestApp(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('Test Application')
        self.setGeometry(200, 200, 400, 400)

        # Create a QVBoxLayout for the main window
        self.layout = QVBoxLayout(self)  # 'self' refers to the main window
        self.groupbox1 = QGroupBox('Data')
        self.groupbox_layout = QVBoxLayout()
        self.groupbox1.setLayout(self.groupbox_layout)

        # Create a QTableWidget
        self.table_widget = ExpandingTableWidget()
        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(['ID', 'Name'])
        self.table_widget.verticalHeader().setVisible(False)
        self.table_widget.setShowGrid(False)
        self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # Create a QPushButton to add rows to the QTableWidget
        self.add_row_button = QPushButton('Add Row')
        self.add_row_button.clicked.connect(self.add_row)

        # Add the QTableWidget and QPushButton to the QGroupBox layout
        self.groupbox_layout.addWidget(self.table_widget, stretch=1)
        self.groupbox_layout.addStretch(0)
        self.groupbox_layout.addWidget(self.add_row_button)

        # Add the QGroupBox to the main QVBoxLayout of the main window
        self.layout.addWidget(self.groupbox1)

    def add_row(self):
        # Add a new row to the QTableWidget when the button is clicked
        row_position = self.table_widget.rowCount()
        self.table_widget.insertRow(row_position)
        self.table_widget.setItem(
            row_position, 0, QTableWidgetItem(str(row_position + 1)))
        self.table_widget.setItem(row_position, 1, QTableWidgetItem(''))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = TestApp()
    window.show()
    sys.exit(app.exec())

Edit:
New QTablewidget subclass using musicamente's comments:

class ExpandingTableWidget(QTableWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.model().rowsInserted.connect(self.set_height)
        self.model().columnsInserted.connect(self.set_height)

    def set_height(self):
        height = self.horizontalHeader().height() + self.verticalHeader().length() + self.frameWidth() * 2
        self.setMaximumHeight(height)
13
Dev. Francesca Mazzeo On
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QTableWidget, QPushButton, QTableWidgetItem, QHeaderView, QMessageBox


class TestApp(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('Test Application')
        self.setGeometry(200, 200, 400, 400)

        # Create a QVBoxLayout for the main window
        self.layout = QVBoxLayout(self)  # 'self' refers to the main window
        self.groupbox1 = QGroupBox('Data')
        self.groupbox_layout = QVBoxLayout()
        self.groupbox1.setLayout(self.groupbox_layout)

        # Create a QTableWidget
        self.table_widget = QTableWidget()
        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(['ID', 'Name'])
        self.table_widget.verticalHeader().setVisible(False)
        self.table_widget.setShowGrid(False)
        self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # Create a QPushButton to add rows to the QTableWidget
        self.add_row_button = QPushButton('Add Row')
        self.add_row_button.clicked.connect(self.add_row)

        # Add the QTableWidget and QPushButton to the QGroupBox layout
        self.groupbox_layout.addWidget(self.table_widget)
        self.groupbox_layout.addWidget(self.add_row_button)

        # Add the QGroupBox to the main QVBoxLayout of the main window
        self.layout.addWidget(self.groupbox1)

    def add_row(self):
        # Add a new row to the QTableWidget when the button is clicked
        row_position = self.table_widget.rowCount()
        self.table_widget.insertRow(row_position)
        self.table_widget.setItem(
            row_position, 0, QTableWidgetItem(str(row_position + 1)))
        self.table_widget.setItem(row_position, 1, QTableWidgetItem(''))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = TestApp()
    window.show()
    sys.exit(app.exec_())

Try this and adapt it to your project.

This is the output:

enter image description here

version 2023-07-26 first code:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QTableWidget, QPushButton, QTableWidgetItem, QHeaderView, QMessageBox


class TestApp(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('Test Application')
        self.setGeometry(200, 200, 400, 400)

        # Create a QVBoxLayout for the main window
        self.layout = QVBoxLayout(self)  # 'self' refers to the main window
        self.groupbox1 = QGroupBox('Data')
        self.groupbox_layout = QVBoxLayout()
        self.groupbox1.setLayout(self.groupbox_layout)

        # Create a QTableWidget
        self.table_widget = QTableWidget()
        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(['ID', 'Name'])
        self.table_widget.verticalHeader().setVisible(False)
        self.table_widget.setShowGrid(False)

        # Set a fixed width for the 'ID' column
        self.table_widget.setColumnWidth(0, 50)
        #Set a fixed width for the 'NAME' column
        self.table_widget.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        #Block widht fro the 'ID' column
        self.table_widget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)

        # Create a QPushButton to add rows to the QTableWidget
        self.add_row_button = QPushButton('Add Row')
        self.add_row_button.clicked.connect(self.add_row)

        # Add the QTableWidget and QPushButton to the QGroupBox layout
        self.groupbox_layout.addWidget(self.table_widget)
        self.groupbox_layout.addWidget(self.add_row_button)

        # Add the QGroupBox to the main QVBoxLayout of the main window
        self.layout.addWidget(self.groupbox1)

    def add_row(self):
        # Add a new row to the QTableWidget when the button is clicked
        row_position = self.table_widget.rowCount()
        self.table_widget.insertRow(row_position)
        self.table_widget.setItem(
            row_position, 0, QTableWidgetItem(str(row_position + 1)))
        self.table_widget.setItem(row_position, 1, QTableWidgetItem(''))

        # Resize the 'Name' column to fit the contents
        self.table_widget.resizeColumnToContents(1)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = TestApp()
    window.show()
    sys.exit(app.exec_())

Try this, I have resized and blocked the auto resize for the ID necklace and for the NAME column it resizes automatically

This is the output:

enter image description here

version 2023-07-26 second code:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGroupBox, QVBoxLayout, QTableWidget, QPushButton, QTableWidgetItem, QHeaderView, QScrollArea, QSizePolicy
from PyQt5.QtCore import Qt

class TestApp(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('Test Application')
        self.setGeometry(200, 200, 400, 400)

        # Create a QVBoxLayout for the main window
        self.layout = QVBoxLayout(self)  # 'self' refers to the main window

        # Create a QScrollArea and set it as the main widget
        self.scroll_area = QScrollArea(self)
        self.layout.addWidget(self.scroll_area)

        # Create a QWidget to hold the content
        self.main_widget = QWidget()
        self.main_layout = QVBoxLayout(self.main_widget)

        # Create a QGroupBox to display the table
        self.groupbox1 = QGroupBox('Data')
        self.groupbox_layout = QVBoxLayout()
        self.groupbox1.setLayout(self.groupbox_layout)
        self.groupbox1.setWindowFlag(Qt.WindowContextHelpButtonHint, False)
        self.groupbox1.hide()  # Hide the group box initially

        # Create a QTableWidget
        self.table_widget = QTableWidget()
        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(['ID', 'Name'])
        self.table_widget.verticalHeader().setVisible(False)
        self.table_widget.setShowGrid(False)
        self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # Add the QTableWidget to the QGroupBox layout
        self.groupbox_layout.addWidget(self.table_widget)

        # Add the QGroupBox to the main_layout of the main_widget
        self.main_layout.addWidget(self.groupbox1)

        # Create a QPushButton to add rows to the QTableWidget
        self.add_row_button = QPushButton('Add Row')
        self.add_row_button.clicked.connect(self.add_row)

        # Add the QPushButton to the main_layout of the main_widget
        self.main_layout.addWidget(self.add_row_button)

        # Set the main_widget as the widget for the QScrollArea
        self.scroll_area.setWidget(self.main_widget)

        # Set the QScrollArea to expand the widget vertically
        self.scroll_area.setWidgetResizable(True)
        self.main_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)

        # Set the number of rows to display (2 rows in this case)
        self.displayed_rows = 1

        # Calculate and set the initial height of the QScrollArea based on the QTableWidget height
        table_height = self.table_widget.rowHeight(0) * self.displayed_rows + self.groupbox_layout.spacing()
        self.scroll_area.setMinimumHeight(table_height)

        # Set the maximum height for the QGroupBox
        self.groupbox1.setMaximumHeight(table_height)

    def add_row(self):
        if not self.groupbox1.isVisible():  # Show the group box when the first row is added
            self.groupbox1.show()

        # Add a new row to the QTableWidget when the button is clicked
        row_position = self.table_widget.rowCount()
        self.table_widget.insertRow(row_position)
        self.table_widget.setItem(row_position, 0, QTableWidgetItem(str(row_position + 1)))
        self.table_widget.setItem(row_position, 1, QTableWidgetItem(''))
 
        # Recalculate the new height of the QGroupBox based on the updated QTableWidget height
        table_height = self.table_widget.rowHeight(
            0) * (self.displayed_rows + 1.5) + self.groupbox_layout.spacing()
        self.groupbox1.setFixedHeight(table_height)
        self.table_widget.setMinimumHeight(table_height)

        if (self.displayed_rows < 5):
            self.displayed_rows += 1
        else:
            # Enable scrolling when the displayed rows reach 5
            self.update_scroll_area_height()

    def update_scroll_area_height(self):
        table_height = self.table_widget.rowHeight(
            0) * (self.displayed_rows + 2) + self.groupbox_layout.spacing()
        self.groupbox1.setFixedHeight(table_height)
        self.scroll_area.setMinimumHeight(table_height)

        # Enable vertical scrolling
        self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = TestApp()
    window.show()
    sys.exit(app.exec_())

Try this and adapt it to your project.

This is the output: enter image description here