使用 PySide2 和 QTableView,如何使用 Pandas 模型在表格视图中获得多个委托? [英] With PySide2 and QTableView how do i get multiple delegates in table view using pandas model?

查看:93
本文介绍了使用 PySide2 和 QTableView,如何使用 Pandas 模型在表格视图中获得多个委托?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经尝试了我能想到的所有方法,并查看了数百个关于表和委托的堆栈溢出问题,并花了几个小时查看试图理解 C++ 语言的文档,但我没有清楚地阅读任何内容声明代表表视图可以接受和不接受的数量有限制,现在我希望我可以说我已经对 pyside2 和 pyqt5 中的基本知识有了深入的了解,尤其是对于表格和模型,但代表们有点介意令人难以置信的是,我已经根据人们的问题(主要来自堆栈溢出)走了这么远,所以这是我第一次尝试寻求帮助..

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..

推荐答案

感谢 musicamante 友好地指出了我的一个简单错误,即忽略了太自我缺失的明显问题,以至于我已经测试过该实例的所有代表成员它的工作原理是代码..

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

这篇关于使用 PySide2 和 QTableView,如何使用 Pandas 模型在表格视图中获得多个委托?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆