pyqt signal not emitted in QAbstractTableModel - python

So i'm writing an application and using QTableView with QAbstractTableModel to display my data. In each row a checkbox is placed in the first column and when it is checked or unchecked, i want a pyqtsignal CheckBoxValue be emitted to MyTable. But it seems that the connected function self.checkboxchecked in MyTable is not called. Although I used to use pyqtSignal several times without any issue, i'm stuck here and couldn't solve it out.
Thank you all for spending time taking care of my question!
import operator # used for sorting
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtGui, QtCore
class MyTable(QWidget):
Checkboxcheckvalue = pyqtSignal(bool)
Rowselected = pyqtSignal(int)
doubleClicked = pyqtSignal(int)
def __init__(self, *args):
QWidget.__init__(self, *args)
# setGeometry(x_pos, y_pos, width, height)
# self.setGeometry(70, 150, 1326, 582)
self.dataList = []
self.header = []
self.currentrow = []
self.setWindowTitle("Click on the header to sort table")
self.table_model = MyTableModel(self, self.dataList, self.header)
self.table_view = QTableView()
#self.table_view.setSelectionMode(QAbstractItemView.SingleSelection)
self.table_view.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.table_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
# bind cell click to a method reference
self.table_view.clicked.connect(self.showSelection)
self.table_view.clicked.connect(self.selectRow)
self.table_view.doubleClicked.connect(self.doubleclickedaction)
self.table_model.CheckBoxValue.connect(self.checkboxchecked)
self.table_view.setModel(self.table_model)
# enable sorting
self.table_view.setSortingEnabled(True)
layout = QVBoxLayout(self)
layout.addWidget(self.table_view)
self.setLayout(layout)
def update_model(self):
self.table_model = MyTableModel(self, self.dataList, self.header)
self.table_view.setModel(self.table_model)
self.table_view.update()
def findLabelName(self,rowindex):
return self.dataList[rowindex][-1]
def doubleclickedaction(self,index):
self.doubleClicked.emit(index.row())
self.currentrow = index.row()
print ('doubleclicked',self.findLabelName(index.row()))
def checkboxchecked(self,value):
print ('table checkboxchecked',self.currentrow,value)
# self.currentrow = index.row()
if value == True:
self.Checkboxcheckvalue.emit(True)
else:
self.Checkboxcheckvalue.emit(False)
def selectedLabel(self,index):
return self.findLabelName(index.row())
def showSelection(self, item):
cellContent = item.data()
# print(cellContent) # test
sf = "You clicked on {}".format(cellContent)
# display in title bar for convenience
self.setWindowTitle(sf)
def selectRow(self, index):
self.Rowselected.emit(index.row())
self.currentrow = index.row()
print("current row is %d", index.row())
pass
class MyTableModel(QAbstractTableModel):
"""
keep the method names
they are an integral part of the model
"""
CheckBoxValue = pyqtSignal(bool)
def __init__(self, parent, mylist, header, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.mylist = mylist
self.header = header
def setDataList(self, mylist):
self.mylist = mylist
self.layoutAboutToBeChanged.emit()
self.dataChanged.emit(self.createIndex(0, 0), self.createIndex(self.rowCount(0), self.columnCount(0)))
self.layoutChanged.emit()
def rowCount(self, parent):
if self.mylist != []:
return len(self.mylist)
else:
return 0
def columnCount(self, parent):
if self.mylist != []:
return len(self.mylist[0])-1
else:
return 0
def data(self, index, role):
if not index.isValid():
return None
if (index.column() == 0):
value = self.mylist[index.row()][index.column()].text()
else:
value = self.mylist[index.row()][index.column()]
if role == QtCore.Qt.EditRole:
return value
elif role == QtCore.Qt.DisplayRole:
return value
elif role == QtCore.Qt.CheckStateRole:
if index.column() == 0:
if self.mylist[index.row()][index.column()].isChecked():
return QtCore.Qt.Checked
else:
return QtCore.Qt.Unchecked
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.header[col]
return None
def sort(self, col, order):
"""sort table by given column number col"""
if col != 0:
pass
else:
col = -1
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.mylist = sorted(self.mylist, key=operator.itemgetter(col))
if order == Qt.DescendingOrder:
self.mylist.reverse()
self.emit(SIGNAL("layoutChanged()"))
print 'sorted'
def flags(self, index):
if not index.isValid():
return None
if index.column() == 0:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable
else:
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role):
if not index.isValid():
return False
if role == QtCore.Qt.CheckStateRole and index.column() == 0:
if value == QtCore.Qt.Checked:
self.mylist[index.row()][index.column()].setChecked(True)
print('checked',index.row())
self.CheckBoxValue.emit(True)
else:
self.mylist[index.row()][index.column()].setChecked(False)
print('unchecked',index.row())
self.CheckBoxValue.emit(False)
self.dataChanged.emit(index, index)
return True
if __name__ == '__main__':
app = QApplication([])
header = ['name', 'type', 'segment', 'exit', 'entry']
dataList = [
[QtGui.QCheckBox('line9'), 'line', '058176', '01', '1705','line9'],
[QtGui.QCheckBox('line3'), 'line', '058176', '02', '1705','line3'],
[QtGui.QCheckBox('line6'), 'line', '058176', '03', '1705','line6'],
[QtGui.QCheckBox('line1'), 'line', '058176', '04', '1705','line'],
[QtGui.QCheckBox('line4'), 'line', '058176', '01', '1705','line4'],
[QtGui.QCheckBox('area4'), 'area', '058176', '02', '1705','area4'],
[QtGui.QCheckBox('area2'), 'area', '058176', '02', '1705','area2'],
[QtGui.QCheckBox('area8'), 'area', '058176', '01', '1705','area8'],
]
win = MyTable()
win.dataList = dataList
win.header = header
win.update_model()
win.show()
app.exec_()

In your case you have created a table_model in the MyTable constructor, and with that object you have made the connection, but later in the update_model method you have created a new model so the previous model has been deleted and its connections as well.
def update_model(self):
self.table_model = MyTableModel(self, self.dataList, self.header)
self.table_view.setModel(self.table_model)
self.table_model.CheckBoxValue.connect(self.checkboxchecked)
self.table_view.update()
Although I prefer to update the model, instead of creating a new model for it, I would create a method to update the data:
class MyTableModel(QAbstractTableModel):
"""
keep the method names
they are an integral part of the model
"""
CheckBoxValue = pyqtSignal(bool)
def __init__(self, parent, mylist, header, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.mylist = mylist
self.header = header
def update_model(self, mylist, header):
self.beginResetModel()
self.mylist = mylist
self.header = header
self.endResetModel()
[...]
and in the update_model of the Table:
class MyTable(QWidget):
[...]
def update_model(self):
self.table_model.update_model(self.dataList, self.header)

Related

Add a checkbox to text in a QTableView cell using delegate

I wish to add a checkbox to a cell with text in my QTableView:
when a cell is not activated we should see a text and a greyed out checkbox, like this
when we open a delegate editor, we should be able to edit text and to change state of a checkbox
Just for info - further I was planning to do next: when a checkbox is activated - a cell will reject signal for updating text in it, and if checkbox is unchecked - text will be updated when special signal is emitted.
I understand that I should subclass QStyledItemDelegate and re-implement paint method, but as far as I am a beginner I found it difficult to figure out this task.
You can use this example to write and test a code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import pickle
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.setCentralWidget(self.table)
self.list = ['item_1', 'item_2','item_3']
self.data = [
[1, 'Blocks γ=500 GOST 31359-2007', self.list[0], 0.18, 0.22],
[2, 'Blocks γ=600 GOST 31359-2008', self.list[0], 0.25, 0.27],
[3, 'Insulation', self.list[0], 0.041, 0.042],
[3, 'Insulation', self.list[0], 0.041, 0.042]
]
self.model = Materials(self.data)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.setSelectionMode(self.table.SingleSelection)
for row in range(len(self.model.materials)):
index = self.table.model().index(row,2)
self.table.setIndexWidget(index, self.setting_combobox(index))
def setting_combobox(self, index):
widget = QtWidgets.QComboBox()
list = self.list
widget.addItems(list)
widget.setCurrentIndex(0)
widget.currentTextChanged.connect(lambda value: self.model.setData(index, value))
return widget
class Materials(QtCore.QAbstractTableModel):
def __init__(self, materials = [[]], parent = None):
super(Materials, self).__init__()
self.materials = materials
def rowCount(self, parent):
return len(self.materials)
def columnCount(self, parent):
return len(self.materials[0])
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.materials[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.materials[row][column]
return value
if role == QtCore.Qt.FontRole:
if index.column() == 0:
boldfont = QtGui.QFont()
boldfont.setBold(True)
return boldfont
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.materials[row][column] = value
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
A possible solution is to enable the role Qt.CheckStateRole and modify the position of the checkbox using the delegate. For this, the model must also be modified to store the state of the checkbox.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class CustomDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
model = index.model()
model.setData(index, QtCore.Qt.Unchecked, QtCore.Qt.CheckStateRole)
super().initStyleOption(option, index)
option.direction = QtCore.Qt.RightToLeft
option.displayAlignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.setCentralWidget(self.table)
self.list = ["item_1", "item_2", "item_3"]
self.data = [
[1, "Blocks γ=500 GOST 31359-2007", self.list[0], 0.18, 0.22],
[2, "Blocks γ=600 GOST 31359-2008", self.list[0], 0.25, 0.27],
[3, "Insulation", self.list[0], 0.041, 0.042],
[3, "Insulation", self.list[0], 0.041, 0.042],
]
self.model = Materials(self.data)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.setSelectionMode(self.table.SingleSelection)
for row in range(len(self.model.materials)):
index = self.table.model().index(row, 2)
self.table.setIndexWidget(index, self.setting_combobox(index))
delegate = CustomDelegate(self.table)
self.table.setItemDelegateForColumn(4, delegate)
self.resize(640, 480)
def setting_combobox(self, index):
widget = QtWidgets.QComboBox()
list = self.list
widget.addItems(list)
widget.setCurrentIndex(0)
widget.currentTextChanged.connect(
lambda value: self.model.setData(index, value)
)
return widget
class Materials(QtCore.QAbstractTableModel):
def __init__(self, materials=[[]], parent=None):
super(Materials, self).__init__()
self.materials = materials
self.check_states = dict()
def rowCount(self, parent):
return len(self.materials)
def columnCount(self, parent):
return len(self.materials[0])
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.materials[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.materials[row][column]
return value
if role == QtCore.Qt.FontRole:
if index.column() == 0:
boldfont = QtGui.QFont()
boldfont.setBold(True)
return boldfont
if role == QtCore.Qt.CheckStateRole:
value = self.check_states.get(QtCore.QPersistentModelIndex(index))
if value is not None:
return value
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.materials[row][column] = value
self.dataChanged.emit(index, index, (role,))
return True
if role == QtCore.Qt.CheckStateRole:
self.check_states[QtCore.QPersistentModelIndex(index)] = value
self.dataChanged.emit(index, index, (role,))
return True
return False
def flags(self, index):
return (
QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsEnabled
| QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsUserCheckable
)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())

Filter data from dataChanged signal in PyQt5

I wish to filter change made to QModelIndex at specific column (for example column 1), and that a change was made to CheckStateRole, not to DisplayRole. I need to filter just the fact that CheckStateRole was changed (ignoring which value it has now). Is it possible?
self.model.dataChanged.connect(lambda x: self.foo(x))
def foo(self, x):
if x.column() == 1 and ???????:
...
I tried x.data(QtCore.Qt.CheckStateRole) == 0, but that's not what I need
here's an example of a code to test:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
self.setCentralWidget(self.table)
self.data = [
['id_1', 'Blocks γ=500 GOST 31359-2007', 0.18, 0.22],
['id_2', 'Blocks γ=600 GOST 31359-2008', 0.25, 0.27],
['id_3', 'Insulation', 0.041, 0.042],
['id_4', 'Insulation', 0.041, 0.042]
]
self.model = Materials(self.data)
self.table.setModel(self.model)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.setSelectionMode(self.table.SingleSelection)
for row in self.model.materials:
key = row[0]
self.model.check_states[key] = 0
self.model.dataChanged.connect(lambda: print(self.model.materials))
self.model.dataChanged.connect(lambda: print(self.model.check_states))
self.model.dataChanged.connect(lambda x: self.foo(x)) ######################
def foo(self, x):
if x.column() == 1 #and ???????????????:
print('success')
class Materials(QtCore.QAbstractTableModel):
def __init__(self, materials = [[]], parent = None):
super(Materials, self).__init__()
self.materials = materials
self.check_states = {}
def rowCount(self, parent):
return len(self.materials)
def columnCount(self, parent):
return len(self.materials[0])
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.materials[row][column]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
value = self.materials[row][column]
return value
if role == QtCore.Qt.CheckStateRole:
if index.column() == 1:
row = index.row()
value = self.check_states.get(self.materials[row][0])
return value
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.materials[row][column] = value
self.dataChanged.emit(index, index)
return True
if role == QtCore.Qt.CheckStateRole:
if index.column() == 1:
row = index.row()
self.check_states[self.materials[row][0]] = value
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
The aim is to print 'success' not only when a change was in column 1, but when user changes checkbox status.

Single selecting row in QTableView activates column edit

How can I make the second column actively in edit mode anytime a row is selected in the tableview as seen in this gif below? I'm trying to recreate this in python/pyside.
Ideally I would like to use some sort of item delegate so i could easily handle the keyPressEvents in the columns cell and add the custom (X) clear button. However I'm not sure how to use delegates like this when using ItemModels. So any help in making this task achievable is appreciated.
class ExampleDelegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
line_edit = QtGui.QLineEdit(parent)
return line_edit
Here is my code and a screenshot:
import os, sys
from PySide import QtGui, QtCore
class HotkeyItem():
def __init__(self, command, shortcut):
self.command = command
self.shortcut = shortcut
class HotkeysModel(QtCore.QAbstractTableModel):
def __init__(self):
super(HotkeysModel, self).__init__()
self.items = []
self.headers = ['Command','Hotkey']
def clear(self):
self.beginResetModel()
self.items = []
self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.items)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
cnt = len(self.headers)
if section < cnt:
return self.headers[section]
return None
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.headers)
def index(self, row, column, parent=QtCore.QModelIndex()):
return self.createIndex(row, column, parent)
def addItem(self, item):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items.append(item)
self.endInsertRows()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
row = index.row()
col = index.column()
if 0 <= row < self.rowCount():
item = self.items[row]
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'command', 'N/A')
elif col == 1:
return getattr(item, 'shortcut', '')
if role == QtCore.Qt.BackgroundRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
return QtGui.QBrush(QtGui.QColor(255, 50, 50, 255))
elif role == QtCore.Qt.FontRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
fnt = QtGui.QFont()
fnt.setBold(True)
fnt.setItalic(True)
return fnt
return None
def flags(self, index):
if not index.isValid():
return 0
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
class Example(QtGui.QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.resize(600, 400)
model = HotkeysModel()
proxyModel = QtGui.QSortFilterProxyModel()
proxyModel.setFilterKeyColumn(0)
proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxyModel.setSourceModel(model)
self.uiView = QtGui.QTableView()
self.uiView.setSortingEnabled(True)
self.uiView.setModel(proxyModel)
self.uiView.setAlternatingRowColors(True)
self.uiView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.uiView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.uiView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.uiView.verticalHeader().hide()
self.uiView.horizontalHeader().show()
lay = QtGui.QVBoxLayout()
lay.addWidget(self.uiView)
self.setLayout(lay)
self.populate()
# connections
selection = self.uiView.selectionModel()
selection.currentRowChanged.connect(self.selection_changed)
# ui->tableView->setCurrentIndex(index);
# ui->tableView->edit(index);
def selection_changed(self, index):
if index.isValid():
row = index.row()
self.uiView.setCurrentIndex(index)
self.uiView.edit(index)
def populate(self):
model = self.uiView.model().sourceModel()
model.clear()
items = [
HotkeyItem(command='Save', shortcut='Ctrl+S'),
HotkeyItem(command='Open', shortcut='Ctrl+O'),
HotkeyItem(command='Close', shortcut='Ctrl+Q'),
HotkeyItem(command='Align Top', shortcut=''),
HotkeyItem(command='Align Bottom', shortcut=''),
HotkeyItem(command='Align Left', shortcut=''),
HotkeyItem(command='Align Right', shortcut=''),
HotkeyItem(command='Align Center', shortcut='Ctrl+O')
]
for x in items:
model.addItem(x)
self.uiView.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiView.resizeColumnsToContents()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Part of the problem is that for an item to be editable, it must have the flag QtCore.Qt.ItemIsEditable activated. The other part is that the index that passes selection_changed can be from the item in the first column and not from the second, so using that index you should get the index from the second column.
In Qt5 the clear button is already implemented and it is only activated using setClearButtonEnabled(True) and the icon is changed using qss, but in the case of Qt4 it does not exist, so it must be created to use this answer.
Finally you must also implement the setData() method.
import os
import sys
from PySide import QtGui, QtCore
class LineEdit(QtGui.QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
btnSize = self.sizeHint().height() - 5
self.clearButton = QtGui.QToolButton(self)
icon = QtGui.QIcon("clear.png")
self.clearButton.setIcon(icon)
self.clearButton.setCursor(QtCore.Qt.ArrowCursor)
self.clearButton.setStyleSheet("QToolButton { border: none; padding: 2px}")
self.clearButton.setFixedSize(btnSize, btnSize)
self.clearButton.hide()
frameWidth = self.style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
self.setStyleSheet("QLineEdit{{ padding-right: {}px }}".format(btnSize - frameWidth))
self.setMinimumHeight(self.sizeHint().height())
self.clearButton.clicked.connect(self.clear)
self.textChanged.connect(self.onTextChanged)
def resizeEvent(self, event):
frameWidth = self.style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
self.clearButton.move(self.width() - self.clearButton.width() - frameWidth, 0)
def onTextChanged(self, text):
self.clearButton.setVisible(text != "")
class Delegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = LineEdit(parent)
font = index.data(QtCore.Qt.FontRole)
editor.setFont(font)
return editor
def setEditorData(self, editor, index):
text = index.data()
editor.setText(text)
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
class HotkeyItem():
def __init__(self, command, shortcut):
self.command = command
self.shortcut = shortcut
class HotkeysModel(QtCore.QAbstractTableModel):
def __init__(self):
super(HotkeysModel, self).__init__()
self.items = []
self.headers = ['Command','Hotkey']
def clear(self):
self.beginResetModel()
self.items = []
self.endResetModel()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.items)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal:
if role == QtCore.Qt.DisplayRole:
cnt = len(self.headers)
if section < cnt:
return self.headers[section]
return None
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self.headers)
def addItem(self, item):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self.items.append(item)
self.endInsertRows()
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
row = index.row()
col = index.column()
if 0 <= row < self.rowCount():
item = self.items[row]
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'command', 'N/A')
elif col == 1:
return getattr(item, 'shortcut', '')
if role == QtCore.Qt.BackgroundRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
return QtGui.QBrush(QtGui.QColor(255, 50, 50, 255))
elif role == QtCore.Qt.FontRole:
shortcuts = filter(None, [x.shortcut for x in self.items])
dups = shortcuts.count(getattr(item, 'shortcut', ''))
if dups > 1:
fnt = QtGui.QFont()
fnt.setBold(True)
fnt.setItalic(True)
return fnt
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.isValid():
row = index.row()
col = index.column()
if 0 <= row < self.rowCount() and 0 <= col < self.columnCount():
it = self.items[row]
if col == 0:
it.command = value
elif col == 1:
it.shortcut = value
return True
return False
def flags(self, index):
fl = QtCore.Qt.NoItemFlags
if index.isValid():
fl |= QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if index.column() == 1:
fl |= QtCore.Qt.ItemIsEditable
return fl
class Example(QtGui.QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.resize(600, 400)
model = HotkeysModel()
proxyModel = QtGui.QSortFilterProxyModel()
proxyModel.setFilterKeyColumn(0)
proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxyModel.setSourceModel(model)
self.uiView = QtGui.QTableView()
self.uiView.setSortingEnabled(True)
self.uiView.setModel(proxyModel)
self.uiView.setAlternatingRowColors(True)
delegate = Delegate(self)
self.uiView.setItemDelegateForColumn(1, delegate)
self.uiView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.uiView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.uiView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.uiView.verticalHeader().hide()
self.uiView.horizontalHeader().show()
lay = QtGui.QVBoxLayout()
lay.addWidget(self.uiView)
self.setLayout(lay)
self.populate()
# connections
selection = self.uiView.selectionModel()
selection.currentChanged.connect(self.openEditor)
self.uiView.clicked.connect(self.openEditor)
def openEditor(self, index):
if index.isValid():
ix = index.sibling(index.row(), 1)
self.uiView.setCurrentIndex(ix)
self.uiView.edit(ix)
def populate(self):
model = self.uiView.model().sourceModel()
model.clear()
items = [
HotkeyItem(command='Save', shortcut='Ctrl+S'),
HotkeyItem(command='Open', shortcut='Ctrl+O'),
HotkeyItem(command='Close', shortcut='Ctrl+Q'),
HotkeyItem(command='Align Top', shortcut=''),
HotkeyItem(command='Align Bottom', shortcut=''),
HotkeyItem(command='Align Left', shortcut=''),
HotkeyItem(command='Align Right', shortcut=''),
HotkeyItem(command='Align Center', shortcut='Ctrl+O')
]
for x in items:
model.addItem(x)
self.uiView.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.uiView.resizeColumnsToContents()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

How to programmatically edit QAbstractTableModel?

Note: I don't believe this is a duplicate of How to programmatically change/update data in Python PyQt4 TableView? as I don't want to insert new rows. I want to edit the table data.
I have changed my code below. The table holds integers in columns titled "2" and "3". When changing the value of the QSpinBox, I want the numbers in the table to get multiplied by the value from the QSpinBox programatically.
Question: How can I programmatically multiply the values in these columns by the spinbox multiplier value?
Note: if the UI doesn't exist, the model still needs to work (without this multiplier feature). Meaning; I believe that the spinbox must initiate model data edit.
import sys
try:
from PySide2 import QtWidgets
except ImportError:
from PyQt5 import QtWidgets
try:
from PySide2 import QtCore
except ImportError:
from PyQt5 import QtCore
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self, table_data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.table_data = table_data
def rowCount(self, parent):
return len(self.table_data)
def columnCount(self, parent):
return len(self.table_data[0])
def flags(self, index):
original_flags = super(MyTableModel, self).flags(index)
return original_flags | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
item = index.internalPointer()
if item is not None:
print(item)
value = self.table_data[row][column]
return value
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.table_data[row][column] = value
self.dataChanged.emit(index, index)
return True
return QtCore.QAbstractTableModel.setData(self, index, value, role)
class Widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self, *args, **kwargs)
self.view = QtWidgets.QTableView()
self.setLayout(QtWidgets.QVBoxLayout())
self.layout().addWidget(self.view)
table_data = [['HD', '1920', '1080', 'other', 'stuff', 'here'], ['lowres', '640', '480', 'other', 'stuff', 'here']]
self.proxy_model = QtCore.QSortFilterProxyModel()
self.model = MyTableModel(table_data=table_data)
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setDynamicSortFilter(True)
self.view.setModel(self.proxy_model)
self.proxy_model.dataChanged.connect(self.on_data_changed)
self.view.setSortingEnabled(True) # requires proxy model
self.view.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.view.horizontalHeader().setStretchLastSection(True)
self.view.horizontalHeader().setSectionsMovable(True)
def on_data_changed(self, _from, _to):
model = _from.model() # proxy model
model.blockSignals(True)
for index in self.view.selectionModel().selectedIndexes():
model.setData(index, _from.data())
model.blockSignals(False)
print('data was changed in table')
def change_value(self, col, multiplier):
print('Multiply the value (for all rows) in column %s with: %s' % (col, multiplier))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
m = QtWidgets.QWidget()
w = Widget()
e = QtWidgets.QSpinBox()
e.setValue(1)
e.valueChanged.connect(lambda: w.change_value(col=1, multiplier=e.value()))
e.valueChanged.connect(lambda: w.change_value(col=2, multiplier=e.value()))
l = QtWidgets.QHBoxLayout()
l.addWidget(w)
l.addWidget(e)
m.setLayout(l)
m.show()
sys.exit(app.exec_())
Am I supposed to execute self.proxy_mode.setData() and if so, how?
This is how I solved it. Not completely sure about getting the index via mapFromSource, but it works.
import sys
import copy
try:
from PySide2 import QtWidgets
except ImportError:
from PyQt5 import QtWidgets
try:
from PySide2 import QtCore
except ImportError:
from PyQt5 import QtCore
class MyTableModel(QtCore.QAbstractTableModel):
def __init__(self, table_data, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.table_data = table_data
def rowCount(self, parent):
return len(self.table_data)
def columnCount(self, parent):
return len(self.table_data[0])
def flags(self, index):
original_flags = super(MyTableModel, self).flags(index)
return original_flags | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
item = index.internalPointer()
if item is not None:
print(item)
value = self.table_data[row][column]
return value
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.table_data[row][column] = value
self.dataChanged.emit(index, index)
return True
return QtCore.QAbstractTableModel.setData(self, index, value, role)
class Widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self, *args, **kwargs)
self.view = QtWidgets.QTableView()
self.setLayout(QtWidgets.QVBoxLayout())
self.layout().addWidget(self.view)
table_data = [['HD', '1920', '1080', 'other', 'stuff', 'here'], ['lowres', '640', '480', 'other', 'stuff', 'here']]
self._original_table_data = copy.deepcopy(table_data)
self.proxy_model = QtCore.QSortFilterProxyModel()
self.model = MyTableModel(table_data=table_data)
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setDynamicSortFilter(True)
self.view.setModel(self.proxy_model)
self.proxy_model.dataChanged.connect(self.on_data_changed)
self.view.setSortingEnabled(True) # requires proxy model
self.view.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.view.horizontalHeader().setStretchLastSection(True)
self.view.horizontalHeader().setSectionsMovable(True)
def on_data_changed(self, _from, _to):
model = _from.model() # proxy model
model.blockSignals(True)
for index in self.view.selectionModel().selectedIndexes():
model.setData(index, _from.data())
model.blockSignals(False)
print('data was changed in table')
def change_value(self, col, multiplier):
print('Multiply the value (for all rows) in column %s with: %s' % (col, multiplier))
index = self.proxy_model.mapFromSource(QtCore.QModelIndex())
row_count = self.proxy_model.rowCount(index)
for row in range(0, row_count):
original_value = self._original_table_data[row][col]
index = self.proxy_model.index(row, col)
self.proxy_model.setData(index, int(original_value)*int(multiplier))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
m = QtWidgets.QWidget()
w = Widget()
q = QtWidgets.QLabel('Multiplier:')
e = QtWidgets.QSpinBox()
e.setValue(1)
e.valueChanged.connect(lambda: w.change_value(col=1, multiplier=e.value()))
e.valueChanged.connect(lambda: w.change_value(col=2, multiplier=e.value()))
l = QtWidgets.QHBoxLayout()
l.addWidget(w)
l.addWidget(q)
l.addWidget(e)
m.setLayout(l)
m.show()
sys.exit(app.exec_())

QTableView model using QComboBox

In this example below I have a simple QTableView which is populated using an AbstractModel. Each row in the table displays information related to a class object called Asset. It has a property called Items that contains a list of strings. I want to know how can i populate the QTableView with a combobox displaying this list of strings for each row.
Secondly when a user changes the item selected in the dropdown, i would like to trigger an event so i can later use it to properly change the color of the colored dot to green or red depending on the object's property called 'Status'
The status would indicate if the Current Version (meaning the latest item in the dropdown list) is the chosen item. If its the last item in the list, meaning the latest item, it would be green, otherwise it's red.
The property 'Active' indicates which item in the dropdownlist is currently selected.
If the status is 0 then it's out dated and if the status is 1 that means the latest version in the dropdownlist is being used.
import sys
from PySide import QtGui, QtCore
class Asset(object):
def __init__(self, name, items=None, status=0, active=0):
self._status = 0
self._name = ''
self._items = []
self._active = active
self.name = name
self.items = items if items != None else []
self.status = status
class AssetModel(QtCore.QAbstractTableModel):
attr = ["Name", "Options"]
def __init__(self, *args, **kwargs):
QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
self._items = []
def clear(self):
self._items = []
self.reset()
def rowCount(self, index=QtCore.QModelIndex()):
return len(self._items)
def columnCount(self, index=QtCore.QModelIndex()):
return len(self.attr)
def addItem(self, sbsFileObject):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(sbsFileObject)
self.endInsertRows()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return AssetModel.attr[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def getItem(self, index):
row = index.row()
if index.isValid() and 0 <= row < self.rowCount():
return index.data(role=QtCore.Qt.UserRole)
return None
def getSelectedItems(self, selection):
objs = []
for i, index in enumerate(selection):
item = self.getItem(index)
objs.append(item)
return objs
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
col = index.column()
if 0 <= col < self.columnCount():
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'name', '')
if col == 1:
return (getattr(item, 'items', []))
elif role == QtCore.Qt.UserRole:
if col == 0:
return item
elif role == QtCore.Qt.DecorationRole:
if col == 0:
status = getattr(item, 'status', 0)
col = QtGui.QColor(255,0,0,255)
if status == 1:
col = QtGui.QColor(255,128,0,255)
elif status == 2:
col = QtGui.QColor(255,255,0,255)
px = QtGui.QPixmap(120,120)
px.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(px)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
px_size = px.rect().adjusted(12,12,-12,-12)
painter.setBrush(col)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawEllipse(px_size)
painter.end()
return QtGui.QIcon(px)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.resize(400,300)
# controls
asset_model = QtGui.QSortFilterProxyModel()
asset_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
asset_model.setSourceModel(AssetModel())
self.ui_assets = QtGui.QTableView()
self.ui_assets.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_assets.setModel(asset_model)
self.ui_assets.verticalHeader().hide()
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(self.ui_assets)
self.setLayout(main_layout)
self.unit_test()
def unit_test(self):
assets = [
Asset('Doug', ['v01', 'v02', 'v03'], 0),
Asset('Amy', ['v10', 'v11', 'v13'], 1),
Asset('Kevin', ['v11', 'v22', 'v53'], 2),
Asset('Leslie', ['v13', 'v21', 'v23'], 0)
]
self.ui_assets.model().sourceModel().clear()
for i, obj in enumerate(assets):
self.ui_assets.model().sourceModel().addItem(obj)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You have 2 tasks:
Make your model editable because when using the combobox you must edit the values, in addition you must implement new roles to access all the properties of Asset, for it modify the class Asset:
class Asset(object):
def __init__(self, name, items=[], active=0):
self.active = active
self.name = name
self.items = items
#property
def status(self):
return self.active == len(self.items) - 1
To make an editable model, you must implement the setData() method and enable the Qt.ItemIsEditable flag:
class AssetModel(QtCore.QAbstractTableModel):
attr = ["Name", "Options"]
ItemsRole = QtCore.Qt.UserRole + 1
ActiveRole = QtCore.Qt.UserRole + 2
def __init__(self, *args, **kwargs):
QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
self._items = []
def flags(self, index):
fl = QtCore.QAbstractTableModel.flags(self, index)
if index.column() == 1:
fl |= QtCore.Qt.ItemIsEditable
return fl
def clear(self):
self.beginResetModel()
self._items = []
self.endResetModel()
def rowCount(self, index=QtCore.QModelIndex()):
return len(self._items)
def columnCount(self, index=QtCore.QModelIndex()):
return len(self.attr)
def addItem(self, sbsFileObject):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(sbsFileObject)
self.endInsertRows()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return AssetModel.attr[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
col = index.column()
if role == AssetModel.ItemsRole:
return getattr(item, 'items')
if role == AssetModel.ActiveRole:
return getattr(item, 'active')
if 0 <= col < self.columnCount():
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'name', '')
if col == 1:
return getattr(item, 'items')[getattr(item, 'active')]
elif role == QtCore.Qt.DecorationRole:
if col == 0:
status = getattr(item, 'status')
col = QtGui.QColor(QtCore.Qt.red) if status else QtGui.QColor(QtCore.Qt.green)
px = QtGui.QPixmap(120, 120)
px.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(px)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
px_size = px.rect().adjusted(12, 12, -12, -12)
painter.setBrush(col)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
QtCore.Qt.SolidLine,
QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
painter.drawEllipse(px_size)
painter.end()
return QtGui.QIcon(px)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
if role == AssetModel.ActiveRole:
setattr(item, 'active', value)
return True
return QtCore.QAbstractTableModel.setData(self, index, value, role)
Use a delegate, for it you must overwrite the methods createEditor(), setEditorData() and setModelData() where we created the QComboBox, updated the selection of the QComboBox with the information of the model, and updated the model with the selection of the QComboBox. We also use paint() to make the QComboBox persistent.
class AssetDelegate(QtGui.QStyledItemDelegate):
def paint(self, painter, option, index):
if isinstance(self.parent(), QtGui.QAbstractItemView):
self.parent().openPersistentEditor(index)
QtGui.QStyledItemDelegate.paint(self, painter, option, index)
def createEditor(self, parent, option, index):
combobox = QtGui.QComboBox(parent)
combobox.addItems(index.data(AssetModel.ItemsRole))
combobox.currentIndexChanged.connect(self.onCurrentIndexChanged)
return combobox
def onCurrentIndexChanged(self, ix):
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor, QtGui.QAbstractItemDelegate.NoHint)
def setEditorData(self, editor, index):
ix = index.data(AssetModel.ActiveRole)
editor.setCurrentIndex(ix)
def setModelData(self, editor, model, index):
ix = editor.currentIndex()
model.setData(index, ix, AssetModel.ActiveRole)
Then we establish the delegate and pass it as a parent to the QTableView so that it can be persisted automatically:
self.ui_assets.setItemDelegateForColumn(1, AssetDelegate(self.ui_assets))
The complete code can be found at the following link.

Categories

Resources