Read-Only QAbstractTableModel Subclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import sys
import csv

from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PySide6.QtWidgets import (QApplication,
    QWidget, QTableView, QVBoxLayout)
from PySide6.QtTest import QAbstractItemModelTester


# 1. Create a QAbstractTableModel subclass.
#    We read the data from a csv file using Python's csv.reader
#    Each row in a reader object is a list making self.csv_data
#    a two-dimensional list suitable for use
#    with QAbstractTableModel.

class CsvModel(QAbstractTableModel):
    
    def __init__(self, source, parent=None):
        
        super().__init__(parent)
        
        self.csv_data = []
        with open(source) as csv_file:
            reader = csv.reader(csv_file)
            self.header = next(reader)
            for row in reader:
                self.csv_data.append(row)

    # 2. Implement the rowCount() and columnCount() methods

    def rowCount(self, parent=QModelIndex()):
        return len(self.csv_data)

    def columnCount(self, parent=QModelIndex()):
        return 4
    
    # 3. Implement the data() method
    
    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self.csv_data[index.row()][index.column()]
        
    # QTableView can have a header
    # but implementing headerData() is still optional.

    def headerData(self, section, orientation, role):
        if orientation == Qt.Orientation.Horizontal:
            if role == Qt.ItemDataRole.DisplayRole:
                return self.header[section]

class Window(QWidget):
    
    def __init__(self):

        super().__init__()

        layout = QVBoxLayout()
        self.setLayout(layout)
        
        # 4. Use the model:
        #    Create a model instance, create a view instance
        #    and and use view.setModel() to connect them.

        model = CsvModel('data.csv')
        QAbstractItemModelTester(model)
        
        view = QTableView()
        view.setModel(model)
        view.resizeColumnsToContents()
        layout.addWidget(view)


if __name__ == '__main__':

    app = QApplication(sys.argv)

    main_window = Window()
    main_window.show()

    sys.exit(app.exec())


QAbstractTableModel lets you make your two-dimensional data available to Qt views. Just as with QAbstractListModel it can be read-only, editable and resizable, depending on the base class methods you choose to implement. To use a read-only QAbstractTableMOdel object in your program

  1. Create a subclass of QAbstractTableModel named CsvModel and make external data available to it. We read the data from the same csv file as in the QAbstractListModel example, data.csv using Python’s csv reader and store it in a member variable named self.csv_data. csv_data is a list and csv reader’s rows are also lists which effectively makes csv_data a two-dimensional list suitable for use with QAbstractTableModel subclasses

  2. Implement the rowCount() and the columnCount() methods. rowCount() is the length of the self.csv_data list and never changes since the model is read-only. columnCount() is hard-coded to return 4, the number of columns in the csv file.

  3. Implement the data() method. data() expects two arguments: index, a QModelIndex instance and role, a member of the DisplayRole enumeration. In the data() body we check if role is equal to DisplayRole and if it is we return the data that the index points to. Note that we need to use both index.row() and index.column() as the data has two dimensions. With this you have a functional QAbstracttableModel subclass.

  4. In your main class (Window), create a CsvModel object, create a QTableView object and set its model using QTableView()setModel()

QAbstractTableModel subclasses are commonly used with QTableView but not necessarily so. QTableView is able to display table headers using the header data you provide to it in the model headerData() method but implementing headerData() is still not mandatory.