Quite often I find myself in need of some custom scheme model, mandating the implementation of more and more models, made even more tedious by the inability of QObject
derived classes to be templates.
Qt has the QStandardItemModel
but that seems a bit verbose and inconvenient to use, especially from the qml side, and total overkill for a basic list model.
There is also the basic qml ListModel
, but that is limited and not elegant to use on the C++ side, and I do suspect a tad more bloated than it needs to be.
Qt has QVariant
, which is what its model/view architecture uses internally, so it is surprising that the framework doesn't provide something as simple as:
// qml code
VarMod {
roles: ["name", "age", "weight"]
Component.onCompleted: {
insert(["Jack", 34, 88.5], -1) // qml doesn't support
insert(["Mary", 26, 55.3], -1) // default arg values
}
}
// cpp code
VarMod vm { "name", "age", "weight" }; // member declaration
vm.insert({ "Jack", 34, 88.5 });
vm.insert({ "Mary", 26, 55.3 });
And here it is.
Note that you do have to be responsible with the parameters, as there is no type safety, in fact it has implicit analog to
ListModel
'sdynamicRoles
- that is, it will accept and work with anyQVariant
compatible value on every role slot.As for memory efficiency, consider that
QVariant
has 8 bytes for data, plus 4 bytes for type id, plus another 4 bytes of padding, for a total of 16 bytes. That is not insignificant if you are using it for small data types, like saybool
, so in case you have a data scheme that has a lot of small (1 - 4 bytes) fields and a scores of items, implementing a full model will still be the better option. It is still a lot better than the generic object model I am using, which has to carry the bloat ofQObject
, and even more significant in the case of qml objects.Additionally,
QVariant
being 16 bytes, I opted to not use the convenience ofQVariantList
for data storage, which has an underlyingQList
, making the situation worse than it needs to be. Although that is fixed in Qt 6, which gets rid ofQList
as it is, and replaces it with an alias ofQVector
. Still,std::vector
helps to avoid that in any case, plus it might actually be a tad faster, since it doesn't have to deal with COW and atomic ref counters. There are several auxiliary methods to help with pre-allocation and release of memory as well.The model has a safeguard against the change the roles for obvious reasons, the latter is primarily intended to be initialized just once, but there is
reset()
that is intended to be used in a more dynamic qml context, making it possible to redefine the model schema on the fly and provide a compatible delegate. For the sake of certainty, the roles can only be redefined after the model has been explicitly reset.There is a minute difference in inserting, on the c++ side, the parameter pack is passed wrapped in
{}
, in qml it is wrapped in[]
, both leveraging implicit conversion in the context specific way. Also, note that qml currently doesn't support omitting parameters with default values provided on the c++ side, so for appending you do have to provide an invalid index. Naturally, it would be trivial to add convenience methods for appending and prepending if needed.In addition to the syntax example of the question, it is also possible to add multiple items at once, from "declarative-y" qml structure such as:
Finally, type safety is possible to implement if the scenario really calls for it, then each role can be specified with the expected type to go with it, such as:
and then iterating and making the necessary checks to ensure type safety when inserting.
Update: I also added serialization, and save/load from disk features, note that this will serialize the data together with the mode schema.
I also created this sorting/filtering view to supplement the model:
For sorting, you just have to specify the sorting role, note that it is the index of the "column" rather than the int value from the roles hash. For filtering it works via a qml functor that receives the model item as a JS array, and expects to return a bool, a c++ functor can be easily added via
std::function
if needed. Also note that it needs a pointer to the actual qml engine.