Chapter 4 - Add a QTableView

Now that you have a QMainWindow, you can include a centralWidget to your interface. Usually, a QWidget is used to display data in most data-driven applications. Use a table view to display your data.

The first step is to add a horizontal layout with just a class:~PySide6.QtWidgets.QTableView. You can create a QTableView object and place it inside a class:~PySide6.QtWidgets.QHBoxLayout. Once the QWidget is properly built, pass the object to the QMainWindow as its central widget.

Remember that a QTableView needs a model to display information. In this case, you can use a class:~PySide6.QtCore.QAbstractTableModel instance.

Note

You could also use the default item model that comes with a class:~PySide6.QtWidgets.QTableWidget instead. QTableWidget is a convenience class that reduces your codebase considerably as you don’t need to implement a data model. However, it’s less flexible than a QTableView, as QTableWidget cannot be used with just any data. For more insight about Qt’s model-view framework, refer to the Model View Programming <https://doc.qt.io/qt-6/model-view-programming.html> documentation.

Implementing the model for your QTableView, allows you to: - set the headers, - manipulate the formats of the cell values (remember we have UTC time and float numbers), - set style properties like text alignment, - and even set color properties for the cell or its content.

To subclass the QAbstractTable, you must reimplement its virtual methods, rowCount(), columnCount(), and data(). This way, you can ensure that the data is handled properly. In addition, reimplement the headerData() method to provide the header information to the view.

Here is a script that implements the CustomTableModel:

 1from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex
 2from PySide6.QtGui import QColor
 3
 4
 5class CustomTableModel(QAbstractTableModel):
 6    def __init__(self, data=None):
 7        QAbstractTableModel.__init__(self)
 8        self.load_data(data)
 9
10    def load_data(self, data):
11        self.input_dates = data[0].values
12        self.input_magnitudes = data[1].values
13
14        self.column_count = 2
15        self.row_count = len(self.input_magnitudes)
16
17    def rowCount(self, parent=QModelIndex()):
18        return self.row_count
19
20    def columnCount(self, parent=QModelIndex()):
21        return self.column_count
22
23    def headerData(self, section, orientation, role):
24        if role != Qt.ItemDataRole.DisplayRole:
25            return None
26        if orientation == Qt.Orientation.Horizontal:
27            return ("Date", "Magnitude")[section]
28        else:
29            return f"{section}"
30
31    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
32        column = index.column()
33        row = index.row()
34
35        if role == Qt.ItemDataRole.DisplayRole:
36            if column == 0:
37                date = self.input_dates[row].toPython()
38                return str(date)[:-3]
39            elif column == 1:
40                magnitude = self.input_magnitudes[row]
41                return f"{magnitude:.2f}"
42        elif role == Qt.ItemDataRole.BackgroundRole:
43            return QColor(Qt.GlobalColor.white)
44        elif role == Qt.ItemDataRole.TextAlignmentRole:
45            return Qt.AlignmentFlag.AlignRight
46
47        return None

Now, create a QWidget that has a QTableView, and connect it to your CustomTableModel.

 1from PySide6.QtWidgets import (QHBoxLayout, QHeaderView, QSizePolicy,
 2                               QTableView, QWidget)
 3
 4from table_model import CustomTableModel
 5
 6
 7class Widget(QWidget):
 8    def __init__(self, data):
 9        super().__init__()
10
11        # Getting the Model
12        self.model = CustomTableModel(data)
13
14        # Creating a QTableView
15        self.table_view = QTableView()
16        self.table_view.setModel(self.model)
17
18        # QTableView Headers
19        self.horizontal_header = self.table_view.horizontalHeader()
20        self.vertical_header = self.table_view.verticalHeader()
21        self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
22        self.vertical_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
23        self.horizontal_header.setStretchLastSection(True)
24
25        # QWidget Layout
26        self.main_layout = QHBoxLayout()
27        size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
28
29        # Left layout
30        size.setHorizontalStretch(1)
31        self.table_view.setSizePolicy(size)
32        self.main_layout.addWidget(self.table_view)
33
34        # Set the layout to the QWidget
35        self.setLayout(self.main_layout)

You also need minor changes to the main_window.py and main.py from chapter 3 to include the Widget inside the MainWindow.

In the following snippets you’ll see those changes highlighted:

 1from PySide6.QtGui import QIcon, QKeySequence
 2from PySide6.QtWidgets import QMainWindow
 3
 4
 5class MainWindow(QMainWindow):
 6    def __init__(self, widget):
 7        super().__init__()
 8        self.setWindowTitle("Eartquakes information")
 9        self.setCentralWidget(widget)
10        # Menu
11        self.menu = self.menuBar()
12        file_menu = self.menu.addMenu("File")
13
14        # Exit QAction
15        file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
16                            "Exit", QKeySequence.StandardKey.Quit, self.close)
17
18        # Status Bar
19        self.status = self.statusBar()
20        self.status.showMessage("Data loaded and plotted")
21
22        # Window dimensions
23        geometry = self.screen().availableGeometry()
24        self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
 1import sys
 2import argparse
 3import pandas as pd
 4
 5from PySide6.QtCore import QDateTime, QTimeZone
 6from PySide6.QtWidgets import QApplication
 7from main_window import MainWindow
 8from main_widget import Widget
 9
10
11def transform_date(utc, timezone=None):
12    utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ"
13    new_date = QDateTime().fromString(utc, utc_fmt)
14    if timezone:
15        new_date.setTimeZone(timezone)
16    return new_date
17
18
19def read_data(fname):
20    # Read the CSV content
21    df = pd.read_csv(fname)
22
23    # Remove wrong magnitudes
24    df = df.drop(df[df.mag < 0].index)
25    magnitudes = df["mag"]
26
27    # My local timezone
28    timezone = QTimeZone(b"Europe/Berlin")
29
30    # Get timestamp transformed to our timezone
31    times = df["time"].apply(lambda x: transform_date(x, timezone))
32
33    return times, magnitudes
34
35
36if __name__ == "__main__":
37    options = argparse.ArgumentParser()
38    options.add_argument("-f", "--file", type=str, required=True)
39    args = options.parse_args()
40    data = read_data(args.file)
41
42    # Qt Application
43    app = QApplication(sys.argv)
44
45    widget = Widget(data)
46    window = MainWindow(widget)
47    window.show()
48
49    sys.exit(app.exec())