使用PySide2和QTableView,我如何在表视图中使用pandas模型获得多个委托?

问题描述 投票:0回答:1

嗨,我已经尝试了所有我能想到的方法,看了上百个关于表和委托人的堆栈溢出问题,并抓了好几个小时的头,看文档试图理解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_())

所以请有人帮助我理解我缺少什么或不理解什么,我想实现的是添加已被洗刷的委托人,并使其成为一个可编辑的表格,但如果我添加spinbox或复选框委托人,该应用程序冻结和崩溃,所以有一个限制,以多少委托人的表视图可以处理或我做错了什么?任何帮助将是非常感激的,请和感谢你提前...

python qtableview pyside2 qabstracttablemodel
1个回答
1
投票

感谢musicamante,指出了如此freindly我的简单的错误,忽略了明显的太自己的缺失,使所有的代表成员的实例,我已经测试,它的工作原理,所以这里的代码......

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_())
© www.soinside.com 2019 - 2024. All rights reserved.