Reputation: 177
I am trying to use a custom delegate to popup custom date and time editors for use inside of a qtreeview. Everything I read says that to get the editor widget to show up in the correct spot, over the current index, you reimpliment the updateEditorGeometry method and use editor.setGeometry(option.rect) to set the position.
I cannot seem to figure this out though. Here is what I have so far. When double clicking on column 1 it shows the editor in the very top left of my screen.
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_()
Upvotes: 3
Views: 764
Reputation: 244142
When you set the Qt::Popup flag, the widget position must be absolute, that is to say with respect to the screen, but option.rect is a QRect relative to the viewport, so the solution is to convert that relative path to absolute using mapToGlobal but for that you must pass a parent to the editor
def createEditor(self, parent, option, index):
editor = NspDateEdit(parent)
editor.setWindowFlags(QtCore.Qt.Popup)
return editor
# ...
def updateEditorGeometry(self, editor, option, index):
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)
Upvotes: 2