为什么我的 QStyleItemDelegate 会在新窗口中而不是在我的 TableView 中弹出?

问题描述

我正在尝试使用 Pyside2 创建一个非常简单的应用程序,该应用程序在用户可以编辑的表中显示一些数据。我希望表格的单元格根据表格中的其他值自动完成,因此我在 this tutorial 之后实现了一个自定义委托。这非常简单,并且委托按预期工作除了出于某种原因委托在新窗口中弹出而不是附加到表格中的单元格,如下所示示例(自定义委托在最后定义):

import pandas as pd
from PySide2 import QtWidgets,QtCore
from PySide2.QtCore import Qt


class TableView(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setObjectName(u"TableView")
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName(u"centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName(u"verticalLayout")
        self.frame = QtWidgets.QFrame(self.centralwidget)
        self.frame.setObjectName(u"frame")
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
        self.verticalLayout_2.setObjectName(u"verticalLayout_2")
        
        self.tableView = QtWidgets.QTableView(self.frame)
        self.tableView.setObjectName(u"tableView")
        delegate = QAutoCompleteDelegate(self.tableView)
        self.tableView.setItemDelegate(delegate)
        self.tableModel = TableModel(self.tableView)
        self.tableView.setModel(self.tableModel)

        self.verticalLayout_2.addWidget(self.tableView)
        self.verticalLayout.addWidget(self.frame)

        self.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName(u"statusbar")
        self.setStatusBar(self.statusbar)

        QtCore.QMetaObject.connectSlotsByName(self)
        
        
class TableModel(QtCore.QAbstractTableModel):

    def __init__(self,parent=None):
        super(TableModel,self).__init__(parent=parent)
        self._data = pd.DataFrame([["A0","B0","C0"],["A1","B1","C1"],["A2","B2","C2"]],columns=["A","B","C"])

    def data(self,index,role):
        # Should only ever refer to the data by iloc in this method,unless you
        # go specifically fetch the correct loc based on the iloc
        row = index.row()
        col = index.column()
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return self._data.iloc[row,col]

    def rowCount(self,index):
        return len(self._data)

    def columnCount(self,index):
        return len(self._data.columns)

    def headerData(self,col,orientation,role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]
        return None
    
    def unique_vals(self,index):
        """ Identify the values to include in an autocompletion delegate for the given index """
        column = index.column()
        col_name = self._data.columns[column]
        return list(self._data[col_name].unique())

    def setData(self,value,role):
        if role != Qt.EditRole:
            return False
        row = self._data.index[index.row()]
        column = self._data.columns[index.column()]
        self._data.loc[row,column] = value
        self.dataChanged.emit(index,index)
        return True

    def flags(self,index):
        flags = super(self.__class__,self).flags(index)
        flags |= Qt.ItemIsEditable
        flags |= Qt.ItemIsSelectable
        flags |= Qt.ItemIsEnabled
        flags |= Qt.ItemIsDragEnabled
        flags |= Qt.ItemIsDropEnabled
        return flags


class QAutoCompleteDelegate(QtWidgets.QStyledItemDelegate):

    def createEditor(self,parent: QtWidgets.QWidget,option: QtWidgets.QStyleOptionViewItem,index: QtCore.QModelIndex) -> QtWidgets.QWidget:
        le = QtWidgets.QLineEdit()
        test = ["option1","option2","option3"]
        complete = QtWidgets.QCompleter(test)
        le.setCompleter(complete)
        return le

    def setEditorData(self,editor:QtWidgets.QLineEdit,index:QtCore.QModelIndex):
        val = index.model().data(index,Qt.EditRole)
        options = index.model().unique_vals(index)
        editor.setText(val)
        completer = QtWidgets.QCompleter(options)
        editor.setCompleter(completer)

    def updateEditorGeometry(self,editor: QtWidgets.QWidget,index: QtCore.QModelIndex):
        editor.setGeometry(option.rect)


app = QtWidgets.QApplication()
tv = TableView()
tv.show()
app.exec_()

显示单元格处于活动状态时表格行为的图像

从我读过的所有内容来看,自定义委托上的 updateEditorGeometry 方法应该将其锚定到活动单元格,但在我的情况下,某些事情显然没有按预期工作。而且我仍在学习 Qt,所以我不能声称了解它在做什么。有没有人可以解释这里发生的事情?非常感谢。

  • 操作系统:Ubuntu 20.04
  • Python 3.8.5
  • PySide2 5.15.0
  • Qt 5.12.9

解决方法

任何没有父级的小部件都将是一个窗口,并且由于 QLineEdit 没有父级,因此您可以观察 OP 指示的行为,因此 createEditor 方法将“父级”作为参数。

另一方面,最好创建一个自定义的 QLineEdit,它可以轻松实现通过模型向 QCompleter 添加建议的处理。

class AutoCompleteLineEdit(QtWidgets.QLineEdit):
    def __init__(self,parent=None):
        super().__init__(parent)
        self._completer_model = QtGui.QStandardItemModel()
        completer = QtWidgets.QCompleter()
        completer.setModel(self._completer_model)
        self.setCompleter(completer)

    @property
    def suggestions(self):
        return [
            self._completer_model.item(i).text()
            for i in range(self._completer_model.rowCount())
        ]

    @suggestions.setter
    def suggestions(self,suggestions):
        self._completer_model.clear()
        for suggestion in suggestions:
            item = QtGui.QStandardItem(suggestion)
            self._completer_model.appendRow(item)


class QAutoCompleteDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(
        self,parent: QtWidgets.QWidget,option: QtWidgets.QStyleOptionViewItem,index: QtCore.QModelIndex,) -> QtWidgets.QWidget:
        le = AutoCompleteLineEdit(parent)
        return le

    def setEditorData(self,editor: QtWidgets.QWidget,index: QtCore.QModelIndex):
        val = index.model().data(index,Qt.EditRole)
        options = index.model().unique_vals(index)
        editor.setText(val)
        editor.suggestions = options

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...