Required signal/slot plumbing for a model/view interface using built-in Qt objects
-
Having looked through a lot of documentation, manuals, etc I (think I) understand the concepts involved with the model/view architecture (in this case also using a proxy to edit the data). But what I can't seem to get out of any source is how the signal/slot interface is connected for a given model and view. The documentation lists multiple signals and slots, but they all seem to have different interfaces, and I don't find ones that match. It's as if there must be 'hidden' signals and/or slots
So in my situation for example, I have a QTreeView and a QStandardItemModel (staying with nice, safe, prepackaged types). The model actually contains a collection of DataType definitions (which are in essence nested QVariant structures) and the view is there to display the definition and allow the user to alter both the type itself (i.e. the variable declaration) and the value contained in the DataType for a particular instance. DataTypes themselves are possibly composite collections of at least 1 element, where an element may be a scalar such as int, double, bool, etc., an array (of DataTypes each of the same type), a union (of different DataTypes) or a struct (of at least one DataType). The TreeView should display the complete hierarchical definition of the DataType, each element having an Name and an associated Value. The Value is either displayed directly if it is a scalar, or else given a string literal: "Union", "Array", or "Struct" if at that level of the hierarchy there is further decomposition. The user can edit both the Name and Value, as well as adding elements to any level of the hierarchy (or deleting them therefrom).
To handle the translation of the Value from an internally-represented type in the QVariant into a string representation I am using a QStyledItemDelegate. The delegate of course handles the editing as well - including, amongst other things, triggering a dialogue if the user adds an element asking what type of element: scalar, array, union, or struct, they want to insert.
But now when I look at the signals and slots exposed by QTreeView, QStandardItemModel, and QStyledItemDelegate, it's far from clear how any of them inform the other about what's happening.
What I would expect is that I or some other mechanism needs to hook up:
A signal from the view to the delegate, indicating that the user has entered an item for editing.
A signal from the view to the delegate, indicating that the user has asked to add a row (a data element)
A signal from the view to the delegate, indicating that the user has asked to delete a row.
A signal from the delegate to the model, indicating that an item has been edited.
A signal from the delegate to the model, indicating that a row has been added.
A signal from the delegate to the model, indicating that a row has been deleted.
A signal from the model to the delegate, indicating that the added row has been committed.
A signal from the model to the delegate, indicating that the deleted row has been removed.
A signal from the delegate to the view, redrawing the view if a row has been added.
A signal from the delegate to the view, redrawing the view if a row has been deleted.
A signal from the delegate to the containing application, indicating that a row is being inserted.
A signal from the containing application or its dialogue, indicating that a datatype has been selected.The last 2 of these are used to bring up the selection dialogue for the datatype to insert and receive its value. I think I need to code these myself which isn't a problem because I've got complete control over the delegate.
Maybe more or fewer signals need to be there.
Looking at candidate signals from the view, doubleClicked seems suitable for an edit operation. Neither add nor delete rows seem to have any obvious signal from the view, although I can always provide one using a QPushButton on the main form. However if the QPushButton is to provide anything intelligent, it needs to know the index the user is currently in within the view. This suggests that the entered signal needs to be emitted by the view and caught by the delegate, then used to set a state which keeps track of the user's current position, so that when a button is clicked on the main form the delegate can inform the model at which index to perform the insert or remove operation. Is this a sensible solution? To me it seems a bit klunky.
It seems all signals from the delegate to the model have to be implemented by me. The QObject signals are clearly not relevant. So there at least I have potentially full control of what is sent where by whom.
I can emit either itemChanged or DataChanged from the model to inform the delegate an item has been edited, and rowsInserted/rowsRemoved respectively for the row insert/delete operations.
But now we come to the problem of slots and what each object would appear to need. From QTreeView doubleClicked and entered will both give me a QModelIndex. But setEditorData and setModelData in the delegate both also ask for a QWidget* editor, and its function createEditor likewise expect a QWidget* parent. How will createEditor be able to access the parent QWidget? Is this somehow automatically passed and/or stored internally to the object when you call setItemDelegate? Who's going to call the QStyledItemDelegate's member functions? I note also createEditor expects a QStyleOptionViewItem. Where's that going to be retrieved from?
QStandardItemModel doesn't have any slots that can receive the QModelIndex which one might expect to pass in from the delegate. I don't see how setModelData can distinguish between an edit and a row insertion/deletion, which seems to limit its use to edits. OK, that's one less signal we might have to pass but still you have the cases where a row is inserted or deleted. Does that mean subclassing QStandardItemModel and adding some appropriate slots? I'm guessing the answer is yes.
Next, the documentation says rowsInserted and rowsRemoved says these are 'a private signal. It can be used in signal connections but cannot be emitted by the user'. But the documentation for the insertRows and removeRows functions indicates: 'The base class implementation of this function does nothing and returns false'. So under what conditions are these signals ever emitted, if at all? If the base class implementation does nothing but users can't emit these signals then it would seem they can never in practice be emitted.
If somehow they do get emitted, and the delegate catches them, then it can issue the signals to rowsInserted/rowsRemoved in the view. It's not clear what it must do in terms of low-level paint operations though - I know that with a Value item that is an array, union, or struct it needs to paint out that text and the actual value for scalars, but when something has been added or deleted I'm not sure how much of that is redrawn automatically, and if so, if the view directly calls the delegate's paint function anyway.
All in all it looks likely that a lot of things are probably done implicitly, but what is and is not done in this way, or how, is not made clear in the documentation that I've been able to look through, so it's nearly impossible for me to know what is going to be handled by the provided Qt implementation and what I have to implement myself. What do I need to do and why? It would be enormously helpful, incidentally, if someone can provide a diagram illustrating the existing signal-slot-function connections within the model/view/delegate framework (i.e. what Qt provides and what you have to do manually). Thanks for any help.
-
TL;DR: You are worrying too much. Qt takes care automatically of (almost) all your problems
I understand the concepts involved with the model/view architecture
I think you misunderstood fundamentally the role of the delegate. On the other hand, if it makes you feel better, nobody understand those concepts, not even the guys who wrote the code 😉
A signal from the view to the delegate, indicating that the user has entered an item for editing.
This is not handled via a signal, the view will directly call
QAbstractItemDelegate::createEditor
andQAbstractItemDelegate::setEditorData
A signal from the view to the delegate, indicating that the user has asked to add a row (a data element)
A signal from the view to the delegate, indicating that the user has asked to delete a row.
A signal from the delegate to the model, indicating that a row has been added.
A signal from the delegate to the model, indicating that a row has been deleted.
A signal from the delegate to the containing application, indicating that a row is being inserted.Neither the view nor the delegate is the thing that does this, the signal comes from the model
QAbstractItemModel::rowsInserted
/QAbstractItemModel::rowsRemoved
A signal from the delegate to the model, indicating that an item has been edited.
There is no signal involved, the delegate takes care of changing the data directly inside
QAbstractItemDelegate::setModelData
A signal from the model to the delegate, indicating that the deleted row has been removed.
???
A signal from the model to the delegate, indicating that the added row has been committed.
In general, you don't commit a row but each single cell
A signal from the delegate to the view, redrawing the view if a row has been added.
A signal from the delegate to the view, redrawing the view if a row has been deleted.Again it doesn't come from the delegate but from the model. This is automatically setup when you call
QAbstractItemView::setModel
A signal from the containing application or its dialogue, indicating that a datatype has been selected
The containing application is not part of the model/view framework so you are free to design it however you want
Looking at candidate signals from the view, doubleClicked seems suitable for an edit operation.
You do not need to do it manually, just set the
QAbstractItemView::setEditTriggers
and Qt will take care of deciding when editing is startedNeither add nor delete rows seem to have any obvious signal from the view
It should not, the model does that, not the view
it needs to know the index the user is currently in within the view
you can use the selection model for this.
QAbstractItemView::selectionModel
and thenQAbstractItemSelectionModel::currentIndex
This suggests that the entered signal needs to be emitted by the view and caught by the delegate
Qt takes care of this part for you, don't need to worry
It seems all signals from the delegate to the model have to be implemented by me.
You don't need signals between those two, the delegate is supposed to act on the model directly
I can emit either itemChanged or DataChanged from the model to inform the delegate an item has been edited, and rowsInserted/rowsRemoved respectively for the row insert/delete operations.
The model will do it for you, you don't need to emit anything, (
itemChanged
isQStandardItemModel
specific)But setEditorData and setModelData in the delegate both also ask for a QWidget* editor, and its function createEditor likewise expect a QWidget* parent.
Those are not slots. The view will call those methods directly whenever it needs them passing all the arguments. You don't need to implement anything
QStandardItemModel doesn't have any slots that can receive the QModelIndex which one might expect to pass in from the delegate.
It does indeed, it inherits the entire
QAbstractItemModel
interface.I don't see how setModelData can distinguish between an edit and a row insertion/deletion
It doesn't. those are 3 different operations and they are handled 1 by 1
Does that mean subclassing QStandardItemModel
Subclassing a model is one of the most involved things you can do in Qt, plenty of traps one can easily fall into. Don't go on that path if you can avoid it (and you almost always do).
rowsInserted and rowsRemoved says these are 'a private signal
You should really read "protected" instead of "private". The subclasses can emit it but they can't be emitted from outside the class
So under what conditions are these signals ever emitted, if at all?
By the subclasses. It's the power of polymorphism
If somehow they do get emitted, and the delegate catches them, then it can issue the signals to rowsInserted/rowsRemoved in the view.
The view catches them directly and automatically, the delegate has no role here.
It's not clear what it must do in terms of low-level paint operations
You don't have to do anything, it is all taken care of by Qt
how much of that is redrawn automatically
100%
the view directly calls the delegate's paint function anyway
correct
I would suggest:
-
@VRonin said in Required signal/slot plumbing for a model/view interface using built-in Qt objects:
TL;DR: You are worrying too much. Qt takes care automatically of (almost) all your problems
I understand the concepts involved with the model/view architecture
OK, you've clarified some things but as you might guess this brings up some new questions.I think you misunderstood fundamentally the role of the delegate. On the other hand, if it makes you feel better, nobody understand those concepts, not even the guys who wrote the code 😉
That's possible. To my understanding the role of a delegate seems to be to change the behaviour of a view away from the default implementation, when you don't want to implement a custom view (which I definitely don't, at least not yet). This applies particularly to editing, but in general when you want some sort of display that differs from a straight text presentation. For example one of the things I'd like to do in the view is that when the user clicks on a particular field that selects the datatype they want to use, the view gives them a combo-box to select the type - but the underlying data that will be used is the QMetaType's type, and meanwhile when not being edited the type field shows the name of the type stored in the metatype system.
A signal from the view to the delegate, indicating that the user has entered an item for editing.
This is not handled via a signal, the view will directly call
QAbstractItemDelegate::createEditor
andQAbstractItemDelegate::setEditorData
Yes, this is not made clear. What I've read so far implied that communication between models, views and delegates was via signals and slots but OK, createEditor and setEditor data are then more like callbacks than something you have to invoke yourself.
A signal from the view to the delegate, indicating that the user has asked to add a row (a data element)
A signal from the view to the delegate, indicating that the user has asked to delete a row.
A signal from the delegate to the model, indicating that a row has been added.
A signal from the delegate to the model, indicating that a row has been deleted.
A signal from the delegate to the containing application, indicating that a row is being inserted.Neither the view nor the delegate is the thing that does this, the signal comes from the model
QAbstractItemModel::rowsInserted
/QAbstractItemModel::rowsRemoved
At some point, though, the model needs to be told to insert or remove rows. The user is the one who is inserting rows in the form dialogue, and presumably they're interacting through the view. As noted it appears as though a button-based interface with buttons 'insert' and 'remove' seems most suitable. But whatever, the model, I think, can't know rows are to be inserted or removed until something tells it that's going to happen. The delegate's predefined (apparently callback) methods don't provide an insertRows or deleteRows interface so presumably that's something I need to add somewhere - whether from the view or the delegate. But I was guessing putting it in the delegate made more sense as 1) it's being asked to handle editing; 2) otherwise I'd have to subclass QTreeView, and I'm eager to avoid subclassing the prebuilt types unless absolutely necessary.
By routing these through a delegate, I can also do things like, e.g. pop up a dialogue asking the user to confirm they want to delete a certain number of rows (and that any subrows beneath them will likewise be removed). In some cases, adding a row also will mean a subrow should be automatically added (because the user selected a composite datatype which has subelements). In others, there will be implications for the display, because if the user deleted a member of a union that had been set as the active member, it needs to select a different union member of the available ones as the active one (under the user's direction) and change the font style.
So how is the model 'usually' informed that it should insert or delete rows?
Incidentally, maybe you can answer this as well. If I use the QStandardItemModel's insertRows() method at the top level of the model (i.e. at a point where parent will be an invalid QModelIndex), as long as I have setColumnCount to some value, will it insert that number of columns, even for an initially empty model (i.e. there are no rows yet)? I'd hope so but the documentation is a bit vague on what happens with empty models when you insert rows.
A signal from the delegate to the model, indicating that an item has been edited.
There is no signal involved, the delegate takes care of changing the data directly inside
QAbstractItemDelegate::setModelData
Yes, now that I understand that the delegate methods are callbacks this makes sense.
A signal from the model to the delegate, indicating that the deleted row has been removed.
???
By the question marks are you wondering why I would think I need this? The reason is that as noted above, there are situations in which which rows have been removed have implications for the display of data. In other words, removing a row isn't free from other consequences.
A signal from the model to the delegate, indicating that the added row has been committed.
In general, you don't commit a row but each single cell
It doesn't really make sense (to me) to commit a single cell when adding or deleting, each row has 3 elements which are bound together and which have different display requirements. So it's not sensible to define a DisplayRole for the triplet on the one hand, and its not sensible to allow independent addition of cells (you can't commit a data member without both a name and a type, and its value should also naturally follow the member). Editing single cells makes sense - obviously e.g. the user might want to change the name of a data member. How would a single-cell-commit concept be adapted to this sort of data/view model?
A signal from the delegate to the view, redrawing the view if a row has been added.
A signal from the delegate to the view, redrawing the view if a row has been deleted.Again it doesn't come from the delegate but from the model. This is automatically setup when you call
QAbstractItemView::setModel
OK. Since the delegate is tasked with actually doing the drawing, and the view is calling its method directly, then I presume by this I can rely on that mechanism to invoke the delegate so the view doesn't just redraw using its default methods?
Looking at candidate signals from the view, doubleClicked seems suitable for an edit operation.
You do not need to do it manually, just set the
QAbstractItemView::setEditTriggers
and Qt will take care of deciding when editing is startedSo much to learn...the documentation (and what books I have) is very vague on what this does or how it works, in the docs it just says:
'This property holds which actions will initiate item editing
This property is a selection of flags defined by EditTrigger, combined using the OR operator. The view will only initiate the editing of an item if the action performed is set in this property.'
which didn't make it clear that as soon as you set this, the model will automatically activate the edit. It looked to me more like a mask field that enables the possibility of editing but where you still would need to set up a signal. Much of my problem is this: that I'm not clear nor have I seen documentation make explicit, what the the underlying chain of control (both manual and automatic) is. It's not always obvious that calling some method or setting some value will automatically hook in a whole set of automatic control logic.
Neither add nor delete rows seem to have any obvious signal from the view
It should not, the model does that, not the view
Again, I'm struggling to understand that. To add something to a model I would expect you have to tell the model that something is to be added, and from the user's point of view that's being done by the view. If rows are being added or deleted automatically, then that doesn't need to go through the view. But if it's the user adding rows, then the interface they're interacting with is the view (I think). Can you elaborate?
it needs to know the index the user is currently in within the view
you can use the selection model for this.
QAbstractItemView::selectionModel
and thenQAbstractItemSelectionModel::currentIndex
OK, that's much better. So I don't need to keep anything in the application or delegate or anything else. Good.
This suggests that the entered signal needs to be emitted by the view and caught by the delegate
Qt takes care of this part for you, don't need to worry
Using the method above avoids having to worry about the entered signal at all, at least at that point.
What happens to the currentIndex when the user deletes a row they had currently selected? I'd prefer for it to select the row (or specifically the first item in the row) immediately before the deletion range. If the range included the first row, then I'd like it to select the first row after the deletion range.
It seems all signals from the delegate to the model have to be implemented by me.
You don't need signals between those two, the delegate is supposed to act on the model directly
I can emit either itemChanged or DataChanged from the model to inform the delegate an item has been edited, and rowsInserted/rowsRemoved respectively for the row insert/delete operations.
The model will do it for you, you don't need to emit anything, (
itemChanged
isQStandardItemModel
specific)Yes, got that. Probably should have said 'can catch either itemChanged or dataChanged'. Is there any preference when using a QStandardItemModel about which of these signals should be caught?
But setEditorData and setModelData in the delegate both also ask for a QWidget* editor, and its function createEditor likewise expect a QWidget* parent.
Those are not slots. The view will call those methods directly whenever it needs them passing all the arguments. You don't need to implement anything
So then the view provides the QWidget* parent, I take it? I did realise neither of these functions were slots - but I hadn't realised earlier that they were callbacks - so I assumed I'd have to call them myself, and provide the parent myself. Presumably also the view provides the QStyleOptionViewItem on demand.
QStandardItemModel doesn't have any slots that can receive the QModelIndex which one might expect to pass in from the delegate.
It does indeed, it inherits the entire
QAbstractItemModel
interface.QAbstractItemModel has various methods to add or delete rows, but the only slots I saw were revert() ,submit(), and resetInternalData(). What I'm missing here is, setModelData() doesn't seem to be suitable for adding or deleting rows, so the delegate (or something else) has to inform the model somehow that changes are to be made. How would it do that?
Subclassing a model is one of the most involved things you can do in Qt, plenty of traps one can easily fall into. Don't go on that path if you can avoid it (and you almost always do).
Yup, that's usually true of almost any well-behaved interface. I'm eager not to subclass anything I don't absolutely have to, but there is a lot of implicit communication that I don't see, am not sure what it will or won't do, and need to understand what will happen so I know what I do need to do.
rowsInserted and rowsRemoved says these are 'a private signal
You should really read "protected" instead of "private". The subclasses can emit it but they can't be emitted from outside the class
OK that wasn't made clear from the documentation. When I saw private I interpreted that as 'strictly private'
For my information, what if I did subclass a QAbstractItemModel? In a hypothetical situation where insertRows was reimplemented in the subclass, is that documentation note an indication that the parent method must always be called as well? Or would it be legal to emit rowsInserted in such a subclass?
If somehow they do get emitted, and the delegate catches them, then it can issue the signals to rowsInserted/rowsRemoved in the view.
The view catches them directly and automatically, the delegate has no role here.
OK so is the routing that the model emits rowsInserted, the view catches them, and then calls the delegate to handle redrawing? There is still the question of how the selected row is then determined in the case of deleteRows, see my earlier question.
I would suggest:
As a side note on books: It's hard to find books on Qt in bricks-and-mortar shops, and virtually impossible to evaluate the quality of the books you may be buying through on-line sources. What I've been trying to find is a book that on the one hand is a comprehensive reference to the Qt functionality, and on the other also documents well the internal control flow - so that you don't have to treat anything in the book as 'boilerplate'. How would I find/identify such books?
Thanks for your help.
-
To my understanding the role of a delegate seems to be to change the behaviour of a view away from the default implementation
No, and this solves a lot of the doubt below.
- the model determines quantity, data and relationship between the items
- the view determines how the items are layed-out
- the delegate takes care of painting and editing of a single item. In an ideal design the delegate doesn't even know what row/column it's acting on
So how is the model 'usually' informed that it should insert or delete rows?
This is another point that answers multiple questions below. the default views have no functionality to add rows/columns, you'll have to implement something yourself (normally an add/remove button)
Incidentally, maybe you can answer this as well. If I use the QStandardItemModel's insertRows() method at the top level of the model (i.e. at a point where parent will be an invalid QModelIndex), as long as I have setColumnCount to some value, will it insert that number of columns, even for an initially empty model (i.e. there are no rows yet)?
Yes. this is also valid for valid children. once you call
insertColumns
on a parent index (be it null or not) all subsequently inserted rows will have that number of columnsThe reason is that as noted above, there are situations in which which rows have been removed have implications for the display of data. In other words, removing a row isn't free from other consequences.
You'll have to connect to to
rowsRemoved
and callQAbstractItemView::update
on the indexes you want to repaintIt doesn't really make sense (to me) to commit a single cell when adding or deleting
What I'm missing here is, setModelData() doesn't seem to be suitable for adding or deleting rowsI don't really know what you mean by "commit". The
QAbstracItemModel
API however requires to have the actions as separate. either you require the data to enter before callinginsertRows
and callsetData
with that or provide "default values" for each cellI can rely on that mechanism to invoke the delegate so the view doesn't just redraw using its default methods?
correct
the model will automatically activate the edit. It looked to me more like a mask field that enables the possibility of editing but where you still would need to set up a signal.
the model doesn't activate the edit. the view does. Editing has effectively 2+1 points of control. For an item to be editable:
QAbstrctItemModel::flags
must haveQt::ItemIsEditable
setQAbstractItemView::editTriggers
must not be QAbstractItemView::NoEditTriggersQAbstractItemDelegate
must actually implement an editor (and not just return a widget that pains only)
What happens to the currentIndex when the user deletes a row they had currently selected?
depends on the view implementation (normally it's just set to a null index) but you can always control this connecting to
QAbstractItemModel::rowsAboutToBeRemoved
'can catch either itemChanged or dataChanged'. Is there any preference when using a QStandardItemModel about which of these signals should be caught?
the only difference is that
itemChanged
only works with QStandardItemModel,dataChanged
is guaranteed to work with all the models.Presumably also the view provides the QStyleOptionViewItem on demand.
correct
documentation note an indication that the parent method must always be called as well? Or would it be legal to emit rowsInserted in such a subclass?
no, you don't need to call the parent (the body of the parent is in fact just
return false
) but there are other requirements like callingQAbstractItemModel::beginInsertRows
andQAbstractItemModel::endInsertRows
that will take care of emitting the appropriate signalsAs a side note on books: It's hard to find books on Qt in bricks-and-mortar shops, and virtually impossible to evaluate the quality of the books you may be buying through on-line sources. What I've been trying to find is a book that on the one hand is a comprehensive reference to the Qt functionality, and on the other also documents well the internal control flow - so that you don't have to treat anything in the book as 'boilerplate'. How would I find/identify such books?
The links I provided are to full PDF books so the cost of reading them is minimal. Those 2 books is where I learned all I know about Qt. Some parts are obsolete (QHttp, QFtp) some teach you a suboptimal solution that has hidden traps (QThread) and other are not part of Qt any more (Phonon) but for the vast majority are more than solid and the model/view part is impeccable
-
Making progress. More below.
@VRonin said in Required signal/slot plumbing for a model/view interface using built-in Qt objects:
To my understanding the role of a delegate seems to be to change the behaviour of a view away from the default implementation
No, and this solves a lot of the doubt below.
- the model determines quantity, data and relationship between the items
- the view determines how the items are layed-out
- the delegate takes care of painting and editing of a single item. In an ideal design the delegate doesn't even know what row/column it's acting on
Well, this design is going to be far from ideal anyway, but notwithstanding I think the delegate I will need to use will have to know what row and column it's acting on, because both the way items are edited and the way they're displayed depends upon which column they're in, and also on any relationships of the parent of the selected item with the child - different types of parent may have different relationships (e.g. an array needs to fill in a default name for the first column whilst other types will have a user-specified name).
I think you and I have more or less the same understanding of the roles of model, view, and delegate, just expressed a bit differently. In un-delegate-helped model-view situations, the view has a 'default' delegate that handles the grunt work, but you don't need to touch that at all. A delegate certainly handles one item at a time, but in a way that's customised for the type of item it's handling. Is that your understanding?
The other part of that though is that it appears the expectation is that a delegate NOT be used as a container for signal routings other than the expected call chain out of a view.
So how is the model 'usually' informed that it should insert or delete rows?
This is another point that answers multiple questions below. the default views have no functionality to add rows/columns, you'll have to implement something yourself (normally an add/remove button)
Yes, that's what I had from the very start. But I see what you're getting at: you don't have to point the slgnals from those buttons at the delegate, as long as the application owns the model you point them at the top-level application and then have that update the model directly. So really the delegate is only used if the changes are being propagated directly from the view.
Incidentally, maybe you can answer this as well. If I use the QStandardItemModel's insertRows() method at the top level of the model (i.e. at a point where parent will be an invalid QModelIndex), as long as I have setColumnCount to some value, will it insert that number of columns, even for an initially empty model (i.e. there are no rows yet)?
Yes. this is also valid for valid children. once you call
insertColumns
on a parent index (be it null or not) all subsequently inserted rows will have that number of columnsThe reason is that as noted above, there are situations in which which rows have been removed have implications for the display of data. In other words, removing a row isn't free from other consequences.
You'll have to connect to to
rowsRemoved
and callQAbstractItemView::update
on the indexes you want to repaintOK, and presumably this is because the view expects to call the delegate's methods rather than have its own methods called by the delegate. The routing is always indirect.
It doesn't really make sense (to me) to commit a single cell when adding or deleting
What I'm missing here is, setModelData() doesn't seem to be suitable for adding or deleting rowsI don't really know what you mean by "commit". The
QAbstracItemModel
API however requires to have the actions as separate. either you require the data to enter before callinginsertRows
and callsetData
with that or provide "default values" for each cellThis is I think the source of confusion on my part. My expectation would have been that insertRows will give you that number of rows, with a number of columns given by setColumnCount or otherwise 1 with (for the StandardItemModel) a set of empty QStandardItems (or whatever type of item for custom models) So you're saying setData has to have been called first and you have to have in effect a temporary row not existing in the model that you then add? If possible I'd prefer the former approach - so that any model can be built up even from a totally uninitialised state, and so that the form of the model can be defined independent of its content, but if what you've said above is true, it sounds as if the thinking was that a model with no data isn't a model.
By 'commit' I was thinking of a single item is added to the model and saved within the model, as opposed to an entire row.
the model will automatically activate the edit. It looked to me more like a mask field that enables the possibility of editing but where you still would need to set up a signal.
the model doesn't activate the edit. the view does. Editing has effectively 2+1 points of control. For an item to be editable:
QAbstrctItemModel::flags
must haveQt::ItemIsEditable
setQAbstractItemView::editTriggers
must not be QAbstractItemView::NoEditTriggersQAbstractItemDelegate
must actually implement an editor (and not just return a widget that pains only)
What I meant by 'model will automatically activate the edit' was that an entire chain of communication was already set up and ready to go such that with a field set in EditTriggers no further programmatic action would be required in either view, delegate, or model to cause the edit to occur. When I read through EditTriggers it seemed to me that it could configure the view such that certain actions in the view were made potentially available to initiate an edit, but maybe further action on the developers' part would be necessary to complete the connection.
So what you're saying is that there is an internal mechanism in the view that's already primed to call upon the delegate and which is sensitised as soon as editTriggers is set.
The main thing here is it isn't really a signal-slot interface but a call-chain interface with some internal function in the view invoking the call chain as soon as it sees certain user actions. It's the delegate then that is your redirect point if you as the programmer want to do something 'interesting' about handling how the model receives signals from the view if something happens.
What happens to the currentIndex when the user deletes a row they had currently selected?
depends on the view implementation (normally it's just set to a null index) but you can always control this connecting to
QAbstractItemModel::rowsAboutToBeRemoved
Which is what I did. I actually have to have both: rowsAboutToBeRemoved and rowsRemoved, because in the former I need to set the selection and in the latter I need to react to what may happen if the new selection falls on certain rows. And also selectionChanged for a few others.
documentation note an indication that the parent method must always be called as well? Or would it be legal to emit rowsInserted in such a subclass?
no, you don't need to call the parent (the body of the parent is in fact just
return false
) but there are other requirements like callingQAbstractItemModel::beginInsertRows
andQAbstractItemModel::endInsertRows
that will take care of emitting the appropriate signalsOh yes, I saw that. So that's (part of) what those do. Interesting.
Thanks again.
-
both the way items are edited and the way they're displayed depends upon which column they're in
That's solved using
setItemDelegateForColumn
from the view with different delegates rather than forcing the delegate to know its surroundings.and also on any relationships of the parent of the selected item with the child
That's ok, looking at the parent is tolerable as there is no better alternative in Qt default implementations
the expectation is that a delegate NOT be used as a container for signal routings other than the expected call chain out of a view
Correct, in normal scenarios it should not have signal/slots (of course your implementation can have as many as you like)
you don't have to point the slgnals from those buttons at the delegate, as long as the application owns the model you point them at the top-level application and then have that update the model directly. So really the delegate is only used if the changes are being propagated directly from the view.
Correct
presumably this is because the view expects to call the delegate's methods rather than have its own methods called by the delegate. The routing is always indirect.
correct and also the view tries to be as lazy as possible and not repaint items that did not change
My expectation would have been that insertRows will give you that number of rows, with a number of columns given by setColumnCount or otherwise 1
The expectation is correct apart for the "otherwise 1" part, it should be "otherwise 0" just as
inserColumns
shouldn't insert at least 1 rowSo you're saying setData has to have been called first
no,
model->insertRows(0,2); model->insertColumn(0);
will create a 2x1 table with alldata()
returningQVariant()
. I was referring to what you mentioned before re validating a row before "committing it"it sounds as if the thinking was that a model with no data isn't a model.
it is indeed a perfectly working model
to have in effect a temporary row
You can achieve this MS-Access-like behaviour with a
QAbstractProxyModel
if you wantSo what you're saying is that there is an internal mechanism in the view that's already primed to call upon the delegate and which is sensitised as soon as editTriggers is set.
correct
The main thing here is it isn't really a signal-slot interface but a call-chain interface with some internal function in the view invoking the call chain as soon as it sees certain user actions.
Correct. Unfortunately, this also means that calling
QAbstractItemModel::setData
from a thread different from the one the view lives in is unsafeIt's the delegate then that is your redirect point if you as the programmer want to do something 'interesting' about handling how the model receives signals from the view if something happens.
yep,
setModelData
is probably the best way to do this -
All looking very good. I was about to mark this as 'problem solved' but some final questions come up - which honestly make me start to wonder whether I might not end up having to use a custom model anyway, something I'm anxious to avoid. But the delegate is getting more complex than expected. See below for details.
@VRonin said in Required signal/slot plumbing for a model/view interface using built-in Qt objects:
both the way items are edited and the way they're displayed depends upon which column they're in
That's solved using
setItemDelegateForColumn
from the view with different delegates rather than forcing the delegate to know its surroundings.Perhaps for a later version. I'm inclined to use one delegate for the moment, simply because it puts the logic in one place and therefore it's easier to see visually whilst developing what's going on. Later on once I've fully understood the flow of control I can split it up. However delegates interact with neighbouring columns anyway, so setting separate delegates for separate columns may not make sense.
This is because in a given row, describing a datatype or data member, there is a column for the type of the member or datatype. If the user sets this, then another associated column, the actual value, changes as well. In addition, if the row is describing a member of a union, and the user has not yet set up the member name, nothing can be changed or committed until a name is given in the name column.
and also on any relationships of the parent of the selected item with the child
That's ok, looking at the parent is tolerable as there is no better alternative in Qt default implementations
I'm discovering this is trickier than I initially thought, because the Qt model approach appears to have plenty of methods for traversing up the model hierarchy from the current index, but few for traversing down. Which I may need to do. A composite datatype, such as a struct, union, or array, has submembers. These will work out as children of the composite parent's row. Trouble is, now what if the user decides to change the underlying type of the parent? Then all its children (and any children it may have etc.), themselves need to be deleted or modified. As far as I know, there's no way the model can 'know' that children of a given parent row (technically, an item in the row), are now invalid, so you need now to traverse recursively down the child hierarchy removing everything. This can be done, but as far as I can tell only by using an integer index to iterate manually through children. I don't see any way to get e.g. an iterator into the child indices of the parent.
presumably this is because the view expects to call the delegate's methods rather than have its own methods called by the delegate. The routing is always indirect.
correct and also the view tries to be as lazy as possible and not repaint items that did not change
OK, so what would happen in the hypothetical I outlined above: A user changes a datatype in a relatively high-level parent row, multiple child rows and their descendants disappear. Will there be one repaint event? I think the view should automatically adjust, after all it's a view, it can't make any assumptions about what may have just happened in the model.
So you're saying setData has to have been called first
no,
model->insertRows(0,2); model->insertColumn(0);
will create a 2x1 table with alldata()
returningQVariant()
. I was referring to what you mentioned before re validating a row before "committing it"Good. That's the behaviour I want.
The main thing here is it isn't really a signal-slot interface but a call-chain interface with some internal function in the view invoking the call chain as soon as it sees certain user actions.
Correct. Unfortunately, this also means that calling
QAbstractItemModel::setData
from a thread different from the one the view lives in is unsafeHopefully this won't arise...OTOH I am very comfortable dealing with thread safety.
However this brings up a more fundamental question regarding the model. The issue is, the model itself is going to be a collection of datatypes, each bundled up into a QVariant object (I speak here in the specific sense, not in the general of how Qt arranges models). Let's consider a single row, containing 3 items: a name, a type, and a value. Now, I would like it to be that the value contains the actual datatype itself: the QVariant I referred to above. It's usually a type subclassed from QVariant to be exact, but the differences are only in the range of allowed types the type can itself contain. Name is just a QString and can be held in DisplayRole (but see below). Type is to be selected from a combo box, it will contain a QString in the DisplayRole and have a user-defined role containing the int (which is the QMetaType::type of the value).
What I would like to do is arrange things so that only the top-level rows (row items), without any parents hold the 'real' data for the model, and that child rows hold simply a series of references to the QVariant methods needed to extract their relevant details. If top-level datatypes are scalars, e.g. ints or doubles, this is trivial, because there's no hierarchy, but with composite types e.g. structs what I'd like is that members of a struct have their rows beneath the row for the struct - each row listing the member name, member type, and value. Value would then be a reference to the structure member as found in the parent.
At top level, member name is a QString that I can access from Qt::DisplayRole directly, but for members, I'd like the name in Qt::DisplayRole to be referenced from its parent's Value item (via something like pointer-to-member), so that when a user edits that name in the view, it then is propagated not just to the direct-mapped item in the model, but to the composite structure of which it is a member. Type, meanwhile, is set via combo box (brought up by the delegate), and if you set the type to a scalar when it had previously been a composite, the members beneath the old composite disappear.
Will this overall approach work in the model - i.e. can I use a setData to set a given role to a reference or pointer-to-member of another item somewhere in the model?
Doing it this way would ease the work required of the delegate because it doesn't have to generate e.g. text strings for DisplayRole internally but rather can simply call upon the methods of the objects contained in the model to provide the desired text. E.g. since I have QVariants I could set DisplayRole to a pointer-to-member toString() of the QVariant to provide the DisplayRole for the value field, and then subclass QVariant with a reimplementation of the toString() method to automatically provide the right text output when the contained type of the QVariant is a struct or array or union.
The key point to observe is that the model I intend isn't supposed to be a table of fixed scalar values, but a list of hierarchical objects, (with their own methods as well), and I don't want to duplicate in table entries values that can be had by calling methods of the underlying objects.
As it is the delegate is looking worrisomely complicated, and starting to look more and more like C rather than C++ when you glance at the code, which to me is usually a sign that there's a better way of doing this. Any thoughts?
Maybe this has drifted into a separate thread. Let me know if you think so and I'll start a new one - the essential question of the original thread has I think been answered.
Thanks.
-
If the user sets this, then another associated column, the actual value, changes as well.
I realise I'm probably too dogmatic in this sense, but I would connect to the change in the first column to change a role (>=Qt::UserRole) in the second column so the delegate has all the information it needs only from the item it is acting on. This is because when you start moving items around it easily becomes a mess if you introduce dependencies on siblings.
nothing can be changed or committed until a name is given in the name column.
As mentioned above, you can manage this in 3 different ways. My personal preference would be to act on
QAbstractItemModel::flags
but, again, free to take a different viewso you need now to traverse recursively down the child hierarchy removing everything
If all you need is removing all children of a
parent
index, just usemodel->removeRows(0,model->rowCount(parent),parent);
(you can delete parts by changing the first 2 arguments)but few for traversing down
QAbstractItemModel::rowCount
andQAbstractItemModel::columnCount
is pretty much all you need.as I can tell only by using an integer index to iterate manually through children.
correct,
for(int i=0;i<model->rowCount(parent);++i){}
an iterator into the child indices of the parent.
While I see how this can be usful, this would be ambiguous to implement. should the iterator scan column or row wise?
Will there be one repaint event?
There might be multiple calls to
QWidget::update
but, given how that method is implemented it will aggregate fast repaints in a single action. In general, you shouldn't worry about it, the view takes care of it.it can't make any assumptions about what may have just happened in the model
It doesn't, when you call
QAbstractItemView::setModel
the view connects to multiple signals emitted by the model includingdataChanged
,rowsInserted
androwsRemoved
and those signals will tell the view what to repaint. again, it's all doen under the hood, nothing to implement manuallyIt's usually a type subclassed from QVariant to be exact, but the differences are only in the range of allowed types
This is a weird case if you ask me. You normally just declare and register a data type, I can't see a reason to subclass QVariant
can I use a setData to set a given role to a reference or pointer-to-member of another item somewhere in the model?
Yes and you can even use a proxy model (subclasses of
QAbstractProxyModel
) which is much easier to subclass than the full model.
Moving to your specific case. You are trying to describe objects and there is are 2 well established technologies for generic object notation, xml and, even more relevant, JSON. What I would do, instead of complicating my life with custom representations (subclasses of QVariant?) I'd just use what is available already. For example, if you represent your object in JSON you can use https://github.com/dridk/QJsonModel if you need finer control you could implement a model based on a DOM xml document
-
@VRonin said in Required signal/slot plumbing for a model/view interface using built-in Qt objects:
If the user sets this, then another associated column, the actual value, changes as well.
I realise I'm probably too dogmatic in this sense, but I would connect to the change in the first column to change a role (>=Qt::UserRole) in the second column so the delegate has all the information it needs only from the item it is acting on. This is because when you start moving items around it easily becomes a mess if you introduce dependencies on siblings.
An interesting approach. I may look into that for a future revision, although at this point a fair amount of water has flowed under the bridge to make the changes immediately.
nothing can be changed or committed until a name is given in the name column.
As mentioned above, you can manage this in 3 different ways. My personal preference would be to act on
QAbstractItemModel::flags
but, again, free to take a different viewI thought about that but it seems to come with the caveat that that would make the data entry dependent upon the order of entering various fields - if I were a typical user I'd think it counterintuitive not to be even allowed to enter a value for e.g. type before I've entered a name. This is always a frustrating thing to encounter in UIs when what you can do depends on the past sequence of what you've done, requires memorising certain patterns of data entry. On the other hand it may well prove to be the simpler approach, and at this point I'm trying to reduce complexity rather than increase it.
so you need now to traverse recursively down the child hierarchy removing everything
If all you need is removing all children of a
parent
index, just usemodel->removeRows(0,model->rowCount(parent),parent);
(you can delete parts by changing the first 2 arguments)Yes, that would work if what you needed to remove were essentially siblings of the selected row being edited, then you could reference the index's parent and do whatever. It's when the current selection itself has children that need to be removed that the fun starts, because then you're searching down the tree rather than up towards higher-level parents - and the search has to be recursive over all children because each child may itself have children etc. etc.
but few for traversing down
QAbstractItemModel::rowCount
andQAbstractItemModel::columnCount
is pretty much all you need.as I can tell only by using an integer index to iterate manually through children.
correct,
for(int i=0;i<model->rowCount(parent);++i){}
Yes that's what I've been using, I get the row count and then iterate over the rows.
an iterator into the child indices of the parent.
While I see how this can be usful, this would be ambiguous to implement. should the iterator scan column or row wise?
you'd just have an argument to the iterator constructor: Qt::RowMajor or Qt::ColumnMajor. Notwithstanding this feature doesn't exist so not much real point worrying about hypothetical objects that don't exist.
It's usually a type subclassed from QVariant to be exact, but the differences are only in the range of allowed types
This is a weird case if you ask me. You normally just declare and register a data type, I can't see a reason to subclass QVariant
It's definitely a weird case, it happens because what I've got is a set of possible datatypes (which can be hierarchical); therefore there is no fixed datatype, you have a list of 'legal' datatypes which is in essence a subset of the types for QVariant with a few more types added (and registered) that can also be there. So the 'type' of the stored value is a QVariant that can represent any of the supported base types of which the value could be composed. A composite (a union, struct, or array) contains members each of some possibly-composite assembly of the allowed basic types, so it's hierarchical, you can't just register all types because in general the number of types is unbounded. A QVariant is the ideal model for this, representing more-or-less exactly the type of object needed.
can I use a setData to set a given role to a reference or pointer-to-member of another item somewhere in the model?
Yes and you can even use a proxy model (subclasses of
QAbstractProxyModel
) which is much easier to subclass than the full model.Good. That eliminates a large class of concerns.
Moving to your specific case. You are trying to describe objects and there is are 2 well established technologies for generic object notation, xml and, even more relevant, JSON.
:-D Laughing here because what I have to do is to translate and parse a problem definition input as an Xml file with embedded JSON. The source is already in that format and the task is to translate a 'convenient external' format into an internal object representation that will be used to generate machine executables from high-level problem descriptions. It's a model-model translator.
The class that you mention though does look as if it could be used for allowing a user to edit the input file before it even reaches the internals of the translator. Which may be in practice the only time you might need to do so.
-
@AlexRast said in Required signal/slot plumbing for a model/view interface using built-in Qt objects:
It's when the current selection itself has children that need to be removed that the fun starts, because then you're searching down the tree rather than up towards higher-level parents - and the search has to be recursive over all children because each child may itself have children etc. etc.
Not really it's the same concept. The code below removes all the children of selected indexes (and it does it recursively)
const QModelIndexList selectList = view->selectionModel()->selectedIndexes(); for(auto&& singleIdx : selectList){ view->model()->removeRows(0,view->model()->rowCount(singleIdx),singleIdx); }