It is not possible to make the text crossed out in a separate cell. It turns out only to apply to everyone. Tried changing in model methods. Also applies to everyone. I looked for an example on the Internet, but it didn't work. The bottom line is for the user to select a cell, click on the button and the text in the cell becomes checked out.
My code
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
STRIKE_ROLE = Qt.UserRole + 1
import pandas as pd
class StrikeDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
font = QFont(option.font)
font.setStrikeOut(index.data(STRIKE_ROLE))
option.font = font
class MyDelegate(QItemDelegate):
def setEditorData(self, editor, index):
text = index.data(Qt.EditRole) or index.data(Qt.DisplayRole)
editor.setText(text)
# def initStyleOption(self,option,index):
#
# QtWidgets.QStyledItemDelegate.initStyleOption(option,index)
# option.font().setStrikeOut(True)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def setData(self, index, value, role):
self._data.iloc[index.row(), index.column()] = value
self.dataChanged.emit(index, index)
return True
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsEditable |Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
def data(self, index, role):
if role == Qt.DisplayRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
def rename_column(self, index, new_name):
self._data.rename(index={self._data.index[index]: new_name}, inplace=True)
return True
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
def add_empty_row(self):
self.beginResetModel()
try:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([[0] * len(self._data.columns)]),
index=['Пусто']))
except IndexError:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([[None] * len(self._data.columns)]),
index=[0]))
self.layoutChanged.emit()
self.endResetModel()
# self.inp_.add_delete_row.emit(self.createIndex(self._data.index[-1], 0), 'add_empty_row')
def delete_row(self, index):
self._data.drop(self._data.index[index.row()], inplace=True)
# self._data.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
class TaskModel(TableModel):
def add_empty_row(self):
self.beginResetModel()
try:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([['Новая задача'] * len(self._data.columns)]),
index=[self._data.index[-1] + 1]))
except IndexError:
self._data = self._data.append(
pd.DataFrame(columns=self._data.columns, data=([[None] * len(self._data.columns)]),
index=[0]))
self.layoutChanged.emit()
self.endResetModel()
def data(self, index, role):
value = self._data.iloc[index.row(), index.column()]
if role == Qt.DisplayRole:
return str(value)
if role == STRIKE_ROLE:
font=QFont('Calibri',13)
return font
def setData(self, index, value, role):
self._data.iloc[index.row(), index.column()] = value
self.dataChanged.emit(index, index)
return True
class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(586, 683)
self.Form=Form
self.horizontalLayout_2 = QHBoxLayout(Form)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.dock_task_calendar = QDockWidget(Form)
self.dock_task_calendar.setObjectName(u"dock_task_calendar")
self.dock_task_calendar.setFeatures(QDockWidget.DockWidgetClosable|QDockWidget.DockWidgetMovable)
self.dockWidgetContents_task = QWidget()
self.dockWidgetContents_task.setObjectName(u"dockWidgetContents_task")
self.verticalLayout_3 = QVBoxLayout(self.dockWidgetContents_task)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.scrollArea_task = QScrollArea(self.dockWidgetContents_task)
self.scrollArea_task.setObjectName(u"scrollArea_task")
self.scrollArea_task.setWidgetResizable(True)
self.scrollAreaWidgetContents_task_2 = QWidget()
self.scrollAreaWidgetContents_task_2.setObjectName(u"scrollAreaWidgetContents_task_2")
self.scrollAreaWidgetContents_task_2.setGeometry(QRect(0, 0, 548, 623))
self.horizontalLayout = QHBoxLayout(self.scrollAreaWidgetContents_task_2)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.verticalLayout_2 = QVBoxLayout()
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.gridLayout.addLayout(self.verticalLayout_2, 4, 0, 1, 1)
self.tasklistView = QListView(self.scrollAreaWidgetContents_task_2)
self.tasklistView.setObjectName(u"tasklistView")
self.tasklistView.setDragEnabled(True)
self.tasklistView.setDragDropOverwriteMode(True)
self.tasklistView.setWordWrap(True)
self.tasklistView.setItemAlignment(Qt.AlignLeading)
self.gridLayout.addWidget(self.tasklistView, 4, 1, 1, 1)
self.model=TaskModel(pd.DataFrame([],columns=['a']))
self.delegater=MyDelegate()
self.tasklistView.setModel(self.model)
self.tasklistView.setItemDelegate(self.delegater)
self.label_task = QLabel(self.scrollAreaWidgetContents_task_2)
self.label_task.setObjectName(u"label_task")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_task.sizePolicy().hasHeightForWidth())
self.label_task.setSizePolicy(sizePolicy)
self.label_task.setMaximumSize(QSize(16777215, 20))
self.label_task.setAlignment(Qt.AlignCenter)
self.gridLayout.addWidget(self.label_task, 2, 1, 1, 1)
self.calendar = QCalendarWidget(self.scrollAreaWidgetContents_task_2)
self.calendar.setObjectName(u"calendar")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.calendar.sizePolicy().hasHeightForWidth())
self.calendar.setSizePolicy(sizePolicy1)
self.calendar.setMaximumSize(QSize(16777215, 400))
self.gridLayout.addWidget(self.calendar, 1, 1, 1, 1)
self.verticalLayout = QVBoxLayout()
self.verticalLayout.setObjectName(u"verticalLayout")
self.add_task = QPushButton(self.scrollAreaWidgetContents_task_2)
self.add_task.setObjectName(u"add_task")
sizePolicy2 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.add_task.sizePolicy().hasHeightForWidth())
self.add_task.setSizePolicy(sizePolicy2)
self.add_task.setMaximumSize(QSize(40, 40))
self.verticalLayout.addWidget(self.add_task)
self.del_task = QPushButton(self.scrollAreaWidgetContents_task_2)
self.del_task.setObjectName(u"del_task")
self.del_task.setMaximumSize(QSize(40, 40))
self.verticalLayout.addWidget(self.del_task)
self.check_task = QPushButton(self.scrollAreaWidgetContents_task_2)
self.check_task.setObjectName(u"check_task")
self.check_task.setMaximumSize(QSize(40, 40))
self.verticalLayout.addWidget(self.check_task)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
self.gridLayout.addLayout(self.verticalLayout, 4, 2, 1, 1)
self.horizontalLayout.addLayout(self.gridLayout)
self.scrollArea_task.setWidget(self.scrollAreaWidgetContents_task_2)
self.verticalLayout_3.addWidget(self.scrollArea_task)
self.dock_task_calendar.setWidget(self.dockWidgetContents_task)
self.horizontalLayout_2.addWidget(self.dock_task_calendar)
self.add_task.pressed.connect(self.model.add_empty_row)
self.retranslateUi(Form)
self.Form.show()
# setupUi
def retranslateUi(self, Form):
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
self.dock_task_calendar.setWindowTitle("")
self.label_task.setText(QCoreApplication.translate("Form", u"C\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u043b/\u0437\u0430\u0434\u0430\u0447", None))
self.add_task.setText(QCoreApplication.translate("Form", u"PushButton", None))
self.del_task.setText(QCoreApplication.translate("Form", u"PushButton", None))
self.check_task.setText(QCoreApplication.translate("Form", u"PushButton", None))
# retranslateUi
if __name__ == '__main__':
app = QApplication(sys.argv)
translator = QTranslator()
if len(sys.argv) > 1:
locale = sys.argv[1]
else:
locale = QLocale.system().name()
translator.load('qt_%s' % locale,
QLibraryInfo.location(QLibraryInfo.TranslationsPath))
app.installTranslator(translator)
form=Ui_Form()
form.setupUi(QWidget())
app.exec_()
Since the OP has not provided an MRE then my solution will only limit the general logic which is:
Create a new role for each item where a boolean (or any other variable) is stored if the strike is applied.
Use a delegate to set the strike in the font.
With the clicked signal change the value stored in the role strike for selected rows.
from functools import cached_property
from PyQt5 import QtCore, QtGui, QtWidgets
STRIKE_ROLE = QtCore.Qt.UserRole + 1
class StrikeDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
font = QtGui.QFont(option.font)
font.setStrikeOut(index.data(STRIKE_ROLE))
option.font = font
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
font = QtGui.QFont(editor.font())
font.setStrikeOut(index.data(STRIKE_ROLE))
editor.setFont(font)
return editor
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
hlay = QtWidgets.QHBoxLayout(central_widget)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(self.add_button)
vlay.addWidget(self.remove_button)
vlay.addWidget(self.strike_button)
vlay.addStretch()
hlay.addWidget(self.view)
hlay.addLayout(vlay)
self.add_button.clicked.connect(self.add)
self.remove_button.clicked.connect(self.remove)
self.strike_button.clicked.connect(self.strike)
#cached_property
def model(self):
return QtGui.QStandardItemModel()
#cached_property
def view(self):
view = QtWidgets.QListView()
delegate = StrikeDelegate()
view.setItemDelegate(delegate)
view.setModel(self.model)
return view
#cached_property
def add_button(self):
return QtWidgets.QPushButton("Add")
#cached_property
def remove_button(self):
return QtWidgets.QPushButton("Remove")
#cached_property
def strike_button(self):
return QtWidgets.QPushButton("Strike")
def add(self):
text, ok = QtWidgets.QInputDialog.getText(self, "Title", "Text:")
if not ok:
return
item = QtGui.QStandardItem(text)
item.setData(True, STRIKE_ROLE)
self.model.appendRow(item)
def remove(self):
rows = [index.row() for index in self.view.selectedIndexes()]
for row in sorted(rows, reverse=True):
self.model.takeRow(row)
def strike(self):
for index in self.view.selectedIndexes():
strike = self.model.data(index, STRIKE_ROLE)
self.model.setData(index, not strike, STRIKE_ROLE)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
hi i've tried all i can think of and had looked at hundreds of stack overflow questions about tables and delegate's, and scratched my head for hours looking at the documentation trying to understand the c++ language and i have not read anything clearly stating that there's limits to the of amount delegate's a table view can take and not take, now i hope i can say i've got a firm understanding of the basic's in pyside2 and pyqt5 especially with tables and models but the delegates is a bit mind boggling, i've gotten this far based on people's questions mostly from stack overflow so this is my first attempt to ask any help..
import pandas as pd
from PySide2 import QtWidgets
from PySide2.QtCore import (Qt, QAbstractTableModel, QModelIndex, QEvent, QPersistentModelIndex,
QSortFilterProxyModel,
QTimer, Slot)
from PySide2.QtWidgets import QTableView, QAbstractItemView, QComboBox, QItemDelegate
class ScheduleModel(QAbstractTableModel):
def __init__(self, schedules_list=None, parent=None):
super(ScheduleModel, self).__init__(parent)
if schedules_list is None:
self.schedules_list = []
else:
self.schedules_list = schedules_list
def rowCount(self, index=QModelIndex()):
return self.schedules_list.shape[0]
def columnCount(self, index=QModelIndex()):
return self.schedules_list.shape[1]
def data(self, index, role=Qt.DisplayRole):
col = index.column()
if index.isValid():
if role == Qt.DisplayRole:
value = self.schedules_list.iloc[index.row(), index.column()]
return str(self.schedules_list.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.schedules_list.columns[section]
if orientation == Qt.Vertical:
return str(self.schedules_list.index[section])
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
if index.isValid() and 0 <= index.row() < len(self.schedules_list):
self.schedules_list.iloc[index.row(), index.column()] = value
if self.data(index, Qt.DisplayRole) == value:
self.dataChanged.emit(index, index, (Qt.EditRole,))
return True
return False
def flags(self, index):
if 1 <= index.column() <= 7:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if index.column() == 5:
return Qt.ItemIsEditable | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable
elif index.column() == 1 and index.column() == 7:
return Qt.DecorationRole
else:
return Qt.ItemIsSelectable
class ClickDelegate(QtWidgets.QStyledItemDelegate):
blankText = '<Click here to add path>'
def openFileDialog(self, lineEdit):
if not self.blankText.startswith(lineEdit.text()):
currentPath = lineEdit.text()
else:
currentPath = ''
path, _ = QtWidgets.QFileDialog.getOpenFileName(lineEdit.window(),
'Select file', currentPath)
if path:
lineEdit.setText(path)
def createEditor(self, parent, option, index):
editor = QtWidgets.QWidget(parent)
layout = QtWidgets.QHBoxLayout(editor)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
editor.lineEdit = QtWidgets.QLineEdit(self.blankText)
layout.addWidget(editor.lineEdit)
editor.setFocusProxy(editor.lineEdit)
editor.lineEdit.installEventFilter(self)
button = QtWidgets.QToolButton(text='...')
layout.addWidget(button)
button.setFocusPolicy(Qt.NoFocus)
button.clicked.connect(lambda: self.openFileDialog(editor.lineEdit))
return editor
def setEditorData(self, editor, index):
if index.data():
editor.lineEdit.setText(str(index.data()))
editor.lineEdit.selectAll()
def setModelData(self, editor, model, index):
if not editor.lineEdit.text():
model.setData(index, None)
elif not self.blankText.startswith(editor.lineEdit.text()):
model.setData(index, editor.lineEdit.text())
def initStyleOption(self, option, index):
super(ClickDelegate, self).initStyleOption(option, index)
if not option.text:
option.text = self.blankText
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QLineEdit):
if (event.type() == QEvent.MouseButtonPress and
source.hasSelectedText() and
self.blankText.startswith(source.text())):
res = super(ClickDelegate, self).eventFilter(source, event)
source.clear()
return res
elif event.type() == QEvent.KeyPress and event.key() in (
Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab):
return False
return super(ClickDelegate, self).eventFilter(source, event)
def checkIndex(self, table, index):
if index in table.selectedIndexes() and index == table.currentIndex():
table.edit(index)
def editorEvent(self, event, model, option, index):
if (event.type() == QEvent.MouseButtonPress and
event.button() == Qt.LeftButton and
index in option.widget.selectedIndexes()):
table = option.widget
QTimer.singleShot(0, lambda: self.checkIndex(table, index))
return super(ClickDelegate, self).editorEvent(event, model, option, index)
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect,
Qt.Unchecked if int(index.data()) == 0 else Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() and Qt.ItemIsEditable) > 0:
return False
if event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
return False
def setModelData(self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, Qt.EditRole)
class DoubleSpinBoxDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate class displaying a double spin box."""
def __init__(self, parent=None, minimum=0.0, maximum=100.0, step=0.01):
QtWidgets.QStyledItemDelegate.__init__(self, parent)
self._min = minimum
self._max = maximum
self._step = step
def createEditor(self, parent, option, index):
editor = QtWidgets.QDoubleSpinBox(parent)
editor.setMinimum(self._min)
editor.setMaximum(self._max)
editor.setSingleStep(self._step)
editor.setAccelerated(True)
editor.installEventFilter(self)
return editor
def setEditorData(self, spinBox, index):
value = float(index.model().data(index, Qt.DisplayRole))
spinBox.setValue(value)
def setModelData(self, spinBox, model, index):
value = spinBox.value()
model.setData(index, value)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class ComboBoxDelegate(QItemDelegate):
def __init__(self, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self.items = []
def setItems(self, items):
self.items = items
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
def setModelData(self, editor, model, index):
# model.setData(index, editor.currentIndex(), Qt.EditRole)
model.setData(index, editor.currentText())
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
#Slot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class SchedulesViewer(QTableView):
# selectionChanged = Signal(QItemSelection)
# data_changed = Signal(QModelIndex, QModelIndex)
def __init__(self, parent=None):
QTableView.__init__(self, parent)
# self.setContextMenuPolicy(Qt.CustomContextMenu)
# self.customContextMenuRequested.connect(self.schedule_context_menu)
address = {'idx': '1',
'presets': 'presets',
'selected_source': 'get_source',
'selected_destinations': 'selected_destinations',
'interval': '0400',
'active': '1',
'priority': 'high',
'categories': 'programming',
'last_total': '222',
}
self.schedule_model = ScheduleModel(pd.DataFrame([address]))
self.proxyModel = QSortFilterProxyModel(self)
self.proxyModel.setSourceModel(self.schedule_model)
self.proxyModel.setDynamicSortFilter(True)
self.setModel(self.proxyModel)
**"""
HAVING LIMITS TO THE AMOUNT OF WIDGETS TABLE VIEW CAN HANDEL
"""
dialog_delegate = ClickDelegate(self)
self.setItemDelegateForColumn(2, dialog_delegate)
self.setItemDelegateForColumn(3, dialog_delegate)
# spin_delegate = DoubleSpinBoxDelegate()
# self.setItemDelegateForColumn(4, spin_delegate)
# CheckBox = CheckBoxDelegate(None)
# self.setItemDelegateForColumn(5, CheckBox)
data = ['programming', 'game_build', 'other']
combo_delegate = ComboBoxDelegate()
combo_delegate.setItems([str(row) for row in data])
self.setItemDelegateForColumn(6, combo_delegate)**
self.setSortingEnabled(True)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
self.verticalHeader().hide()
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.proxyModel.sort(0, Qt.AscendingOrder)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.setSelectionMode(QAbstractItemView.SingleSelection)
# self.selectionModel().selectionChanged.connect(self.selectionChanged)
self.show()
if __name__ == "__main__":
import sys
from PySide2.QtWidgets import QApplication
app = QApplication(sys.argv)
addressWidget = SchedulesViewer()
addressWidget.show()
sys.exit(app.exec_())
so please would someone help me understand what i'am i missing or not understanding, all that i want to achieve is to add the delegate's that have been hashed out and make it a editable table, but if i add either the spinbox or the checkbox delegate the app freezes and crashes so is there a limit as to how many delegate's the table view can handle or what i'am i doing wrong? Any help would be much appreciated please and thank you in advance..
Thanks to musicamante that pointed out so freindly my simple mistake of overlooking the obvious of the too self's missing to make all the delegates members of the instance and i have tested and it works so here is the code..
import pandas as pd
from PySide2 import QtWidgets
from PySide2.QtCore import (Qt, QAbstractTableModel, QModelIndex, QEvent,
QPersistentModelIndex,
QSortFilterProxyModel,
QTimer, Slot)
from PySide2.QtWidgets import QTableView, QAbstractItemView, QComboBox, QItemDelegate
class ScheduleModel(QAbstractTableModel):
def __init__(self, schedules_list=None, parent=None):
super(ScheduleModel, self).__init__(parent)
if schedules_list is None:
self.schedules_list = []
else:
self.schedules_list = schedules_list
def rowCount(self, index=QModelIndex()):
return self.schedules_list.shape[0]
def columnCount(self, index=QModelIndex()):
return self.schedules_list.shape[1]
def data(self, index, role=Qt.DisplayRole):
col = index.column()
if index.isValid():
if role == Qt.DisplayRole:
value = self.schedules_list.iloc[index.row(), index.column()]
return str(self.schedules_list.iloc[index.row(), index.column()])
return None
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.schedules_list.columns[section]
if orientation == Qt.Vertical:
return str(self.schedules_list.index[section])
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
if index.isValid() and 0 <= index.row() < len(self.schedules_list):
self.schedules_list.iloc[index.row(), index.column()] = value
if self.data(index, Qt.DisplayRole) == value:
self.dataChanged.emit(index, index, (Qt.EditRole,))
return True
return False
def flags(self, index):
if 1 <= index.column() <= 7:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if index.column() == 5:
return Qt.ItemIsEditable | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable
elif index.column() == 1 and index.column() == 7:
return Qt.DecorationRole
else:
return Qt.ItemIsSelectable
class ClickDelegate(QtWidgets.QStyledItemDelegate):
blankText = '<Click here to add path>'
def openFileDialog(self, lineEdit):
if not self.blankText.startswith(lineEdit.text()):
currentPath = lineEdit.text()
else:
currentPath = ''
path, _ = QtWidgets.QFileDialog.getOpenFileName(lineEdit.window(),
'Select file', currentPath)
if path:
lineEdit.setText(path)
def createEditor(self, parent, option, index):
editor = QtWidgets.QWidget(parent)
layout = QtWidgets.QHBoxLayout(editor)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
editor.lineEdit = QtWidgets.QLineEdit(self.blankText)
layout.addWidget(editor.lineEdit)
editor.setFocusProxy(editor.lineEdit)
editor.lineEdit.installEventFilter(self)
button = QtWidgets.QToolButton(text='...')
layout.addWidget(button)
button.setFocusPolicy(Qt.NoFocus)
button.clicked.connect(lambda: self.openFileDialog(editor.lineEdit))
return editor
def setEditorData(self, editor, index):
if index.data():
editor.lineEdit.setText(str(index.data()))
editor.lineEdit.selectAll()
def setModelData(self, editor, model, index):
if not editor.lineEdit.text():
model.setData(index, None)
elif not self.blankText.startswith(editor.lineEdit.text()):
model.setData(index, editor.lineEdit.text())
def initStyleOption(self, option, index):
super(ClickDelegate, self).initStyleOption(option, index)
if not option.text:
option.text = self.blankText
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QLineEdit):
if (event.type() == QEvent.MouseButtonPress and
source.hasSelectedText() and
self.blankText.startswith(source.text())):
res = super(ClickDelegate, self).eventFilter(source, event)
source.clear()
return res
elif event.type() == QEvent.KeyPress and event.key() in (
Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab):
return False
return super(ClickDelegate, self).eventFilter(source, event)
def checkIndex(self, table, index):
if index in table.selectedIndexes() and index == table.currentIndex():
table.edit(index)
def editorEvent(self, event, model, option, index):
if (event.type() == QEvent.MouseButtonPress and
event.button() == Qt.LeftButton and
index in option.widget.selectedIndexes()):
table = option.widget
QTimer.singleShot(0, lambda: self.checkIndex(table, index))
return super(ClickDelegate, self).editorEvent(event, model, option, index)
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which
it's applied.
"""
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect,
Qt.Unchecked if int(index.data()) == 0 else Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise
do nothing.
'''
if not int(index.flags() and Qt.ItemIsEditable) > 0:
return False
if event.type() == QEvent.MouseButtonRelease and event.button() ==
Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
return False
def setModelData(self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, Qt.EditRole)
class DoubleSpinBoxDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate class displaying a double spin box."""
def __init__(self, parent=None, minimum=0.0, maximum=100.0, step=0.01):
QtWidgets.QStyledItemDelegate.__init__(self, parent)
self._min = minimum
self._max = maximum
self._step = step
def createEditor(self, parent, option, index):
editor = QtWidgets.QDoubleSpinBox(parent)
editor.setMinimum(self._min)
editor.setMaximum(self._max)
editor.setSingleStep(self._step)
editor.setAccelerated(True)
editor.installEventFilter(self)
return editor
def setEditorData(self, spinBox, index):
value = float(index.model().data(index, Qt.DisplayRole))
spinBox.setValue(value)
def setModelData(self, spinBox, model, index):
value = spinBox.value()
model.setData(index, value)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class ComboBoxDelegate(QItemDelegate):
def __init__(self, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self.items = []
def setItems(self, items):
self.items = items
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
def setModelData(self, editor, model, index):
# model.setData(index, editor.currentIndex(), Qt.EditRole)
model.setData(index, editor.currentText())
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
#Slot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class SchedulesViewer(QTableView):
# selectionChanged = Signal(QItemSelection)
# data_changed = Signal(QModelIndex, QModelIndex)
def __init__(self, parent=None):
QTableView.__init__(self, parent)
# self.setContextMenuPolicy(Qt.CustomContextMenu)
# self.customContextMenuRequested.connect(self.schedule_context_menu)
address = {'idx': '1',
'presets': 'presets',
'selected_source': 'get_source',
'selected_destinations': 'selected_destinations',
'interval': '0400',
'active': '1',
'priority': 'high',
'categories': 'programming',
'last_total': '222',
}
self.schedule_model = ScheduleModel(pd.DataFrame([address]))
self.proxyModel = QSortFilterProxyModel(self)
self.proxyModel.setSourceModel(self.schedule_model)
self.proxyModel.setDynamicSortFilter(True)
self.setModel(self.proxyModel)
"""
HAVING LIMITS TO THE AMOUNT OF WIDGETS TABLE VIEW CAN HANDEL
"""
self.setItemDelegateForColumn(2, ClickDelegate(self))
self.setItemDelegateForColumn(3, ClickDelegate(self))
self.setItemDelegateForColumn(4, DoubleSpinBoxDelegate(self))
self.setItemDelegateForColumn(5, CheckBoxDelegate(self))
data = ['programming', 'game_build', 'other']
combo_delegate = ComboBoxDelegate(self)
combo_delegate.setItems([str(row) for row in data])
self.setItemDelegateForColumn(6, combo_delegate)
self.setSortingEnabled(True)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
self.verticalHeader().hide()
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.proxyModel.sort(0, Qt.AscendingOrder)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.setSelectionMode(QAbstractItemView.SingleSelection)
# self.selectionModel().selectionChanged.connect(self.selectionChanged)
self.show()
if __name__ == "__main__":
import sys
from PySide2.QtWidgets import QApplication
app = QApplication(sys.argv)
addressWidget = SchedulesViewer()
addressWidget.show()
sys.exit(app.exec_())
CustomMenu class inherits from QMenu. Its custom setData() method accepts an argument and sets it to a class variable model. I did this because QToolButton, QToolMenu and QAction do not support model/view framework. From what I know aside from all QList-QTable-QTree Views and Trees only the QComboBox does support model.
So the question: would it be possible to extend the functionality of other non-model widgets so they could be used as driven-by-model widgets too?
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class CustomMenu(QMenu):
def __init__(self):
super(CustomMenu, self).__init__()
self.model=None
def setModel(self, model):
self.model=model
self.pupulate()
def pupulate(self):
for item in self.model.items:
self.addAction(item)
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def rowCount(self, QModelIndex):
return len(self.items)
def columnCount(self, QModelIndex):
return 1
def data(self, index, role):
if not index.isValid(): return QVariant()
if role == Qt.DisplayRole:
return QVariant(self.items[index.row()])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
self.items[index.row()]=value
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
layout=QVBoxLayout(self)
self.setLayout(layout)
tablemodel=Model(self)
tableView=QTableView()
tableView.horizontalHeader().setStretchLastSection(True)
tableView.setModel(tablemodel)
layout.addWidget(tableView)
combo=QComboBox()
combo.setModel(tablemodel)
layout.addWidget(combo)
toolButton=QToolButton(self)
toolButton.setText('Tool Button')
toolButton.setPopupMode(QToolButton.InstantPopup)
toolButton.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed))
menu=CustomMenu()
menu.setModel(tablemodel)
toolButton.setMenu(menu)
layout.addWidget(toolButton)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
EDITED LATER: An attempt to implement a QDataWidgetMapper():
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class CustomMenuButton(QWidget):
def __init__(self, parent):
super(CustomMenuButton, self).__init__(parent)
self.dataMapper=QDataWidgetMapper()
layout=QVBoxLayout()
self.setLayout(layout)
toolButton=QToolButton(self)
toolButton.setText('Tool Button')
toolButton.setPopupMode(QToolButton.InstantPopup)
self.menu=QMenu(toolButton)
toolButton.setMenu(self.menu)
def setModel(self, model):
self.dataMapper.setModel(model)
for row in range(model.rowCount()):
action=self.menu.addAction('Item')
self.dataMapper.addMapping(action, row)
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid(): return QVariant()
if role == Qt.DisplayRole:
return QVariant(self.items[index.row()])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
self.items[index.row()]=value
self.dataChanged.emit(index, index)
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
layout=QVBoxLayout(self)
self.setLayout(layout)
tablemodel=Model(self)
tableView=QTableView()
tableView.horizontalHeader().setStretchLastSection(True)
tableView.setModel(tablemodel)
layout.addWidget(tableView)
combo=QComboBox()
combo.setModel(tablemodel)
layout.addWidget(combo)
menuButton=CustomMenuButton(self)
layout.addWidget(menuButton)
menuButton.setModel(tablemodel)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
EDIT 2: Defined a class CustomAction(QAction) with dataChanged = pyqtSignal(QModelIndex,QModelIndex)...
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
class CustomAction(QAction):
dataChanged = pyqtSignal(QModelIndex,QModelIndex)
def __init__(self, name, parent):
super(CustomAction, self).__init__(name, parent)
class CustomMenuButton(QWidget):
def __init__(self, parent):
super(CustomMenuButton, self).__init__(parent)
self.dataMapper=QDataWidgetMapper()
layout=QVBoxLayout()
self.setLayout(layout)
toolButton=QToolButton(self)
toolButton.setText('Tool Button')
toolButton.setPopupMode(QToolButton.InstantPopup)
self.menu=QMenu(toolButton)
toolButton.setMenu(self.menu)
def setModel(self, model):
self.dataMapper.setModel(model)
for row in range(model.rowCount()):
index=model.index(row,0)
itemName=model.data(index, Qt.DisplayRole).toPyObject()
actn=CustomAction(itemName, self.menu)
self.menu.addAction(actn)
self.dataMapper.addMapping(actn, row)
class Model(QAbstractTableModel):
def __init__(self, parent=None, *args):
QAbstractTableModel.__init__(self, parent, *args)
self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def rowCount(self, parent=QModelIndex()):
return len(self.items)
def columnCount(self, parent=QModelIndex()):
return 1
def data(self, index, role):
if not index.isValid(): return QVariant()
if role == Qt.DisplayRole:
return QVariant(self.items[index.row()])
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid():
if role == Qt.EditRole:
self.items[index.row()]=value
self.dataChanged.emit(index, index)
return True
return False
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
layout=QVBoxLayout(self)
self.setLayout(layout)
tablemodel=Model(self)
tableView=QTableView()
tableView.horizontalHeader().setStretchLastSection(True)
tableView.setModel(tablemodel)
layout.addWidget(tableView)
combo=QComboBox()
combo.setModel(tablemodel)
layout.addWidget(combo)
menuButton=CustomMenuButton(self)
layout.addWidget(menuButton)
menuButton.setModel(tablemodel)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
This is what the QDataWidgetMapper class was designed for. The following blog has a great tutorial covering exactly how to implement it: http://www.yasinuludag.com/blog/?p=98
EDIT: I should add that QDataWidgetMapper works with QWidget and its subclasses, so QAction would not apply. For classes that are not QWidget or its subclasses, you might have to implement your own custom model-view binding.
I want to make the first column with checkbox, and get the check status of that, how can I do that? I override the flag(), but it seems do not work, I am sure where is the problem?
And I got this links, but it does work for my code either. And I don't want to use delegate, because it is too complicated.
data = [['00','01','02'],
['10','11','12'],
['20','21','22']]
class MainWindow(QWidget):
def __init__(self, parent=None, *args):
super(MainWindow, self).__init__(parent)
clipTableWidget = QTableWidget()
self.model = TModel(data, self)
clipTableView = QTableView()
clipTableView.setModel(self.model)
layout = QVBoxLayout()
layout.addWidget(clipTableView)
self.setLayout(layout)
class TModel(QAbstractTableModel):
def __init__(self, datain, parent=None):
super(TModel, self).__init__(parent)
self.arraydata = datain
def rowCount(self, parent=QModelIndex()):
return len(self.arraydata)
def columnCount(self, parent=QModelIndex()):
return len(self.arraydata[0])
def data(self, index, role):
if not index.isValid():
return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
def flags(self, index):
if not index.isValid():
return QVariant()
elif index.column() == 1:
return Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsUserCheckable
return QVariant()
Ok here is the solution to your problem. The reason Why your flags weren't working is because those flags dont work on text , they are meant for QItems .
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class MainWindow(QWidget):
def __init__(self, parent=None, *args):
super(MainWindow, self).__init__(parent)
data = [['00','01','02'],
['10','11','12'],
['20','21','22']]
clipTableWidget = QTableWidget()
self.model = QStandardItemModel(self)
clipTableView = QTableView()
count1=0
for row in data:
count2 = 0
for column in row:
if count2 == 0:
item = QStandardItem(column)
item.setCheckable(True)
item.setCheckState(False)
item.setFlags(Qt.ItemIsUserCheckable| Qt.ItemIsEnabled)
self.model.setItem(count1,count2,item)
count2+=1
else:
item = QStandardItem(column)
self.model.setItem(count1,count2,item)
count2+=1
count1+=1
clipTableView.setModel(self.model)
layout = QVBoxLayout()
layout.addWidget(clipTableView)
self.setLayout(layout)
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()