What's the correct way to setup the following project with a Model/View/Delegate structure?
-
I'm new to Qt. I'm using QtCreator and have managed to get quite far in my project, but it's becoming unwieldy. I suspect the answer is to use Model/View/Delegates.
I've read through all the documentation and had a couple of tries, but I don't think I'm breaking things up in a sensible way. I'm hoping someone can point me the right direction.
My data structure looks like this:
struct MyData { int dataA; std::string dataB; struct DataCStruct { int data1; int data2; } dataC; std::map<int,DataCStruct> dataD; };
My real data is bigger, but I'm just trying to show the complexity.
I'd like my UI to be broken up into tab-pages. Page one will just be about editing dataA. Page two might be editing dataC. Page three would be adding rows of data, deleting, etc from dataD.
For the data model, my thought was that a 'row' would refer to either dataA, dataB, dataC, dataD. And the 'column' would only be important for dataD. Is this correct? The way I thought to implement it was:
enum STAT { DATAA = 0, DATAB = 1, DATAC = 2, DATAD = 3 };
and then in the ::data method of the model, I would do something like
QVariant MyModel::data(const QModelIndex& index, int role) const { if(!index.isValid()) { return QVariant(); } if(index.parent.isValid()) { // this must be an element of dataD, because none of the others would have a valid parent if(index.row() == 0) { return m_referenceToUnderlyingStruct.dataD[parent.column()].data1; } else if(index.row() == 1) { return m_referenceToUnderlyingStruct.dataD[parent.column()].data2; } else { // the row must indicate which "stat" this is switch(static_cast<STAT>(index.row())) { case STAT::DATAA: return m_referenceToUnderlyingStruct.dataA; case STAT::DATAB: return QString::fromStdString(m_referenceToUnderlyingStruct.dataB); case STAT::DATAC: // in this case, the column indicates which ITEM if(index.column() == 0) { return m_referenceToUnderlyingStruct.dataC.data1; } else if(index.column() == 1) { return m_referenceToUnderlyingStruct.dataC.data2; } } } return QVariant(); }
but you can see this is already starting to get a little confusing and unmanageable. Whilst I think I see how row, column, and parent can reference elements of any data structure, the implementation of this looks bad to me. In my real structure, I need to use an std::unordered_map, which I'm worried might break things considering there's no guarantee on the order of elements after they've moved.
If the above is correct, how do I even begin to construct views?
So, like I said, I want several pages across tabs to edit this single data structure. Each of these tabs might have multiple edit boxes, spin controls, tables or lists. Some of these would be for editing the structure, others would be to apply some kind of logic to determine values to write into this structure.
Initially, each little section of controls--for example the controls to edit dataA--are on their own form; that's how I did this without a model. So if I want to now use a model, do I create a single view for these controls? I'm confused because how can the view know which elements to request from the model, is this something I determine in my custom view?
And if I create a new View, how do I visualise this in the QtDesigner?
From the examples I've seen, the views create Delegates to actually edit the information. But if my view is going to show both dataA and dataB, and allow the user to edit both, how does this work?
I could really use a better example of a project than the simple examples in the documentation, because I can't see how to achieve my use case. If anyone can point me to examples, head me in the right direction, or even just list the classes and steps I'd need to do for this small example, it would be greatly appreciated.
-
I'd like my UI to be broken up into tab-pages. Page one will just be about editing dataA. Page two might be editing dataC. Page three would be adding rows of data, deleting, etc from dataD.
Let's be clear: do you mean there is just a single instance of
MyData
to be edited in the whole program, or do you mean that there is a whole table of many rows, each of which is its ownMyData
instance?There is something odd/inconsistent about a UI where the first pages are for editing data inside a single instance and the subsequent pages are about inserting/deleting multiple instances. For example, when you add a new instance at page 3 you are not able to edit its details before/while adding as that is in pages 1/2.
For the data model, my thought was that a 'row' would refer to either dataA, dataB, dataC, dataD. And the 'column' would only be important for dataD. Is this correct?
While technically this can be done in Qt because its
data()
method works withQVariant
s for each row+column cell value and that would allow each row+column cell to store/return a quite different value type (the first asint
, the second asstd::string
, etc.), that is most unusual for a model, which is expected to have the type in the same column in each row. I don't think you will get any actual advantage in bothering to treat the members of yourstruct
as separate rows. They would normally be columns.Don't necessarily get too hooked on "how do I visualise this in the QtDesigner". Sometimes you are better off/have to write code for which there is no Designer UI or just a "placeholder".
If you need to add/delete rows, each of which is
MyData
instance, as well as edit rows, the standard/simplest/traditional interface is "master-detail". Your model has rows for each one and columns corresponding to each member. Then you can use aQTableView
(on the first page) to show existing rows. There the user can press buttons to add a new row or delete an existing one. Or they can select an existing row and press a button to edit, at which point they get the widgets to edit that row on the master-detail page or moved onto other pages if you prefer. For that you might like to take a look at Qt's QDataWidgetMapper, which produces and manages editing widgets for each column in a given row.You may need to go more advanced for a member like your
std::map<int,DataCStruct> dataD;
since this itself has many rows.Or you may just want your own bunch of widgets over your own multiple pages and handle your own storage to/from a
MyData
instance. -
@JonB said in What's the correct way to setup the following project with a Model/View/Delegate structure?:
I'd like my UI to be broken up into tab-pages. Page one will just be about editing dataA. Page two might be editing dataC. Page three would be adding rows of data, deleting, etc from dataD.
Let's be clear: do you mean there is just a single instance of
MyData
to be edited in the whole program, or do you mean that there is a whole table of many rows, each of which is its ownMyData
instance?What I mean is there's one instance of MyData. But it's big and makes sense to split it's view and editing across tabs. So like tab 1 might be "basic details", tab 2 might be "complex information".
To complicate things, what you put in Tab 1 might change what is available as options or validation etc in Tab 2.
Or you may just want your own bunch of widgets over your own multiple pages and handle your own storage to/from a
MyData
instance.This is what I did originally. The problem I had was that if one widget on Tab 1 edits a value in the structure, I needed to send a signal that this happened. Multiple other forms then need to be connected to this signal etc and fail validation, potentially changing a value they control. This in turn generates a new signal. Etc etc
To me, that seemed like I was doing things wrong. It was getting too complicated and was going to become error prone. From what I understand, the MVC parts of Qt do all that behind the scenes, so I thought it was a no brainer to use it.
However, I did recently read that it may be better to create a Qt Model for each single attribute of the structure. Each of these small models can be easily given to the existing views. When the underlying structure receives a call from a model that an attribute is to be updated, it can call every model to inform it of the update (this will maybe be a sort of "master model" on top of the actual underlying data structure).
The only complexity there is where there's variable containers of sub-structures, as I'd need to spin up views dynamically. But I think this should be easy.
But then I sort of think I could just hand-roll all this anyway and get the same result. It seems to me that Qt's MVC paradigm doesn't really work for much beyond the simple examples given, and due to the complexity in complicated data models, is the same effort to use as just doing something yourself.
But happy to be corrected!
-
@Deckhead
Yes, you might have to connect signals between multiple widgets. Or, you change a model and that sends signals to other widgets. For something as complex and specific as you describe, where changing one value alters what's going on for other widgets, you can't expect Qt's internal MV structure to somehow automatically produce this.Yes you can have multiple, separate models and connect these together as you see suitable. For example, you might decide that your maps/lists are worthy of their own model, with links from the top-level model to the sub-model where appropriate. Though the
QDataWidgetMapper
I mentioned does handle a list as a single element in its source model. -
Out of curiosity: Why you decided to delete the other topic without any further interaction?
It's not very nice esp. when people have commented on it. You also could have continued the discussion there ;-)
Isn't it about the same issue?! -
@Pl45m4 said in What's the correct way to setup the following project with a Model/View/Delegate structure?:
Out of curiosity: Why you decided to delete the other topic without any further interaction?
It's not very nice esp. when people have commented on it. You also could have continued the discussion there ;-)
Isn't it about the same issue?!I deleted it because there was no value in the replies for someone else who came searching for answers.
-
@Deckhead said in What's the correct way to setup the following project with a Model/View/Delegate structure?:
I deleted it because there was no value in the replies for someone else who came searching for answers.
Strange attitude...
Usually these threads evolve over time and by the information you provide... deleting the question right away because the first two answers or so are that exactly what you are looking for is weird but ok.
Also why do you care about future readers?! You started the topic because you needed help?!
Good luck with that