QListWidget item editing
-
Qt 5.7. I am using a
QListWidget
, where itsQListWidgetItem
s have theItemIsEditable
flag. At the moment, at least, the items are the standard strings, with the editor being the built-inQLineEdit
, i.e. I am not providing my own editor.I need to know when the user starts editing an item and when he has finished editing an item (e.g. while editing, buttons I have attached to the list widget for adding/deleting items should be disabled).
Call me dumb, but I cannot see signals (or overridable) functions for these?
-
The only signal I can see for starting out is
QListWidget::itemDoubleClicked
. Is that the only way to start editing, and that's what I'm supposed to use? -
Worse for finished editing. There is
QListWidget::itemChanged
, which seems great if the user makes a change and clicks elsewhere or whatever --- I receive that one. -
But what if the user does not change the item? He might do anything, e.g. click some other widget, tab away, I have no idea. How do I know what the
QLineEdit
seems to know when it takes the user out of editing mode and no change has been made?
-
-
@VRonin
Well, I followed your principle of having theQStyledItemDelegate
emit signals with theQModelIndex
as a parameter.I diverged from yours slightly. Given that I'm now overriding
destroyEditor()
so as to access the index, it seems to me that I might as well be consistent and overridecreateEditor()
for beginning editing. So the following two overrides:class JEditableListStyledItemDelegate(QtWidgets.QStyledItemDelegate): # class variable for "editStarted" signal, with QModelIndex parameter editStarted = QtCore.pyqtSignal(QtCore.QModelIndex, name='editStarted') # class variable for "editFinished" signal, with QModelIndex parameter editFinished = QtCore.pyqtSignal(QtCore.QModelIndex, name='editFinished') def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex): editor = super().createEditor(parent, option, index) if editor is not None: self.editStarted.emit(index) return editor def destroyEditor(self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex): self.editFinished.emit(index) return super().destroyEditor(editor, index)
The above seems to give me fairly reliable signals for what I want, e.g. disable other buttons for doing things to
QListWidget
(add row, delete row) while the user is editing a row.Seem reasonable to you? Thanks for all your input.
-
That's because it's handled by the delegate.
SubclassQStyledItemDelegate
class SignalItemDelegate : public QStyledItemDelegate{ Q_OBJECT Q_DISABLE_COPY(SignalItemDelegate) public: explicit SignalItemDelegate(QObject* parent = Q_NULLPTR):QStyledItemDelegate(parent){ QObject::connect(this,&SignalItemDelegate::closeEditor,this,&SignalItemDelegate::editFinished); } void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE{ editStarted(); return QStyledItemDelegate::setEditorData(editor,index); } Q_SIGNALS: void editStarted(); void editFinished(); };
And then call something like:
SignalItemDelegate* delegate = new SignalItemDelegate(listWidget); QObject::connect(delegate,&SignalItemDelegate::editStarted,[](){qDebug("edit started")}); QObject::connect(delegate,&SignalItemDelegate::editFinished,[](){qDebug("edit finished")}); listWidget->setItemDelegate(delegate);
-
@VRonin
Ah ha! Your famousQStyledItemDelegate
, which I have steadfastly not got into so far! OK, let me go set this up and see where I get. I may have further questions for what I'm trying to actually achieve fully (the above is just one part), I'll come back to your expertise on that if I may.... -
Did you get a chance to look other set of signals like.
currentItemChanged(..) clicked(..) entered(..) pressed(..)
-
@dheerendra
Yep, I looked through all those. None of them pick up, e.g., terminating editing, at least, so no good.... -
terminatingEditing has to be custom. You have no choice other the your own delegate :)
-
@dheerendra
Yep, that's fine, as I wrote to @VRonin above, I am presently having a go at his code (converted to Python/PyQt!) to get me going.... -
@VRonin
OK, I have come up with the basics which I believe emulates/corresponds to your C++ as best I can. I now have a further question about something which you don't do in your example which I would like.First, I'll paste the guts of my Python/PyQt code. You'll see it's rather different in signals/slots/emits. This may help or hinder, I don't know, feel free to ignore if that's best.
class JEditableListStyledItemDelegate(QtWidgets.QStyledItemDelegate): # class variable for "editStarted" signal editStarted = QtCore.pyqtSignal(name='editStarted') # class variable for "editFinished" signal editFinished = QtCore.pyqtSignal(name='editFinished') def __init__(self, parent: QtCore.QObject=None): super().__init__(parent) self.closeEditor.connect(self.editFinished) def setEditorData(self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex): self.editStarted.emit() return super().setEditorData(editor, index) class JEditableListWidget(QtWidgets.QListWidget): # class variable for "editStarted" signal editStarted = QtCore.pyqtSignal(name='editStarted') # class variable for "editFinished" signal editFinished = QtCore.pyqtSignal(name='editFinished') def __init__(self, parent: QtWidgets.QWidget=None): super().__init__(parent) styledItemDelegate = JEditableListStyledItemDelegate(self) styledItemDelegate.editStarted.connect(self.editStarted) styledItemDelegate.editFinished.connect(self.editFinished) self.setItemDelegate(styledItemDelegate)
The above "works". The
JEditableListStyledItemDelegate
emits its owneditStarted
/Finished
signals. TheJEditableListWidget
"redirects" those signals to its owneditStarted
/Finished
signals, for the outside world to slot onto. So far so good?Now, if you look at a
QListWidget
signal for an item like http://doc.qt.io/qt-5/qlistwidget.html#itemChanged, its signature isQListWidget::itemChanged(QListWidgetItem *item)
. Hence it sends to the outside world a parameter of theQListWidgetItem
which has been changed. MyeditStarted
/Finished
signals should do the same.Now I get stuck as to how I'm supposed to do that.
I start by changing my
JEditableListWidget
signals to:class JEditableListWidget(QtWidgets.QListWidget): # class variable for "editStarted" signal, including item editStarted = QtCore.pyqtSignal(QtWidgets.QListWidgetItem, name='editStarted') # class variable for "editFinished" signal, including item editFinished = QtCore.pyqtSignal(QtWidgets.QListWidgetItem, name='editFinished')
The extra first parameter of
QtWidgets.QListWidgetItem
declares the signal as forwarding the item as a parameter.Now how do I pass that? I believe that (a) I will need a lambda for the signal and (b) I need to know what item is being edited. I think where I had:
styledItemDelegate.editStarted.connect(self.editStarted)
I now need something like:
styledItemDelegate.editStarted.connect(lambda: self.editStarted(someItemWidget))
[Actually I suspect it might be more like:
styledItemDelegate.editStarted.connect(lambda: self.editStarted.emit(someItemWidget))
]
Could you tell me:-
Is this how you would do it in C++ (e.g. with a lambda)? (If not, I may be doing it wrong.) Could you show me your C++ for how you would do this and I will attempt to figure it to PyQt? (Also, I believe your code omitted an
emit
because I know that's a NO-OP in C++; but could you include it wherever it should be, because in PyQt you can see we have to useemit
as a function.) -
How do I calculate what the
someItemWidget
will be, from inside myJEditableListWidget
?
-
-
It's actually super easy.
In the delegate:- add an argument of type
QtCore.QModelIndex
to the signals. - remove
self.closeEditor.connect(self.editFinished)
- change
self.editStarted.emit()
toself.editStarted.emit(index)
- add
def destroyEditor(self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex): self.editFinished.emit(index) return super().destroyEditor(editor, index)
Now in
JEditableListWidget
you can connect a slot that accepts aQtCore.QModelIndex
. To find the correspondingQListWidgetItem
you can just callitem(index.row())
- add an argument of type
-
@VRonin
TVM. I'll implement next week. You're passing theQtCore.QModelIndex
available in theJEditableListStyledItemDelegate
class up the signal chain to indicate whichQListWidgetItem
was acted upon. I thought theJEditableListWidget
class would know which item in the list was being edited, but seemingly not? -
@VRonin
Well, I followed your principle of having theQStyledItemDelegate
emit signals with theQModelIndex
as a parameter.I diverged from yours slightly. Given that I'm now overriding
destroyEditor()
so as to access the index, it seems to me that I might as well be consistent and overridecreateEditor()
for beginning editing. So the following two overrides:class JEditableListStyledItemDelegate(QtWidgets.QStyledItemDelegate): # class variable for "editStarted" signal, with QModelIndex parameter editStarted = QtCore.pyqtSignal(QtCore.QModelIndex, name='editStarted') # class variable for "editFinished" signal, with QModelIndex parameter editFinished = QtCore.pyqtSignal(QtCore.QModelIndex, name='editFinished') def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex): editor = super().createEditor(parent, option, index) if editor is not None: self.editStarted.emit(index) return editor def destroyEditor(self, editor: QtWidgets.QWidget, index: QtCore.QModelIndex): self.editFinished.emit(index) return super().destroyEditor(editor, index)
The above seems to give me fairly reliable signals for what I want, e.g. disable other buttons for doing things to
QListWidget
(add row, delete row) while the user is editing a row.Seem reasonable to you? Thanks for all your input.
-
@JonB said in QListWidget item editing:
Given that I'm now overriding destroyEditor() so as to access the index, it seems to me that I might as well be consistent and override createEditor() for beginning editing.
Only doubt I have is if the delegate recycles the editor. Try going from editing 1 cell to editing another one directly and see if both create and destroy are called or it just calls
setEditorData
-
@VRonin
OK, thanks! You should have said that concern was why you had overridden different/"inconsistent" methods, I had no idea why! Tried with direct click to edit another item, all is well, does call destroy and then create.So I shall mark what I did as the solution to my original question. TVM.
P.S.
Your Xmas hat looks good. But a lot of others are also wearing them...! :) -
@JonB said in QListWidget item editing:
Your Xmas hat looks good. But a lot of others are also wearing them...! :)
Well that is is on purpose :)