Adam Adam - 1 month ago 24
Python Question

Virtual column in QTableView?

I'm started to learning Qt4 Model/View Programming and I have beginner question.

I have simple application which show sqlite table in

QTableView
:

class Model(QtSql.QSqlTableModel):
def __init__(self, parent=None):
super(Model, self).__init__(parent)
self.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange)

self.setTable("test")
self.select()

class App(QtGui.QMainWindow):
def __init__(self, model):
QtGui.QMainWindow.__init__(self)

self.ui = Ui_MainWindow()
self.ui.setupUi(self)

self.ui.tableView.setModel(model)

if __name__ == "__main__":
myDb = QtSql.QSqlDatabase.addDatabase("QSQLITE")
myDb.setDatabaseName("test.db")

if not myDb.open():
print 'FIXME'

model = Model()

app = QtGui.QApplication(sys.argv)
window = App(model)
window.show()
sys.exit(app.exec_())


Here how database looks like:

sqlite> create table test (a INTEGER, b INTEGER, c STRING);
sqlite> insert into test VALUES(1, 2, "xxx");
sqlite> insert into test VALUES(6, 7, "yyy");


So I'm getting something like:

+---+---+-----+
| a | b | c |
+---+---+-----+
| 1 | 2 | xxx |
+---+---+-----+
| 6 | 7 | yyy |
+---+---+-----+


Is it possible to modify
Model
to have in
QTableView
something like virtual column? For example something like:

+---+---+-----+-----+
| a | b | sum | c |
+---+---+-----+-----+
| 1 | 2 | 3 | xxx |
+---+---+-----+-----+
| 6 | 7 | 13 | yyy |
+---+---+-----+-----+


Or maybe I should do it in some other way?

Answer

Yes, you can do that. Although @BrtH's answer is relevant, models are tricky and it's easy to get lost. So I thought a more case in point example would be better.

Personally, I'd use a proxy model derived from QAbstractProxyModel. But, in your case reimplementing QSqlTableModel is also feasible. Below is an implementation for your goal. Note that, it's essential for you to know basics of Model/View methodology so that you understand what each method does.

class Model(QtSql.QSqlTableModel):
    def __init__(self, parent=None):
        super(Model, self).__init__(parent)
        self.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange)

        self.setTable("test")
        self.select()


    def columnCount(self, parent=QtCore.QModelIndex()):
        # this is probably obvious
        # since we are adding a virtual column, we need one more column
        return super(Model, self).columnCount()+1


    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.DisplayRole and index.column()==2:
            # 2nd column is our virtual column.
            # if we are there, we need to calculate and return the value
            # we take the first two columns, get the data, turn it to integer and sum them
            # [0] at the end is necessary because pyqt returns value and a bool
            # http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qvariant.html#toInt
            return sum(self.data(self.index(index.row(), i)).toInt()[0] for i in range(2))
        if index.column() > 2:
            # if we are past 2nd column, we need to shift it to left by one
            # to get the real value
            index = self.index(index.row(), index.column()-1)
        # get the value from base implementation
        return super(Model, self).data(index, role)


    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        # this is similar to `data`
        if section==2 and orientation==QtCore.Qt.Horizontal and role==QtCore.Qt.DisplayRole:
            return 'Sum'
        if section > 2 and orientation==QtCore.Qt.Horizontal:
            section -= 1
        return super(Model, self).headerData(section, orientation, role)


    def flags(self, index):
        # since 2nd column is virtual, it doesn't make sense for it to be Editable
        # other columns can be Editable (default for QSqlTableModel)
        if index.column()==2:
            return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
        return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable


    def setData(self, index, data, role):
        # similar to data.
        # we need to be careful when setting data (after edit)
        # if column is after 2, it is actually the column before that
        if index.column() > 2:
            index = self.index(index.row(), index.column()-1)
        return super(Model, self).setData(index, data, role)