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.

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.

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.
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.

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 FalseYou 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 FalseThis 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:
passThis 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:
- Handle Button Click Events Using Signals and Slots in PyQt6
- Arrange Widgets Using QGridLayout in PyQt6
- Random Number Generator with QLCDNumber in PyQt6

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.