<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - tables</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/tables.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2020-05-22T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Display Table with QTableWidget — How to display database query results in a PyQt6 table using QTableWidget</title><link href="https://www.pythonguis.com/faq/display-table-with-qtablewidget/" rel="alternate"/><published>2020-05-22T09:00:00+00:00</published><updated>2020-05-22T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-05-22:/faq/display-table-with-qtablewidget/</id><summary type="html">You've got data coming back from a database query as a list of tuples, and you want to show it in a table inside your PyQt6 application. &lt;code&gt;QTableWidget&lt;/code&gt; is a great fit for this &amp;mdash; it gives you a ready-made table you can fill with data without needing to set up a custom model.</summary><content type="html">
            &lt;p&gt;You've got data coming back from a database query as a list of tuples, and you want to show it in a table inside your PyQt6 application. &lt;code&gt;QTableWidget&lt;/code&gt; is a great fit for this &amp;mdash; it gives you a ready-made table you can fill with data without needing to set up a custom model.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to take query results (a list of tuples) and display them in a &lt;code&gt;QTableWidget&lt;/code&gt;, complete with column headers and a button to trigger the display.&lt;/p&gt;
&lt;h2 id="setting-up-qtablewidget-with-data"&gt;Setting up QTableWidget with data&lt;/h2&gt;
&lt;p&gt;Let's start with the basics. A &lt;code&gt;QTableWidget&lt;/code&gt; is a table where each cell holds a &lt;code&gt;QTableWidgetItem&lt;/code&gt;. To populate it, you need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the number of rows and columns on the table.&lt;/li&gt;
&lt;li&gt;Loop through your data and create a &lt;code&gt;QTableWidgetItem&lt;/code&gt; for each value.&lt;/li&gt;
&lt;li&gt;Place each item in the correct row and column.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's a minimal example that displays a hardcoded list of tuples &amp;mdash; the same shape as your database results:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTableWidget,
    QTableWidgetItem, QVBoxLayout, QWidget,
)


data = [
    ("GR001", "ZTH", "2020-08-09 09:25", "PRG", "2020-08-09 10:55", "SX-DNH"),
    ("GR002", "PRG", "2020-08-09 18:30", "CFU", "2020-08-09 21:40", "SX-DNH"),
    ("GR003", "CFU", "2020-07-12 09:00", "EFL", "2020-07-12 09:20", "SX-DGA"),
]


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Flight Data")

        self.table = QTableWidget()
        self.table.setRowCount(len(data))
        self.table.setColumnCount(len(data[0]))

        for row_index, row_data in enumerate(data):
            for col_index, value in enumerate(row_data):
                self.table.setItem(
                    row_index, col_index, QTableWidgetItem(str(value))
                )

        layout = QVBoxLayout()
        layout.addWidget(self.table)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see your data displayed in a table. Each value is converted to a string with &lt;code&gt;str(value)&lt;/code&gt; &amp;mdash; this handles &lt;code&gt;datetime&lt;/code&gt; objects, numbers, and anything else that comes back from the database.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QTableWidget showing flight data in rows and columns" src="https://www.pythonguis.com/static/faq/display-table-with-qtablewidget/qtablewidget-flight-data-basic.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/display-table-with-qtablewidget/qtablewidget-flight-data-basic.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/display-table-with-qtablewidget/qtablewidget-flight-data-basic.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/display-table-with-qtablewidget/qtablewidget-flight-data-basic.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/display-table-with-qtablewidget/qtablewidget-flight-data-basic.png?tr=w-600 600w" loading="lazy" width="1308" height="368"/&gt;&lt;/p&gt;
&lt;h2 id="adding-column-headers"&gt;Adding column headers&lt;/h2&gt;
&lt;p&gt;A table without headers isn't very useful. You can set horizontal header labels using &lt;code&gt;setHorizontalHeaderLabels()&lt;/code&gt;. If you know your column names (from your database schema), you can pass them directly:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;headers = [
    "Flight ID", "Origin", "Departure",
    "Destination", "Arrival", "Aircraft",
]
self.table.setHorizontalHeaderLabels(headers)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you're working with a database cursor, you can also pull the column names dynamically from the cursor's &lt;code&gt;description&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;cursor.execute("SELECT * FROM Flight")
records = cursor.fetchall()
headers = [description[0] for description in cursor.description]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This means you don't need to hardcode column names &amp;mdash; useful when querying different tables.&lt;/p&gt;
&lt;h2 id="loading-data-on-a-button-click"&gt;Loading data on a button click&lt;/h2&gt;
&lt;p&gt;You mentioned wanting a button that loads the data when clicked. To do this, you create the &lt;code&gt;QTableWidget&lt;/code&gt; and the &lt;code&gt;QPushButton&lt;/code&gt; up front, then connect the button's &lt;code&gt;clicked&lt;/code&gt; signal to a method that populates the table.&lt;/p&gt;
&lt;p&gt;Here's a complete example showing this pattern. We're using hardcoded data here in place of a real database query, but you can swap in your cursor code directly:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import datetime
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTableWidget,
    QTableWidgetItem, QVBoxLayout, QWidget, QPushButton,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Flight Database Viewer")
        self.setFixedSize(800, 600)

        self.table = QTableWidget()
        self.load_button = QPushButton("Load Flight Data")
        self.load_button.clicked.connect(self.load_data)

        layout = QVBoxLayout()
        layout.addWidget(self.load_button)
        layout.addWidget(self.table)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def load_data(self):
        # Replace this with your actual database query:
        # cursor = cnx.cursor()
        # cursor.execute("SELECT * FROM Flight")
        # records = cursor.fetchall()
        # headers = [desc[0] for desc in cursor.description]

        # Simulated query results
        records = [
            (
                "GR001", "ZTH",
                datetime.datetime(2020, 8, 9, 9, 25),
                "PRG",
                datetime.datetime(2020, 8, 9, 10, 55),
                "SX-DNH",
            ),
            (
                "GR002", "PRG",
                datetime.datetime(2020, 8, 9, 18, 30),
                "CFU",
                datetime.datetime(2020, 8, 9, 21, 40),
                "SX-DNH",
            ),
            (
                "GR003", "CFU",
                datetime.datetime(2020, 7, 12, 9, 0),
                "EFL",
                datetime.datetime(2020, 7, 12, 9, 20),
                "SX-DGA",
            ),
            (
                "GR004", "CFU",
                datetime.datetime(2020, 8, 15, 11, 30),
                "ZTH",
                datetime.datetime(2020, 8, 15, 12, 0),
                "SX-DGF",
            ),
            (
                "GR005", "EFL",
                datetime.datetime(2020, 8, 20, 18, 5),
                "ZTH",
                datetime.datetime(2020, 8, 20, 18, 30),
                "SX-DVI",
            ),
        ]
        headers = [
            "Flight ID", "Origin", "Departure",
            "Destination", "Arrival", "Aircraft",
        ]

        self.populate_table(records, headers)

    def populate_table(self, records, headers):
        self.table.setRowCount(len(records))
        self.table.setColumnCount(len(headers))
        self.table.setHorizontalHeaderLabels(headers)

        for row_index, row_data in enumerate(records):
            for col_index, value in enumerate(row_data):
                item = QTableWidgetItem(str(value))
                self.table.setItem(row_index, col_index, item)

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


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you click the "Load Flight Data" button, the table fills with the flight records. The &lt;code&gt;populate_table&lt;/code&gt; method is separate from the loading logic, so you can reuse it for different queries &amp;mdash; just pass in different &lt;code&gt;records&lt;/code&gt; and &lt;code&gt;headers&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="formatting-datetime-values"&gt;Formatting datetime values&lt;/h2&gt;
&lt;p&gt;You'll notice that &lt;code&gt;str(datetime.datetime(2020, 8, 9, 9, 25))&lt;/code&gt; produces &lt;code&gt;"2020-08-09 09:25:00"&lt;/code&gt;. If you'd like a cleaner format, you can use &lt;code&gt;strftime&lt;/code&gt; to control how dates appear:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;for col_index, value in enumerate(row_data):
    if isinstance(value, datetime.datetime):
        display_value = value.strftime("%Y-%m-%d %H:%M")
    else:
        display_value = str(value)
    item = QTableWidgetItem(display_value)
    self.table.setItem(row_index, col_index, item)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This gives you &lt;code&gt;"2020-08-09 09:25"&lt;/code&gt; instead of &lt;code&gt;"2020-08-09 09:25:00"&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="reusing-the-table-for-multiple-queries"&gt;Reusing the table for multiple queries&lt;/h2&gt;
&lt;p&gt;Since you mentioned needing to display different tables, you can add multiple buttons &amp;mdash; each one calling the same &lt;code&gt;populate_table&lt;/code&gt; method with different data. Here's how that might look:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.flight_button = QPushButton("Load Flights")
self.flight_button.clicked.connect(self.load_flights)

self.aircraft_button = QPushButton("Load Aircraft")
self.aircraft_button.clicked.connect(self.load_aircraft)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each &lt;code&gt;load_*&lt;/code&gt; method runs its own query and calls &lt;code&gt;self.populate_table(records, headers)&lt;/code&gt;. Because &lt;code&gt;populate_table&lt;/code&gt; sets the row count, column count, and headers each time it's called, the table resets cleanly for each new dataset.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's the full application with two buttons to demonstrate switching between different datasets:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import datetime
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTableWidget,
    QTableWidgetItem, QVBoxLayout, QHBoxLayout,
    QWidget, QPushButton,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Database Table Viewer")
        self.setFixedSize(900, 600)

        self.table = QTableWidget()

        self.flight_button = QPushButton("Load Flights")
        self.flight_button.clicked.connect(self.load_flights)

        self.aircraft_button = QPushButton("Load Aircraft")
        self.aircraft_button.clicked.connect(self.load_aircraft)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.flight_button)
        button_layout.addWidget(self.aircraft_button)

        layout = QVBoxLayout()
        layout.addLayout(button_layout)
        layout.addWidget(self.table)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def load_flights(self):
        # Replace with: cursor.execute("SELECT * FROM Flight")
        records = [
            (
                "GR001", "ZTH",
                datetime.datetime(2020, 8, 9, 9, 25),
                "PRG",
                datetime.datetime(2020, 8, 9, 10, 55),
                "SX-DNH",
            ),
            (
                "GR002", "PRG",
                datetime.datetime(2020, 8, 9, 18, 30),
                "CFU",
                datetime.datetime(2020, 8, 9, 21, 40),
                "SX-DNH",
            ),
            (
                "GR003", "CFU",
                datetime.datetime(2020, 7, 12, 9, 0),
                "EFL",
                datetime.datetime(2020, 7, 12, 9, 20),
                "SX-DGA",
            ),
            (
                "GR004", "CFU",
                datetime.datetime(2020, 8, 15, 11, 30),
                "ZTH",
                datetime.datetime(2020, 8, 15, 12, 0),
                "SX-DGF",
            ),
            (
                "GR005", "EFL",
                datetime.datetime(2020, 8, 20, 18, 5),
                "ZTH",
                datetime.datetime(2020, 8, 20, 18, 30),
                "SX-DVI",
            ),
        ]
        headers = [
            "Flight ID", "Origin", "Departure",
            "Destination", "Arrival", "Aircraft",
        ]
        self.populate_table(records, headers)

    def load_aircraft(self):
        # Replace with: cursor.execute("SELECT * FROM Aircraft")
        records = [
            ("SX-DNH", "ATR 42-500", 48, "Olympic Air"),
            ("SX-DGA", "DHC-8-400", 78, "Olympic Air"),
            ("SX-DGF", "DHC-8-400", 78, "Olympic Air"),
            ("SX-DVI", "ATR 72-600", 72, "Olympic Air"),
            ("SX-DNF", "ATR 42-500", 48, "Olympic Air"),
        ]
        headers = ["Registration", "Type", "Capacity", "Operator"]
        self.populate_table(records, headers)

    def populate_table(self, records, headers):
        self.table.clear()
        self.table.setRowCount(len(records))
        self.table.setColumnCount(len(headers))
        self.table.setHorizontalHeaderLabels(headers)

        for row_index, row_data in enumerate(records):
            for col_index, value in enumerate(row_data):
                if isinstance(value, datetime.datetime):
                    display_value = value.strftime("%Y-%m-%d %H:%M")
                else:
                    display_value = str(value)
                item = QTableWidgetItem(display_value)
                self.table.setItem(row_index, col_index, item)

        self.table.resizeColumnsToContents()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Click "Load Flights" and you'll see the flight schedule. Click "Load Aircraft" and the same table switches to show aircraft data with different columns. The &lt;code&gt;populate_table&lt;/code&gt; method handles everything &amp;mdash; clearing the old data, setting new dimensions, and filling in the cells.&lt;/p&gt;
&lt;p&gt;To connect this to your actual database, replace the hardcoded &lt;code&gt;records&lt;/code&gt; lists with your cursor calls:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def load_flights(self):
    cursor = cnx.cursor()
    cursor.execute("SELECT * FROM Flight")
    records = cursor.fetchall()
    headers = [desc[0] for desc in cursor.description]
    self.populate_table(records, headers)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="going-further-with-qtableview-and-models"&gt;Going further with QTableView and models&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QTableWidget&lt;/code&gt; works well for straightforward cases like this. If your application grows more complex &amp;mdash; for example, if you need editable tables, sorting, filtering, or very large datasets &amp;mdash; you might want to look into &lt;code&gt;QTableView&lt;/code&gt; with a custom model. The model/view architecture gives you more control and better performance with large amounts of data.&lt;/p&gt;
&lt;p&gt;You can read more about that approach in these tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/modelview-architecture/"&gt;Model View Architecture &amp;mdash; Qt Documentation and PyQt6 Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;QTableView with NumPy and Pandas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For your university project though, &lt;code&gt;QTableWidget&lt;/code&gt; with the &lt;code&gt;populate_table&lt;/code&gt; pattern shown here should serve you well. It's simple, readable, and easy to extend as you add more tables to your application.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="python"/><category term="qtablewidget"/><category term="tables"/><category term="qt"/><category term="qt6"/></entry></feed>