I got the following problem:
I have a GUI with a qtableview, a generic delegate (as in the book of Marc Summerfield) and a datamodel for that.
I designed a Dialog which shows up, if i want to change certain columns. There is a label, a combobox and a button in the dialog.
Until now, everything works fine, selection can be done, data can be processed.
But there is no possibility to set the position where the dialog pops up. So sometimes it is simply un-operatable (almost off the screen and so on)
So my question:
How can I set the position, where the dialog pops up?
Minimum example:
The generic delegate
class GenericDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(GenericDelegate, self).__init__(parent)
self.delegates = {}
def insertColumnDelegate(self, column, delegate, **kwargs):
delegate.setParent(self)
self.delegates[column] = delegate
def removeColumnDelegate(self, column):
if column in self.delegates:
del self.delegates[column]
def paint(self, painter, option, index):
delegate = self.delegates.get(index.column())
if delegate is not None:
delegate.paint(painter, option, index)
else:
QItemDelegate.paint(self, painter, option, index)
def createEditor(self, parent, option, index):
delegate = self.delegates.get(index.column())
if delegate is not None:
return delegate.createEditor(parent, option, index)
else:
return QStyledItemDelegate.createEditor(self, parent, option, index)
def setEditorData(self, editor, index):
delegate = self.delegates.get(index.column())
if delegate is not None:
delegate.setEditorData(editor, index)
else:
QStyledItemDelegate.setEditorData(self, editor, index)
def setModelData(self, editor, model, index):
delegate = self.delegates.get(index.column())
if delegate is not None:
delegate.setModelData(editor, model, index)
else:
QStyledItemDelegate.setModelData(self, editor, model, index)
def setDelegateData(self, index, data):
delegate = self.delegates.get(index.column())
if delegate is not None and hasattr(delegate, "setDelegateData"):
delegate.setDelegateData(data)
else:
pass
The columns delegate
class ComboBoxColumnDelegate(QStyledItemDelegate):
def __init__(self, dialog, editordata=[], parent=None, target="Folder"):
super().__init__(parent)
self.editor = dialog
self.parent = parent
self.editordata = editordata
self.target = target
def createEditor(self, parent, option, index):
editor = self.editor(parent=self.parent, target = self.target, values=self.editordata)
return editor
def setDelegateData(self, data = {}):
if not data:
return 0
for key, value in data.items():
if hasattr(self, key):
setattr(self, key, value)
def setEditorData(self, editor, index):
editor.setData(self.editordata)
def setModelData(self, editor, model, index):
if editor.result() == QDialog.Accepted:
model.setData(index, editor.selectedChoice(), Qt.EditRole)
For the dialog, a normal QDialog can be taken, since only the position of it matters.
In the mainwindow I initialize the delegate:
class mw(QMainWindow)
def __init(self, parent=None):
super(mw, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self._init_testtablemodel()
....
def _init_testtablemodel(self):
self.testmodel = testtablemodel(self)
self.ui.tableView_tests.setModel(self.testmodel)
# generic delegate
test_delegate = GenericDelegate()
# Insert the delegates for the columns
test_delegate.insertColumnDelegate(0, ComboboxColumnDelegate())
...
self.ui.tableView_tests.setItemDelegate(test_delegate)
The model is as recommended:
class testtablemodel(QAbstractTableModel)
def __init__(self, parent=None)
...
def rowCount(self, index):
...
def columnCount(self, index):
...
def data(self, index, role):
...
def setData(self, index, value, role):
...
def headerData(self, section, orientation, role):
...
def flags(self, index):
...
...
As mentioned befor, all data processing works perfectly nice, but the position of the editor poping up is bad.
I hope, the basic structure of the program is understandable.
Edit:
Changed the QItemDelegate to QStyledItemDelegate, which works as well and makes no difference so far.
I am trying to update my model when a combo box is changed. I have a function connected to the combobox that updates the model but from this question - "the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good)". I also want the change of the combobox to affect what appears in a QLineEdit.
QItemDelegate:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
comboBox.currentIndexChanged.connect(lambda: self.comboBoxChanged(comboBox, index))
return comboBox
return super().createEditor(parent, option, index)
# updates model but does not update view - shows what I want to happen
def comboBoxChanged(self, comboBox, index):
if comboBox.currentText() == "1":
index.model()._data[index.row()][0] = "1"
index.model()._data[index.row()][1] = "One"
elif comboBox.currentText() == "2":
index.model()._data[index.row()][0] = "2"
index.model()._data[index.row()][1] = "Two"
# self.dataChanged.emit(index, index) # can't call this here
QAbstractTableModel:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
return super().flags(index) | Qt.ItemIsEditable
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
Main:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView()
data = [["1", "One"]]
self.model = TableModel(data)
self.table.setModel(self.model)
self.table.setItemDelegate(Delegate())
self.print_data = QPushButton("Print data")
self.print_data.clicked.connect(self.printData)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index)
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.print_data)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def printData(self):
print(self.model._data)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
How can I register the change in the qcombobox to correctly update the model?
and
How do I make this trigger the change in qlineedit?
The data of a model should be preferably updated by using the existing setData function, which provides a centralized interface that avoids confusing bugs and mistakes.
In your case, you need to set the data of the sibling index, which is the index at the same row in the next column (no need to set the index value itself, as Qt is able to get it automatically from the combo, and it should be done in setModelData() anyway).
def comboBoxChanged(self, comboBox, index):
if comboBox.currentText() == "1":
value = "One"
elif comboBox.currentText() == "2":
value = "Two"
sibling = index.sibling(index.row(), 1)
index.model().setData(sibling, value)
Just to be clear, your code could have worked, but dataChanged is a signal of the model, not the delegate, so it should have been:
index.model().dataChanged.emit(index, index)
But, as already said, it's better to avoid using different ways of changing data in the model, especially if done from outside it. The major benefit of having a single function that takes care of that is that whenever something is going to change in the structure and implementation of the model the only thing left to check is all the occurrences of that function call (in case the column order changes, for example).
I am trying to add a row to QTableView with a QAbstractTableModel and QItemDelegate where the widgets appear in the added row. From what I've read I need to call .edit(index) on each item of the added row to call createEditor where the widgets are created however I am getting edit: editing failed
QItemDelegate:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
# COMBOBOX, LINEEDIT, TIMEDIT
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
return comboBox
elif index.column() == 1:
lineEdit = QLineEdit(parent)
return lineEdit
elif index.column() == 2:
timeEdit = QTimeEdit(parent)
return timeEdit
def setEditorData(self, editor, index):
value = index.model()._data[index.row()][index.column()]
if index.column() == 0:
editor.setCurrentIndex(self.type_items.index(value))
elif index.column() == 1:
editor.setText(str(value))
elif index.column() == 2:
editor.setTime(value)
QAbstractTableModel:
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
pass
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
Main:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView(localWidget)
data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]
self.model = TableModel(data)
self.table.setModel(self.model)
self.table.setItemDelegate(Delegate())
self.add_row = QPushButton("Add Row", localWidget)
self.add_row.clicked.connect(self.addRow)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index)
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.add_row)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def addRow(self):
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.beginInsertRows(QModelIndex(), self.model.rowCount(), self.model.rowCount())
self.model._data.append(new_row_data)
self.model.endInsertRows()
for i in range(len(new_row_data)):
index = self.table.model().index(self.model.rowCount()-1, i)
self.table.edit(index)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
How can I trigger the QItemDelegate's createEditor to create the widgets for the added row's items and setEditorData to populate them?
First of all, you could use openPersistentEditor as you already did in the __init__.
There are two reasons for the error in self.table.edit():
in order to allow editing of an item through edit(), the flags() function must be overridden and provide the Qt.ItemIsEditable flag;
you cannot call edit() multiple times on different indexes within the same function;
Then, there are other important problems in your code:
you're not correctly using the model, as data() is not implemented;
the delegates should use the base functions of the model whenever they provide standard behavior (accessing index.model()._data is not good);
setEditorData is unnecessary, as long as the above aspects are respected: Qt automatically sets the data based on the data type and the editor class;
setData() should be implemented in order to correctly set the data on the model, otherwise the new data won't be accessible;
changes in the model structure (like creating a new row) should be done in the model class;
Here is a revised version of your code:
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
for text in self.type_items:
comboBox.addItem(text, (index.row(), index.column()))
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
# no setEditorData() required
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def appendRowData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._data.append(data)
self.endInsertRows()
def data(self, index, role=Qt.DisplayRole):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
# allow editing of the index
return super().flags(index) | Qt.ItemIsEditable
class MainWindow(QMainWindow):
# ...
def addRow(self):
row = self.model.rowCount()
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.appendRowData(new_row_data)
for i in range(self.model.columnCount()):
index = self.model.index(row, i)
self.table.openPersistentEditor(index)
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_())