Leveraging Qt models for nested data structures
-
Here is a simplified version of what I'm trying to achieve. For every Store that is added, one or more Departments can be added. For every Department that is added, one or more Employees can be added. Selecting a row in a list will update the lists to the right of it. E.g, selecting Sesame Street will update the Departments list to show all the Departments that belong to that Store. Selecting Clothing will update the Employees list to show all the Employees that belong to that Department.
This relationship is also demonstrated below (note that these will actually be classes, and capable of emitting signals when their data is changed internally, for example, from external sources):
struct Employee { std::string id; std::string name; double hourly_rate; }; struct Department { std::string id; std::string name; int personCount; bool status; Employee* onShift; std::vector<Employee> employees; }; struct Store { std::string id; std::string name; std::string address; std::vector<Department> departments; };
On another tab, we have an aggregate list of all Departments across Stores, presenting live data, with a drop down list to select an on shift leader from the pool of Employees that belong to that Department.
As far as Qt models are concerned, I imagine there a several ways to approach this. I prefer to think of Qt models as only an interface between a view and data, rather than themselves being responsible for, or having ownership of said data.
I propose handing ownership of our data to a Manager class which also contains any subclassed models we create. For the configuration page, we have three QAbstractListModel subclasses for Stores, Departments and Employees. For the Departments and Employees models, we can pass a list pointer and call begin/endResetModel as needed to update the lists when items are selected.
For the All Departments page, we have an aggregateDepartments model also subclassed from QAbstractListModel. This would contain a list of pointers for all Departments across Stores. The On Shift Leader dropdown is a bit more tricky as we can't use a single model. I'm wondering in this case whether it would make more sense for Department to have its own instance of the Employee list which it can return to the QML delegate...
As you can see, we now have numerous models for what is a fairly basic data hierarchy. Synchronisation between models could become an issue, so the underlying data should only be modified via the Manager class, which can then orchestrate updating of the models.
Have I described a worthy approach to the problem or have I created a convoluted mess? I can't really think of a better way
-
I have used Qt's model fairly a lot, so basically your interpretation seems legit.
So there is
QStandardtItemModel
, which contains the data it serves, and I usually derive fromQStandardItem
to add e.g., additional members that I don't want to funnel through custom data roles. But I merely use this model/approach when I need a very basic model, and I'm too lazy (or the use just doesn't justify) to build a full-fledged model myself. Like displaying a couple of items in some minor dialog.For the rest of it I totally share your thoughts and I often find myself struggling where to put the data. Thechnically I think it's correct to perceive the
QAbstractItemModel
as an interfacing layer between your actual model/data and the presentation/views. But usually when you start designing your business like that you'll quickly find yourself duplicating code and struggling to access the data you need in the model.Like in your example as of now I think I'd make one tree model. The root nodes are the stores, then each store node has the departmens as its child nodes, which have the employees as their child nodes.
Now if I gave you aQModelIndex
to an employee, how'd you access the employee's data? You either have to traverse your data structure from the root to the employee by using some public interfaces (likeStore.at(x).department.at(y).employee.at(z).lastName()
, x/y/z being the child indices in the model) or you have to use the data pointer fromQModelIndex
to directly point to the employee's data. Which is not a problem technically, but now you have already mixed item model and internal knowledge of the data...So I normally end up with the model holding the actual data itself.
A while back I designed a tiny wrapper aroundQAbstractItemModel
that handles most of the range checks/model index checks and so on, and just calls virtual functions to get/set the data. So the model logic is tucked away as much as it gets, and the model and I live in small derived itemclasses. -
@Kamajii said in Leveraging Qt models for nested data structures:
So I normally end up with the model holding the actual data itself.
And if not,
beginResetModel()
andendResetModel()
are your best friends :)
(to reset the model and load data from your source from "scratch" to re-init the model/view). -
Hi,
Aren't you describing a use case for a database and thus Qt's SQL module and its models ?
-
Like in your example as of now I think I'd make one tree model.
I had considered creating a tree model, but I have some reservations. Store, Department, and Employee have additional responsibilities beyond being simple data containers, and I worry that either I end up decoupling the data from the logic (which goes against OOP principles), or I have to subclass the three classes from a common class, override functions, and therefore haven't achieved clear separation of UI and business logic.
To my mind at least, it makes sense to design this as if it were a command line application, and then throw some code on top to interface with a GUI. Maybe that's not the right way to think about things, but I don't like the idea of the GUI framework I'm using dictating how I should structure my data.
Aren't you describing a use case for a database and thus Qt's SQL module and its models ?
Truthfully, the application has nothing to do with stores, departments, or employees. I changed the names to make the problem easier to reason with. I don't think the data layout is so complex that it justifies introducing relational databases. And besides, with the very frequent live updates coming over the network I can see latency issues arising.