Editable 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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
#    same as in the read-only subclass example.

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 rowCount(), columnCount() and data()

    def rowCount(self, parent=QModelIndex()):
        return len(self.csv_data)
    
    def columnCount(self, parent=QModelIndex()):
        return 4
    
    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self.csv_data[index.row()][index.column()]

    # 3. Implement setData()
    
    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            if self.csv_data[index.row()][index.column()] != value:
                self.csv_data[index.row()][index.column()] = value
                self.dataChanged.emit(index, index)
                return True
            return False
        return False
    
    # 4. Implement flags()
    
    def flags(self, index):
        flags = Qt.ItemFlags.ItemIsSelectable | \
            Qt.ItemFlags.ItemIsEnabled | \
            Qt.ItemFlags.ItemIsEditable
        return flags

    # QTableView can have a header

    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)
        
        # 5. Use the model
        #    Create the model, create the view
        #    and assign the model to the view.

        model = CsvModel('data.csv')
        QAbstractItemModelTester(model)
        view = QTableView()
        view.setModel(model)
        view.resizeColumnsToContents()
        layout.addWidget(view)
        
        model.dataChanged.connect(self.on_data_changed)
        
    def on_data_changed(self, topLeft, bottomRight, roles):
        print(f'Model changed, r: {topLeft.row()}, c: {topLeft.column()}')
        data = topLeft.model().data(topLeft, Qt.ItemDataRole.DisplayRole)
        print(f'Data: {data}')


if __name__ == '__main__':

    app = QApplication(sys.argv)

    main_window = Window()
    main_window.show()

    sys.exit(app.exec())


In the previous example we created a read-only QAbstractTableModel subclass by implementing three methods: rowCount(), columnCount() and data(). To make a model editable you need to implement two more: setData() and flags(). To use an editable table model

  1. Create a subclass of QAbstractTableModel

  2. Implement its rowCount(), columnCount() and data() methods. The implementation is the same as in the read-only model example.

  3. Implement the setData() method. setData() accepts three arguments: index, a QModelIndex object that you will use to get the coordinates of the data point to be changed, value which is the new data value and role, one of the Qt.ItemDataRole enumeration members, in most cases EditRole. In the method body check if role is equal to EditRole, check if the new data is actually different from the current data, change the data and emit the dataChanged() signal.

  4. Implement the flags() method. In this method you return the item flags given its index. In the example all items (ie. fields) are flagged as selectable, enabled and editable. This does not have to be the case. For instance, if you had a model based on a SQL table you would flag the primary key fields as selectable only, making only the primary keys read-only.

  5. Then, in the main class, create a model instance, create a QTableView instance and use QTableView.setModel() to connect the two.