自定义委托项编辑器小部件出现在错误的位置
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>
我正在尝试使用自定义委托来弹出自定义日期和时间编辑器,以便在 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>