2
votes

I am using PYQT5. I have a QtableView widget which I use for quick data entry similar to a spreadsheet. Some columns are editable while others (labels etc) are not. I have achieved this quite simply by subclassing QItemDelegate.

What I would like to do is when a user tabs from an editable cell, it will skip any non-editable cell and go to the next editable cell. I think I need to examine a keypress event after editing somewhere and determine which column is next. Alternatively, when I land in a non-editable cell, I should move immediately to the next editable cell. My code is:

class Rates_sheet(QDialog, Ui_rates_sheet):
    """Displays rates for the next x days for quick changes"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        QDialog.__init__(self)
        self.ui = Ui_rates_sheet()
        self.ui.setupUi(self)
        self.ui.num_days.setValue(45)
        self.ui.get_avail.clicked.connect(self.make_rate_sheet)
        self.ui.publish_avail.clicked.connect(self.publish_rates)
        self.populate()

    def populate(self):
        self.rates_model = QSqlTableModel()
        self.rates_model.setTable("rates_sheet")
        self.rates_model.select()
        self.rates_view = self.ui.rates_grid
        self.rates_view.setModel(self.rates_model)
        self.rates_view.resizeColumnsToContents()
        self.rates_view.setSortingEnabled(True)
        self.rates_view.setColumnHidden(0, True)
        for x in range(7,12):
            self.rates_view.setColumnHidden(x, True)
        self.rates_view.horizontalHeader().moveSection(3,10)
        self.rates_view.horizontalHeader().moveSection(3,12)
        self.rates_view.horizontalHeader().moveSection(13,2)
        self.rates_view.setItemDelegate(Tmodel(self))
        self.rates_model.setHeaderData(1, Qt.Horizontal, "Date")
        self.rates_model.sort(1, Qt.AscendingOrder)



    def make_rate_sheet(self):
        pass

    def publish_rates(self):
        pass


class Tmodel(QItemDelegate):
    """Remplement rates_sheet table"""

    def __init__(self, parent=None):
        QItemDelegate.__init__(self)


    def createEditor(self, parent, option, index):
        if index.column() == 4:
            spinbox = QSpinBox(parent)
            spinbox.setRange(1,4)
            return spinbox
        elif index.column() in [5,6,11,12]:
            spinbox = QSpinBox(parent)
            spinbox.setRange(49,499)
            return spinbox

EDIT:

I have tried to reimplement QSqlTableModel to change the value in flags but end up with a runtime error:

class MySqlTableModel(QSqlTableModel):
def __init__(self):
    QSqlTableModel.__init__(self)

def flags(self, index):
    if index.isValid():
        if index.column() == 4:
            flags ^= 1
            return flags

The error I get is:

    File "f:\Dropbox\pms\main-5.py", line 2658, in <module>
  sys.exit(app.exec_())

builtins.TypeError: invalid result from MySqlTableModel.flags(), NoneType cannot be converted to PyQt5.QtCore.ItemFlags in this context

Now I am even more confused.

1
As far as I can recall you can implement the flags method of the model and drop out ItemIsSelectable option which may also help with tabbing over it. See doc.qt.io/qt-5/qabstractitemmodel.html#flags.maxik
I am really confused. I would have thought that whether an item was selectable or not would have been the job of the view. I am struggling to re-implement QSqlTableModel and make it work. Can you give more PYQT details?Dkellygb
Qt handles that kind of differently, see doc.qt.io/qt-5.6/model-view-programming.html. About PyQt I am the wrong guy for you, sry. But reading this and maybe some examples here doc.qt.io/qt-5.6/modelview.html should be adoptable to Python. May the force be with you.maxik

1 Answers

1
votes

After sweating over this for days, the answer is that you can successfully reimplement QsqlTableModel. Here is how I arrived at the answer.

First I saw recommendations on the net mostly in the QT forums for C++ that you should reimplement QsqlQueryModel instead of QsqlTableModel. But then you have to create the methods for setdata, data, rowcount, columncount, insertrows, deleterows not to mention flags yourself. This seemed like a lot of work and prone to error. I could not get it to work.

Thinking that all of the above was pointless and a lot of work, I found a code snippet from stackoverflow where someone was trying to use the dataChanged signal from QsqlQueryModel and they were recommended to overide QsqlTableModel. Finally I saw an example of how to do precisely what I was attempting. Therefore I updated my code as follows:

class Rates_sheet(QDialog, Ui_rates_sheet):
    """Displays rates for the next x days for quick changes"""

    def __init__(self):
        """Constructor"""
        QDialog.__init__(self)
        self.ui = Ui_rates_sheet()
        self.ui.setupUi(self)
        self.ui.num_days.setValue(45)
        self.ui.get_avail.clicked.connect(self.make_rate_sheet)
        self.ui.publish_avail.clicked.connect(self.publish_rates)
        self.populate()

    def populate(self):
        self.rates_model = MySqlTableModel()
        self.rates_model.setTable("rates_sheet")
        self.rates_model.select()
        self.rates_view = self.ui.rates_grid
        self.rates_view.setModel(self.rates_model)
        self.rates_view.resizeColumnsToContents()
        self.rates_view.setSortingEnabled(True)
        self.rates_view.setColumnHidden(0, True)
        self.rates_view.setItemDelegate(Tmodel(self))
        self.rates_model.setHeaderData(1, Qt.Horizontal, "Date")
        self.rates_model.sort(1, Qt.AscendingOrder)

    def make_rate_sheet(self):
        pass

    def publish_rates(self):
        pass

class MySqlTableModel(QSqlTableModel):
    """Overides QSqlTableModel to make columns not selectable"""

    def __init__(self):
        QSqlTableModel.__init__(self)

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole:
            value = value.strip() if type(value) == str else value

        return super(MySqlTableModel, self).setData(index, value, role)

    def flags(self, index):
        flags = super(MySqlTableModel, self).flags(index)

        if index.column() in (4, 5, 6, 11):
            flags |= Qt.ItemIsEditable
        else:
            flags &= Qt.ItemIsSelectable
        return flags

This was the solution. I am still testing but I haven't found any problems yet.