自定义委托项编辑器小部件出现在错误的位置

Custom delegate item editor widget appearing in the wrong place

我正在尝试使用自定义委托来弹出自定义日期和时间编辑器,以便在 qtreeview 中使用。我读到的所有内容都说,要让编辑器小部件显示在当前索引的正确位置,您需要重新实现 updateEditorGeometry 方法并使用 editor.setGeometry(option.rect) 来设置位置。

虽然我似乎无法弄清楚这一点。这是我到目前为止所拥有的。双击第 1 列时,它会在屏幕的最左上方显示编辑器。

from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import Qt
import datetime


class NspDateEdit(QtWidgets.QWidget):

    editingFinished = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(NspDateEdit, self).__init__(parent)
        # self.resize(137, 25)
        # self.setMaximumSize(QtCore.QSize(120, 25))

        self.date_edit = QtWidgets.QDateEdit()
        self.date_edit.setCalendarPopup(True)
        self.check_box = QtWidgets.QCheckBox()
        self.check_box.setText("")

        self.main_layout = QtWidgets.QHBoxLayout()
        self.main_layout.addWidget(self.date_edit)
        self.main_layout.addWidget(self.check_box)
        self.main_layout.setContentsMargins(2, 0, 0, 0)
        self.setLayout(self.main_layout)

        self.check_box.stateChanged.connect(self._on_state_change)
        self.date_edit.editingFinished.connect(self._on_editingFinished)

        self.setDate(None)

    def _on_state_change(self):
        state = self.checkState()
        if state:
            current_date = self.date_edit.date()
            current_date_py = current_date.toPyDate()
            current_date_str = current_date_py.strftime('%Y-%m-%d')
            if current_date_str == '2000-01-01':
                new_date = datetime.datetime.now().date()
            else:
                new_date = current_date_py
            self.setDate(new_date)
        else:
            self.setDate(None)
        self.editingFinished.emit()

    def _on_editingFinished(self):
        self.editingFinished.emit()

    def checkState(self):
        state = self.check_box.checkState()
        if state == Qt.Checked:
            return True
        else:
            return False

    def setDate(self, val):
        self.date_edit.setEnabled(True)
        if val is None:
            self.check_box.setCheckState(Qt.Unchecked)
            self.date_edit.setEnabled(False)
        else:
            self.date_edit.setDate(val)
            self.check_box.setCheckState(Qt.Checked)

    def date(self):
        if self.checkState():
            return self.date_edit.date().toPyDate()
        else:
            return None


class NspAbstractItemDelegate(QtWidgets.QStyledItemDelegate):

    def __init__(self):
        super(NspAbstractItemDelegate, self).__init__()

    def createEditor(self, parent, option, index):
        editor = NspDateEdit()
        editor.setWindowFlags(QtCore.Qt.Popup)
        return editor

    def setEditorData(self, editor, index):
        data = index.data()
        formatted = datetime.datetime.strptime(data, '%m/%d/%y').date()
        editor.setDate(formatted)

    def setModelData(self, editor, model, index):
        data = editor.date()
        txt = data.strftime('%m/%d/%y')
        model.setData(index, txt)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)


class testTreeview(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(testTreeview, self).__init__(parent)
        self.mainTree = QtWidgets.QTreeView()

        self.testButton = QtWidgets.QPushButton()

        self.lo = QtWidgets.QVBoxLayout()
        self.lo.addWidget(self.mainTree)
        self.lo.addWidget(self.testButton)
        self.setLayout(self.lo)

        self.model = QtGui.QStandardItemModel()
        self.mainTree.setModel(self.model)

        self.populate()

        self.testButton.clicked.connect(self._on_clicked_button)
        self.mainTree.setItemDelegateForColumn(0, NspAbstractItemDelegate())

    def _on_clicked_button(self):
        self.mainTree.edit(self.mainTree.currentIndex())  #, QtWidgets.QAbstractItemView.EditTrigger(), None)

    def populate(self):

        row = [QtGui.QStandardItem('05/12/15'), QtGui.QStandardItem('07/13/18'), ]
        row2 = [QtGui.QStandardItem('12/21/21'), QtGui.QStandardItem('11/05/17'), ]
        all_rows = list(row)
        all_rows.extend(row2)
        for item in all_rows:
            item.setEditable(True)
        root = self.model.invisibleRootItem()
        root.appendRow(row)
        root.appendRow(row2)


if __name__ == "__main__":
    from PyQt5 import QtCore, QtGui, QtWidgets

    app = QtWidgets.QApplication([])
    volume = testTreeview()
    volume.show()
    app.exec_()

当你设置Qt::Popup标志时,widget的位置必须是绝对的,也就是说相对于屏幕,但是option.rect是一个相对于viewport的QRect,所以解决方法是使用 mapToGlobal 将该相对路径转换为绝对路径,但为此您必须将父级传递给编辑器

def createEditor(self, parent, option, index):
    editor = NspDateEdit(<b>parent</b>)
    editor.setWindowFlags(QtCore.Qt.Popup)
    return editor

# ...

def updateEditorGeometry(self, editor, option, index):
    <b>r = QtCore.QRect(option.rect)
    if editor.windowFlags() & QtCore.Qt.Popup and editor.parent() is not None:
        r.setTopLeft(editor.parent().mapToGlobal(r.topLeft()))
    editor.setGeometry(r)</b>