Tutorial on Editing a QTableView

Thx, this is a great site. I also find the book very informative and structured. It’s a great help trying to learn pyqt for someone who doesn’t use python professionally.

In your further adding of content, would it be possible with a guide on how to edit the information in QTableView, how to save it back to the dataframe (pandas). Saving data back to external files are covered in your book.

Best regards, David

I have been trying for a few days to get edit mode to work with a QTableView using Pandas for the model (QAbstractTableModel). Having searched all over the internet although I found suggestions to implement the ‘flags()’ method but it doesn’t seem to work.

I purchased Martin’s excellent ‘Create GUI Applications - Pyside2’ book and was hoping to it would cover this but couldn’t find it. I basically have what is on pages 301-302 of the book. If anyone has the code to add to that to simply make the fields editable it would be a great help.

Thanks

Here you go @Vic_T – this example is using a list but should be easy to convert. The key is to implement a setData method responding to EditRole and also implement the flags method so you can let the view know the items are editable.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt


class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            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

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])
    
    def flags(self, index):
        return Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsEditable

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()
        

        self.table = QtWidgets.QTableView()

        data = [
          [1, 9, 2],
          [1, 0, -1],
          [3, 5, 2],
          [3, 3, 2],
          [5, 8, 9],
        ]

        self.model = TableModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app=QtWidgets.QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec_()

This didn’t make it into the latest edition of the book (had to stop somewhere!) but it will be in the next update in a few months.

2 Likes

@martin
Many thanks Martin - that was of great help :+1:

In the end I had the correct code, but my implementation of the flags() method within the model class was indented by one tab too many - I only spotted that by taking your example and adding my code one step at a time. Although I am a developer I am relatively new Python so I wasn’t aware of that pitfall. I didn’t have any error message but it does explain why my debug code wasn’t appearing in the console - lol.

I wish I had bought your book a month or so ago. You explain the PyQt5 / PySide2 concepts simply and concisely without complicating matters just for the sake of it. I could have effectively learnt in 3 days what has taken me a month to pick up looking at online tutorials and YouTube videos.

Cheers

Vic

Thanks @Vic_T that’s great to hear :+1:

I’ve been caught out by indentation on subclasses more than once myself – pretty confusing when it works but does the exact opposite of what is written!

It’s often class attributes that get lost in another function – this can be helped by always following the same structure for your classes, e.g.

class MyClass:
      <attribute definitions>
      <dunder methods>
      <other methods>

…at least then you can’t accidentally do…

class MyClass:

    def __init__(self):
        # something here

        attribute1 = 1

    def another_method(self):
        # ....

With methods the only advice I can give is –

  1. keep your methods short
  2. keep indentation to a minimum (see below)
  3. avoid having multiple blank lines in the method body (easily looks like a gap to slot something into).

…this can be quite a bit different from other languages, but you’ll get used to it.

For indentation the following examples should give some ideas. Starting from a function, then removing indentation (same result).

def another_method(x, y, z):
	if x > 5:
		return True
		
	else:
		return False
		
def another_method(x, y, z):
	if x > 5:
		return True
		
	return False

def another_method(x, y, z):
	return x > 5

The main path through a function should (generally) be unindented. This is the default path – check your conditions before and return if needed. Using return without a parameter is the same as falling out the bottom of a function (returns None).

def another_method(x, y, z):
	if x > 5:
	   if y > 3:
		   # blah blah blah
		   # blah blah blah
		   # blah blah blah

	
def another_method(x, y, z):
	if not (x > 5 and y > 3):
		return
		
   # blah blah blah
   # blah blah blah
   # blah blah blah

You can do this even if you have multiple steps, eg.

def another_method(x, y, z):
	if x > 5:
	   # blah blah blah
	   if y > 3:
		   # blah blah blah
		   # blah blah blah

	
def another_method(x, y, z):
	if not x > 5:
	    return
	
	# blah blah blah

	if not y > 3:
		return
		
   # blah blah blah
   # blah blah blah

It can feel a bit backwards – you’re checking for what you don’t want. Think of it as checking whether “something is wrong” and you need to pull the ejector seat.

The side effect of all this is that the last line of your function (or method) will not be indented so it’s harder to accidentally add something inside it.

This went on a bit, but I hope it was useful :slight_smile:

Many thanks again @martin. Some great tips there. The book really is an excellent resource to have around so it is good to know you plan to expand it further.

To give you another example of where it saved me a ton of time. I am launching a separate process from the new Qt5 GUI, and I needed to monitor the process by checking a date in a ‘handshake file’ and then providing feedback to the GUI. A perfect use case for threading.

Although I have used threading before in Java (and even interrupts in 6502 assembler many moons ago :slightly_smiling_face:), when I first looked at Python threading a while back it was quite daunting from what I read then. However I read through your section on Threading/Workers/Manager/etc. and it was extremely straight forward and a piece of cake to implement - a few hours from reading to implementation. It was really the best description/tutorial of threading I have come across, and you managed to cover the various approaches that are possible and pitfalls of each. :+1:

Hi

I have combined the tips above to make qtableview editable when using pandas model inspired by link below by adding a setData mehod as recommended ( tweaked for pandas dataframe)

https://learndataanalysis.org/display-pandas-dataframe-with-pyqt5-qtableview-widget/

It works, I can edit the table, but when I click in the table to edit, it clears the previous contents of the cell, which is annoying. Do you have a suggestion to what could be changed to prevent this?

class pandasModel(QAbstractTableModel):

def __init__(self, data):
    QAbstractTableModel.__init__(self)
    self._data = data

def rowCount(self, parent=None):
    return self._data.shape[0]

def columnCount(self, parnet=None):
    return self._data.shape[1]

def data(self, index, role=Qt.DisplayRole):
    if index.isValid():
        if role == Qt.DisplayRole:
            return str(self._data.iloc[index.row(), index.column()])
    return None

def setData(self, index, value, role):
    if role == Qt.EditRole:
        if index.column()==1:  # only allow 2 columns to be edited
            self._data.iloc[index.row(),index.column()] = value
            return True
    
def headerData(self, col, orientation, role):
    if orientation == Qt.Horizontal and role == Qt.DisplayRole:
        return self._data.columns[col]
    return None

def flags(self, index):
    return Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsEditable

Hi @martin, you mentioned an upcoming update to the book. What I’m looking to do is allow the user to both update existing data in a table and add to additional rows. Adding rows or updating data would fire a trigger allowing the program to deal with the updates and insertions. Is this kind of thing covered in the current version of the book? Thank a lot.

@jesmark I think you need add EditRole to your display method, e.g.

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            if role == Qt.DisplayRole or role == Qt.EditRole:
                return str(self._data.iloc[index.row(), index.column()])
        return None