python - 使用 PySide2 和 QTableView 如何使用 pandas 模型在表格视图中获得多个委托?
问题描述
嗨,我已经尝试了所有我能想到的,并查看了数百个关于表和委托的堆栈溢出问题,并且在查看试图理解 C++ 语言的文档时挠了几个小时,我还没有阅读任何明确说明有表视图可以接受和不接受的数量的限制,现在我希望我可以说我已经对 pyside2 和 pyqt5 中的基本知识有了深刻的理解,尤其是在表格和模型方面,但代表有点令人难以置信,我基于人们的问题,主要来自堆栈溢出,已经走到了这一步,所以这是我第一次尝试寻求帮助..
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_())
所以请有人帮我理解我错过或不理解的内容,我想要实现的只是添加已散列的委托并使其成为可编辑的表格,但如果我添加旋转框或复选框委托应用程序冻结和崩溃,因此表视图可以处理多少委托或我做错了什么是否有限制?任何帮助将不胜感激,并在此先感谢您..
解决方案
感谢 musicamante 如此友好地指出我的简单错误,即忽略了明显的自我缺失以使所有代表成为实例的成员,并且我已经测试并且它有效,所以这里是代码..
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_())
推荐阅读
- c++ - 用于 std::optional<> 的 SWIG Python 包装器
- java - Spring Beans 实现接口的工厂
- java - Spring调度程序根据一天中的时间固定延迟
- audio - 自定义 HLS 段 - 调整 m3u8 - 可能吗?
- apache-spark - 使用缓存扩展堆内存
- java - 如何使用弹簧批处理集成从远程服务器(主服务器)在工作服务器的多个节点上运行/执行作业?
- python-3.x - 跟踪运行 Python Django 的函数的进度
- c - 我试图弄清楚这是否会以某种方式溢出
- fullcalendar - 全日历时间轴保持滚动位置
- ravendb - RavenDb:声明类型的强制序列化不是分配给集合的继承类型