Create Dynamic Tables with QTableWidget in PyQt6

As a Python developer, working on a project for one of my clients, I needed to display tabular data in a PyQt6 application. After exploring various options, I found QTableWidget to be the most flexible and user-friendly solution.

In this tutorial, I will cover several ways to use QTableWidget effectively in PyQt6, from creating a basic table to implementing advanced features like sorting and filtering.

So let’s get in..

Create a Basic QTableWidget

Let’s start with the fundamentals of creating a QTableWidget in PyQt6:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Sales Data Viewer")
        self.setGeometry(100, 100, 600, 400)

        # Create the table widget
        self.table = QTableWidget()

        # Set row and column count
        self.table.setRowCount(4)
        self.table.setColumnCount(3)

        # Set headers
        self.table.setHorizontalHeaderLabels(["Product", "Q1 Sales", "Q2 Sales"])

        # Populate with sample data
        products = ["Laptops", "Smartphones", "Tablets", "Accessories"]
        q1_sales = ["$42,500", "$65,300", "$28,700", "$15,900"]
        q2_sales = ["$48,200", "$72,100", "$32,450", "$18,370"]

        for row in range(4):
            self.table.setItem(row, 0, QTableWidgetItem(products[row]))
            self.table.setItem(row, 1, QTableWidgetItem(q1_sales[row]))
            self.table.setItem(row, 2, QTableWidgetItem(q2_sales[row]))

        # Resize columns to content
        self.table.resizeColumnsToContents()

        # Create layout and add table
        layout = QVBoxLayout()
        layout.addWidget(self.table)

        # Create container widget and set layout
        container = QWidget()
        container.setLayout(layout)

        # Set central widget
        self.setCentralWidget(container)

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

You can refer to the screenshot below to see the output.

QTableWidget in PyQt6

This code creates a simple table with product sales data. The table has three columns and four rows with appropriate headers.

Customize Table Appearance

Now that we have a basic table, let’s see how we can customize its appearance:

# Inside __init__ method, after creating the table

# Alternating row colors
self.table.setAlternatingRowColors(True)

# Adjust row heights
self.table.verticalHeader().setDefaultSectionSize(40)

# Stretch last column to fill available space
self.table.horizontalHeader().setStretchLastSection(True)

# Set selection behavior to select entire rows
self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)

# Hide vertical header (row numbers)
self.table.verticalHeader().setVisible(False)

# Set grid style
self.table.setShowGrid(True)
self.table.setGridStyle(Qt.PenStyle.DotLine)

You can refer to the screenshot below to see the output.

Create QTableWidget in PyQt6

These customizations make the table more visually appealing and user-friendly. The alternating row colors improve readability, and hiding the vertical header gives a cleaner look.

Read Use QListWidget in PyQt6

Add Interactivity with Signals and Slots

One of the most efficient features of PyQt is its signal-slot mechanism. We can make our table interactive by connecting signals to slots:

# Inside __init__ method, after setting up the table
self.table.cellClicked.connect(self.cell_clicked)
self.table.cellDoubleClicked.connect(self.cell_double_clicked)
self.table.cellChanged.connect(self.cell_changed)

# Add these methods to the MainWindow class
def cell_clicked(self, row, column):
    print(f"Cell clicked: row {row}, column {column}")
    item = self.table.item(row, column)
    if item:
        print(f"Content: {item.text()}")

def cell_double_clicked(self, row, column):
    print(f"Editing cell at row {row}, column {column}")

def cell_changed(self, row, column):
    item = self.table.item(row, column)
    if item:
        print(f"Cell at row {row}, column {column} changed to: {item.text()}")

These connections allow us to respond to user interactions with the table, such as clicking on cells or editing content.

Check out QComboBox Widget in PyQt6

Implement Sorting and Filtering

For larger datasets, sorting and filtering become essential. Here’s how to implement them:

from PyQt6.QtCore import Qt, QSortFilterProxyModel
from PyQt6.QtWidgets import QLineEdit

# Inside __init__ method
# Enable sorting
self.table.setSortingEnabled(True)

# Create a search field
self.search_field = QLineEdit()
self.search_field.setPlaceholderText("Search products...")
self.search_field.textChanged.connect(self.filter_table)
layout.insertWidget(0, self.search_field)  # Add search field above table

# Add this method to MainWindow class
def filter_table(self, text):
    for row in range(self.table.rowCount()):
        should_show = False
        for col in range(self.table.columnCount()):
            item = self.table.item(row, col)
            if item and text.lower() in item.text().lower():
                should_show = True
                break

        self.table.setRowHidden(row, not should_show)

You can refer to the screenshot below to see the output.

Create Dynamic Tables with QTableWidget in PyQt6

With these additions, users can now sort the table by clicking on column headers and filter the data by typing in the search field.

Read How to Use QSlider Widget in PyQt6

Load Data from External Sources

In real-world applications, you’ll likely load data from external sources like CSV files or databases:

import csv

# Method to load data from CSV
def load_from_csv(self, filename):
    try:
        with open(filename, 'r') as csv_file:
            reader = csv.reader(csv_file)
            header = next(reader)  # Read header row

            # Set up table
            self.table.setColumnCount(len(header))
            self.table.setHorizontalHeaderLabels(header)

            # Clear existing data
            self.table.setRowCount(0)

            # Add data rows
            for row_data in reader:
                row = self.table.rowCount()
                self.table.insertRow(row)
                for column, data in enumerate(row_data):
                    self.table.setItem(row, column, QTableWidgetItem(data))

            # Resize columns
            self.table.resizeColumnsToContents()

        return True
    except Exception as e:
        print(f"Error loading CSV: {e}")
        return False

You can call this method to load data from a CSV file into your table.

Export Table Data

It’s often useful to allow users to export table data:

def export_to_csv(self, filename):
    try:
        with open(filename, 'w', newline='') as csv_file:
            writer = csv.writer(csv_file)

            # Write header
            header = []
            for column in range(self.table.columnCount()):
                header.append(self.table.horizontalHeaderItem(column).text())
            writer.writerow(header)

            # Write data rows
            for row in range(self.table.rowCount()):
                row_data = []
                for column in range(self.table.columnCount()):
                    item = self.table.item(row, column)
                    if item:
                        row_data.append(item.text())
                    else:
                        row_data.append("")
                writer.writerow(row_data)

        return True
    except Exception as e:
        print(f"Error exporting to CSV: {e}")
        return False

This method exports the current table data to a CSV file.

Check out Build a Simple Digital Clock with QLCDNumber in PyQt6

Implement Context Menus

Context menus provide a convenient way for users to perform actions on table items:

from PyQt6.QtWidgets import QMenu, QMessageBox

# Add this to __init__
self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu)

# Add this method to MainWindow
def show_context_menu(self, position):
    menu = QMenu()
    delete_action = menu.addAction("Delete Row")
    add_action = menu.addAction("Add Row")
    clear_action = menu.addAction("Clear Selection")

    action = menu.exec(self.table.mapToGlobal(position))

    if action == delete_action:
        selected_rows = set()
        for index in self.table.selectedIndexes():
            selected_rows.add(index.row())

        # Remove rows in reverse order to avoid index shifting problems
        for row in sorted(selected_rows, reverse=True):
            self.table.removeRow(row)

    elif action == add_action:
        row = self.table.rowCount()
        self.table.insertRow(row)

    elif action == clear_action:
        self.table.clearSelection()

This context menu lets users add or delete rows and clear selections directly from the table.

Advanced Cell Customization

You can customize individual cells with different colors, fonts, or alignment:

from PyQt6.QtGui import QColor, QFont
from PyQt6.QtCore import Qt

# Example of customizing cells based on values
def highlight_high_sales(self):
    for row in range(self.table.rowCount()):
        for col in range(1, 3):  # Only check Q1 and Q2 sales columns
            item = self.table.item(row, col)
            if item:
                # Remove currency symbol and commas for comparison
                value_text = item.text().replace('$', '').replace(',', '')
                try:
                    value = float(value_text)
                    if value > 50000:
                        # Highlight high sales in green
                        item.setBackground(QColor(200, 250, 200))
                        item.setFont(QFont("Arial", 10, QFont.Weight.Bold))
                    elif value < 20000:
                        # Highlight low sales in light red
                        item.setBackground(QColor(250, 200, 200))
                except ValueError:
                    pass

This method scans the sales columns and highlights high sales in green and low sales in red, making it easy to identify important data points at a glance.

I hope you found this article helpful. QTableWidget is a useful component in PyQt6 that allows you to create interactive and feature-rich tables for your applications. With the techniques covered here, you should be well-equipped to implement tables that meet your specific requirements.

PyQt6-related tutorials:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.