python - 自定义委托(delegate)项目编辑器小部件出现在错误的位置

标签 python pyqt pyqt5 qtreeview qstyleditemdelegate

我正在尝试使用自定义委托(delegate)来弹出自定义日期和时间编辑器,以便在 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 标志时,小部件位置必须是绝对的,也就是说相对于屏幕,但 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>

关于python - 自定义委托(delegate)项目编辑器小部件出现在错误的位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58070610/

相关文章:

qt - pyqt qt4 QTableView 如何禁用某些列的排序?

python - 在 PyQt5 中使窗口缩放到屏幕大小

python - "replace(*something)"中的星号是做什么用的? (Python)

python - 使用 for 循环打印(大)列表

python - 如何以编程方式从 Python 提升到 Visual Studio 的顶部

python - PyQT5 QComboBox - 获取组合框的值

python - PyQt5:如何使用 QRegularExpression 进行替换

python - celery 记录 : consistent way to log inside and outside of a task

python - 如何检测小部件是否在视线范围内? pyqt

python - 如何依次播放视频的多个片段