In the Model-Views course, we covered Displaying Tabular Data in Qt ModelViews. This takes a data source, such as a list of lists, a NumPy array, or a Pandas DataFrame, and displays it in a QTableView. But often, displaying is just the first step—you also want your users to be able to add and edit the table, updating the underlying data object.
Reader Vic T asked:
I have been trying for a few days to get edit mode to work with a
QTableViewusing Pandas for the model viaQAbstractTableModel. Having searched all over the internet although, I found suggestions to implement theflags()method, but it doesn't seem to work.
This is correct. You need to implement the flags() method on your model to inform Qt that your model supports editing. To do this, your method needs to return the Qt.ItemIsEditable flag, which you or together (using the pipe | character) with the other flags.
In this tutorial, you'll learn how to make a QTableView editable by implementing flags(), setData(), and updating data() on your QAbstractTableModel. We'll cover complete working examples for Python lists, Pandas DataFrames, and NumPy arrays.
- Implement the flags() method to enable editing
- Implement setData() to write changes back to your data
- Update data() to show current values when editing
- Complete Example: Editable QTableView with a List of Lists
- Complete Example: Editable QTableView with a Pandas DataFrame
- Complete Example: Editable QTableView with a NumPy Array
- Summary
Implement the flags() method to enable editing
The first step to making your QTableView editable is to return the Qt.ItemIsEditable flag from your model's flags() method:
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
However, to get the editing working, you also need to implement a setData() method. This method is the model's interface between Qt and your data object. It takes care of making the changes to the data.
Remember, Qt views don't know anything about your data beyond what you tell them via the model. Likewise, they also don't know how to update your list, NumPy array, or DataFrame objects with the new data that has been provided. You need to handle that yourself!
Implement setData() to write changes back to your data
Below are some example setData() methods for lists of lists, NumPy, and Pandas. The only difference is how we index into the data object:
- List
- Pandas
- NumPy
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
return True
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(),index.column()] = value
return True
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data[index.row(), index.column()] = value
return True
Notice that we first need to check whether the role is Qt.EditRole to determine if an edit is currently being made. After making the edit, we return True to confirm this.
Update data() to show current values when editing
If you try the above on your model, you should be able to edit the values. However, you'll notice that when editing, it clears the current value of the cell — you have to start from an empty cell. To display the current value when editing, you need to modify the data() method to return the current value when the role is Qt.EditRole as well as when it is Qt.DisplayRole.
For example:
- List
- Pandas
- NumPy
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data[index.row()][index.column()]
return str(value)
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data[index.row(), index.column()]
return str(value)
That's it! You should now have a properly editable QTableView. Below are complete working examples for Python lists, NumPy arrays, and Pandas DataFrames using PyQt5.
Complete Example: Editable QTableView with a List of Lists
The following example uses a nested list of lists as a data source for an editable QTableView. This is the simplest approach if you don't need the additional features provided by NumPy or Pandas:
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView
class ListModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
# The following takes the first sub-list, and returns
# the length (only works if all rows are an equal length)
return len(self._data[0])
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data[index.row()][index.column()]
return str(value)
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
return True
return False
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.table = QTableView()
data = [
[1, 9, 2],
[1, 0, -1],
[3, 5, 2],
[3, 3, 2],
[5, 8, 9],
]
self.model = ListModel(data)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
Complete Example: Editable QTableView with a Pandas DataFrame
The following example uses a Pandas DataFrame as the data source for an editable QTableView, adding column headings from the DataFrame's column names:
import pandas as pd
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView
class PandasModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, parent=None):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
return True
return False
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.table = QTableView()
data = pd.DataFrame(
[
[1, 9, 2],
[1, 0, -1],
[3, 5, 2],
[3, 3, 2],
[5, 8, 9],
],
columns=["A", "B", "C"],
)
self.model = PandasModel(data)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
Complete Example: Editable QTableView with a NumPy Array
The following example uses a NumPy array as the data source for an editable QTableView. NumPy arrays enforce data types, so we must first coerce the value to an integer before setting it on the array. If the user enters something which isn't a valid integer (e.g. jdskfjdskjfndsf ), the int() call will throw a ValueError, which we catch. By returning False when this exception is thrown, we cancel the edit:
import numpy as np
from PyQt5.QtCore import QAbstractTableModel, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView
class NumPyModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data[index.row(), index.column()]
return str(value)
def setData(self, index, value, role):
if role == Qt.EditRole:
try:
value = int(value)
except ValueError:
return False
self._data[index.row(), index.column()] = value
return True
return False
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.table = QTableView()
data = np.array(
[
[1, 9, 2],
[1, 0, -1],
[3, 5, 2],
[3, 3, 2],
[5, 8, 9],
]
)
self.model = NumPyModel(data)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
Summary
To make a QTableView editable in PyQt5, you need to implement three things on your QAbstractTableModel:
flags()— ReturnQt.ItemIsEditablealongsideQt.ItemIsSelectableandQt.ItemIsEnabledto tell Qt that cells are editable.setData()— HandleQt.EditRoleto write the new value back to your underlying data source (list, Pandas DataFrame, or NumPy array).data()— Return the current value for bothQt.DisplayRoleandQt.EditRoleso the cell is pre-filled when the user starts editing.
With these three methods in place, your QTableView will support full inline editing of table data.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick
(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!