allenwjohnson11084 | 2021-05-14 14:39:58 UTC | #1
I am new to PyQt and having to really dig to understand how the objects fit together. I am probably overlooking some basic things at this point. I have started building my application, but keep tripping into things that don't work the way I want. Please be understanding if I am missing basic concepts. Doing this as retirement project, no longer being paid to code. The problem is that I want to add a row to a tableView and have it selected. This code is extracted from a more complex environment. What am I doing wrong?
import sys
from PyQt5 import QtCore as qtc
from PyQt5 import QtWidgets as qtw
"""
Model: based on QAbstractTableModel
View: QTable view
data: simple table of integers (3 columns)
Implemented insert and remove row methods.
I want an inserted table row to be selected
Problem: selectRow after insert of table row works only
if I have just deleted the next to last entry in the table
"""
# Model +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class TableModel(qtc.QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role):
if role == qtc.Qt.DisplayRole:
value = self._data[index.row()][index.column()]
return str(value)
def setData(self, index, value, role):
if role == qtc.Qt.EditRole:
self._data[index.row()][index.column()] = value
return True
def rowCount(self, index):
rows = len(self._data)
return rows
def columnCount(self, index):
cols = len(self._data[0])
return cols
def flags(self, index):
flags = qtc.Qt.ItemIsSelectable|qtc.Qt.ItemIsEnabled|qtc.Qt.ItemIsEditable
return flags
def removeRow(self, row):
self.rowsAboutToBeRemoved.emit(qtc.QModelIndex(), row, row)
self._data.pop(row)
self.rowsRemoved.emit(qtc.QModelIndex(), row, row)
def insertRow(self, data)->int:
row = len(self._data) + 1
self.rowsAboutToBeInserted.emit(qtc.QModelIndex(), row, row)
self._data.append(data)
self.rowsInserted.emit(qtc.QModelIndex(), row, row)
return row
# end class TableModel
class MainWindow(qtw.QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(qtc.QSize(350, 200))
self.cIndex = None # current selection
self.pIndex = None # previous selection
self.rowData = 10 # for generated adds
self.sm = None # selection model
# view +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
self.table = qtw.QTableView()
self.table.setSelectionMode(qtw.QAbstractItemView.SingleSelection)
self.table.setSelectionBehavior(qtw.QAbstractItemView.SelectRows)
# model +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
data = [ [1, 9, 2] ] # start with 1 row
self.model = TableModel(data)
self.table.setModel(self.model)
sm = self.table.selectionModel()
sm.currentRowChanged.connect(self.currentRowChanged)
self.sm = sm
# buttons ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
buttons = qtw.QHBoxLayout()
self.pbAdd = qtw.QPushButton('Add')
self.pbDelete = qtw.QPushButton('Delete')
self.pbAdd.clicked.connect(self.pbAddClicked)
self.pbDelete.clicked.connect(self.pbDeleteClicked)
# layout ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
vBox = qtw.QVBoxLayout()
vBox.addWidget(self.table)
buttons.addWidget(self.pbAdd)
buttons.addWidget(self.pbDelete)
vBox.addLayout(buttons)
# complete main window
self.widget = qtw.QWidget()
self.widget.setLayout(vBox)
self.setCentralWidget(self.widget)
self.show()
def currentRowChanged(self, cIndex, pIndex):
self.cIndex = cIndex
self.pIndex = pIndex
# add row
def pbAddClicked(self):
col = self.rowData
data = [col, col+1, col+2]
self.rowData += 10
row = self.model.insertRow(data)
# select the inserted row
# *** this only works when the next to last entry has been deleted ???
self.table.selectRow(row)
# remove row
def pbDeleteClicked(self):
row = self.cIndex.row()
self.model.removeRow(row)
# end class MainWindow
app=qtw.QApplication([])
window=MainWindow()
app.exec_()
allenwjohnson11084 | 2021-05-19 07:06:37 UTC | #2
I found my own very basic problem. It wasn't related to the interaction between the view, selection model and model. Which thanks to my problem and hours spent looking at the documentation and drawing diagrams I now understand much better. I was using an invalid index for my selectRow call. I would expect an error, but apparently the call is just ignored. No additional responses are needed.
martin | 2021-05-19 11:55:24 UTC | #3
Thanks for the update @allenwjohnson11084 glad you got it sorted!
When you say invalid index, was the index out of bounds or was index.isValid()
returning False
? There are quite a few methods that will return an "invalid index" if something isn't there, but it's not considered an error -- e.g. the parent of a top-level item in a tree will return an invalid parent index. In a lot of cases you can consider it the model equivalent of Python's None
-- a valid empty value.
Never miss an update
Enjoyed this? Subscribe to get new updates straight in your Inbox.
Not sure if it's relevant to the issue but when overriding methods on your subclass you should follow the same signature as the superclass, e.g. insertRow
should accept both the data and the location to insert it, and return a bool
for success/failure, rather than the index.
insertRow(int row, const QModelIndex &parent = QModelIndex())
Since selections are handled by the view, and inserts on the model, to do what you're trying to achieve (auto select inserted rows) I'd probably use the model .rowsInserted
and hook that into the selection model for the table view.
P.S. Apologies for the delay in replying I'm out of office (house being renovated) so checking in less frequently for the next 2 weeks.