Add a checkbox to text in a QTableView cell using delegate - python

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())

Related

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.

Change state of checkbox programmatically in qtableview

I found this model to visualize qtableview with checkboxes. It works, but now I want also to change the state of checkboxes programmatically (for example a button that check/uncheck all checkboxes). I have no idea how I can do it...
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class TableModel(QAbstractTableModel):
def __init__(self, parent=None):
super(TableModel, self).__init__(parent)
self.tableData = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
self.checks = {}
def columnCount(self, *args):
return 3
def rowCount(self, *args):
return 3
def checkState(self, index):
if index in self.checks.keys():
return self.checks[index]
else:
return Qt.Unchecked
def data(self, index, role=Qt.DisplayRole):
row = index.row()
col = index.column()
if role == Qt.DisplayRole:
return '{0}'.format(self.tableData[row][col])
elif role == Qt.CheckStateRole and col == 0:
return self.checkState(QPersistentModelIndex(index))
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role == Qt.CheckStateRole:
self.checks[QPersistentModelIndex(index)] = value
return True
return False
def flags(self, index):
fl = QAbstractTableModel.flags(self, index)
if index.column() == 0:
fl |= Qt.ItemIsEditable | Qt.ItemIsUserCheckable
return fl
You have to use the setData() method, in this method the dataChanged signal must be emitted when the value associated with a role changes:
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role == Qt.CheckStateRole:
self.checks[QPersistentModelIndex(index)] = value
self.dataChanged.emit(index, index)
return True
return False
If you want to check and uncheck the items you must iterate over the items:
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.button = QPushButton("Checked", checkable=True)
self.button.clicked.connect(self.on_clicked)
self.view = QTableView()
self.model = TableModel(self)
self.view.setModel(self.model)
lay = QVBoxLayout(self)
lay.addWidget(self.button)
lay.addWidget(self.view)
#pyqtSlot(bool)
def on_clicked(self, state):
c = 0
for r in range(self.model.rowCount()):
ix = self.model.index(r, c)
self.model.setData(
ix, Qt.Checked if state else Qt.Unchecked, Qt.CheckStateRole
)
self.button.setText("Unchecked" if state else "Checked")

Remove QComboBox when QCheckBox is toggled off or on using QItemDelegate

I would like to update the cell content of a QTableView with a ComboBox whenever the toggle of a Checkbox above changes. I am using QTableView and a custom delegate to draw the ComboBoxes. The Checkboxes are controlled within the QTableView itself. Currently, when I toggle the checkboxes, the Comboboxes below will appear, but I could not manage to remove the ComboBoxes when toggling off the CheckBoxes.
My code sample is below.
import sys
import pandas as pd
from pandas.api.types import is_numeric_dtype
import numpy as np
from PyQt5.QtCore import (QAbstractTableModel, Qt, pyqtProperty, pyqtSlot,
QVariant, QModelIndex, pyqtSignal)
from PyQt5.QtWidgets import (QComboBox, QApplication, QAbstractItemView,
QItemDelegate, QCheckBox, QMainWindow, QTableView)
class DataFrameModel(QAbstractTableModel):
DtypeRole = Qt.UserRole + 1000
ValueRole = Qt.UserRole + 1001
def __init__(self, df=pd.DataFrame(), parent=None):
super(DataFrameModel, self).__init__(parent)
self._dataframe = df
self.df2 = pd.DataFrame(self._dataframe.iloc[2:3, :].to_dict())
def setDataFrame(self, dataframe):
self.beginResetModel()
self._dataframe = dataframe.copy()
self.endResetModel()
def dataFrame(self):
return self._dataframe
dataFrame = pyqtProperty(pd.DataFrame, fget=dataFrame, fset=setDataFrame)
#pyqtSlot(int, Qt.Orientation, result=str)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role != Qt.DisplayRole:
return QVariant()
if orientation == Qt.Horizontal:
try:
return self._dataframe.columns.tolist()[section]
except (IndexError,):
return QVariant()
elif orientation == Qt.Vertical:
try:
if section in [0, 1]:
pass
else:
return self._dataframe.index.tolist()[section - 2]
except (IndexError,):
return QVariant()
def rowCount(self, parent=QModelIndex()):
if parent.isValid():
return 0
return len(self._dataframe.index)
def columnCount(self, parent=QModelIndex()):
if parent.isValid():
return 0
return self._dataframe.columns.size
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
col = index.column()
row = index.row()
dt = self.df2[self.df2.columns[col]].dtype
is_numeric = is_numeric_dtype(self.df2[self.df2.columns[col]])
if row == 0 and is_numeric:
value = self._dataframe.iloc[row, col].text()
else:
value = self._dataframe.iloc[row, col]
if role == Qt.DisplayRole:
return value
elif role == Qt.CheckStateRole:
if row == 0 and is_numeric:
if self._dataframe.iloc[row, col].isChecked():
return Qt.Checked
else:
return Qt.Unchecked
elif role == DataFrameModel.ValueRole:
return value
elif role == DataFrameModel.ValueRole:
return value
if role == DataFrameModel.DtypeRole:
return dt
return QVariant()
def roleNames(self):
roles = {
Qt.DisplayRole: b'display',
DataFrameModel.DtypeRole: b'dtype',
DataFrameModel.ValueRole: b'value'
}
return roles
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
col = index.column()
row = index.row()
is_numeric = is_numeric_dtype(self.df2[self.df2.columns[col]])
if role == Qt.CheckStateRole and index.row() == 0:
if is_numeric:
if value == Qt.Checked:
self._dataframe.iloc[row, col].setChecked(True)
self._dataframe.iloc[row, col].setText("Grade Item")
else:
self._dataframe.iloc[row, col].setChecked(False)
self._dataframe.iloc[row, col].setText("Not a Grade")
elif row == 1 and role == Qt.EditRole:
if isinstance(value, QVariant):
value = value.value()
if hasattr(value, 'toPyObject'):
value = value.toPyObject()
self._dataframe.iloc[row, col] = value
elif row >= 2 and role == Qt.EditRole:
try:
value = eval(value)
if not isinstance(
value,
self._dataframe.applymap(type).iloc[row, col]):
value = self._dataframe.iloc[row, col]
except:
value = self._dataframe.iloc[row, col]
self._dataframe.iloc[row, col] = value
self.dataChanged.emit(index, index, (Qt.DisplayRole,))
return True
def flags(self, index):
if not index.isValid():
return None
if index.row() == 0:
return (Qt.ItemIsEnabled | Qt.ItemIsSelectable |
Qt.ItemIsUserCheckable)
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def sort(self, column, order):
self.layoutAboutToBeChanged.emit()
col_name = self._dataframe.columns.tolist()[column]
sheet1 = self._dataframe.iloc[:2, :]
sheet2 = self._dataframe.iloc[2:, :].sort_values(
col_name, ascending=order == Qt.AscendingOrder, inplace=False)
sheet2.reset_index(drop=True, inplace=True)
sheet3 = pd.concat([sheet1, sheet2], ignore_index=True)
self.setDataFrame(sheet3)
self.layoutChanged.emit()
class ComboBoxDelegate(QItemDelegate):
def __init__(self, parent, choices=None):
super().__init__(parent)
self.items = choices
def createEditor(self, parent, option, index):
self.parent().model().dataChanged.emit(index, index, (Qt.DisplayRole,))
if is_numeric_dtype(
self.parent().model().df2[
self.parent().model().df2.columns[index.column()]]):
checked = self.parent().model().dataFrame.iloc[
0, index.column()].isChecked()
if checked:
editor = QComboBox(parent)
editor.addItems(self.items)
editor.currentIndexChanged.connect(self.currentIndexChanged)
return editor
def paint(self, painter, option, index):
if isinstance(self.parent(), QAbstractItemView):
self.parent().openPersistentEditor(index)
def setModelData(self, editor, model, index):
value = editor.currentText()
model.setData(index, value, Qt.DisplayRole)
def setEditorData(self, editor, index):
text = index.data(Qt.DisplayRole) or ""
editor.setCurrentText(text)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
#pyqtSlot()
def currentIndexChanged(self):
editor = self.sender()
self.commitData.emit(editor)
class MainWindow(QMainWindow):
def __init__(self, pandas_sheet):
super().__init__()
self.pandas_sheet = pandas_sheet
self.table = QTableView()
self.setCentralWidget(self.table)
check_bx_lst = []
is_number = [is_numeric_dtype(self.pandas_sheet[col]) for col
in self.pandas_sheet.columns]
for is_numb in is_number:
if is_numb:
checkbox = QCheckBox('Not a Grade')
checkbox.setChecked(False)
check_bx_lst.append(checkbox)
else:
check_bx_lst.append(None)
for i in range(2):
self.pandas_sheet.loc[-1] = [' '] * \
self.pandas_sheet.columns.size
self.pandas_sheet.index = self.pandas_sheet.index + 1
self.pandas_sheet = self.pandas_sheet.sort_index()
self.pandas_sheet.loc[0] = check_bx_lst
model = DataFrameModel(self.pandas_sheet)
self.table.setModel(model)
self.table.setSortingEnabled(True)
delegate = ComboBoxDelegate(self.table,
[None, 'Test', 'Quiz'])
self.table.setItemDelegateForRow(1, delegate)
self.table.resizeColumnsToContents()
self.table.resizeRowsToContents()
if __name__ == '__main__':
df = pd.DataFrame({'a': ['student ' + str(i) for i in range(5)],
'b': np.arange(5),
'c': np.random.rand(5)})
app = QApplication(sys.argv)
window = MainWindow(df)
window.table.model().sort(df.columns.get_loc("a"), Qt.AscendingOrder)
window.setFixedSize(280, 200)
window.show()
sys.exit(app.exec_())
I would like to remove the Combobox when the checkbox above is toggled off.
Any help is really appreciated.
You are using openPersistentEditor within the paint function, which is simply wrong: painting happens very often for every index the delegate is used, and you're practically calling createEditor each time each cell in that row is painted, something that happens for all the cells when the view is scrolled, or for any cell hovered by the mouse.
Following your logic, the creator is finally created probably due to painting requested by data change, because at that point the if conditions in createEditor are True. From that point on, you create a persistent editor, and if you don't remove it it will just stay there.
Obviously, all this is not a good approach, mostly because you virtually check if it's ok to create the editor from a paint function, which doesn't make much sense.
You should connect to the dataChanged signal for the model to verify the checked status and then open or close the editor accordingly.
class MainWindow(QMainWindow):
def __init__(self, pandas_sheet):
# ...
model.dataChanged.connect(self.dataChanged)
def dataChanged(self, topLeft, bottomRight, roles):
if topLeft.row() == 0:
if topLeft.data(QtCore.Qt.CheckStateRole):
self.table.openPersistentEditor(topLeft.sibling(1, topLeft.column()))
else:
self.table.closePersistentEditor(topLeft.sibling(1, topLeft.column()))
I've over-simplified the if condition, but I assume that the concept is clear enough.

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_())

Categories

Resources