Thoughts on my recent Experiment with Qt & Templates
published at 12.04.2018 23:41 by Jens Weller
So in March of this year I did play around with two libraries in Qt and boost, to test out some ideas. This post is about ideas and problems I'm currently thinking about.
For a while now I was thinking about how to combine Modern C++, templates and Qt better. And with verdigris, a small library that enables usage of templates in Qt, this seemed doable. And also I wanted to explore the usages for TMP with mp11, boosts new and just this year released TMP library for C++11. Unlike hana, I actually can make use of it, and as verdigris enables usages of templates in Qt, I thought to try and combine them. For some time I was thinking about how to generate simple UIs, instead of writing them with Qt Designer and some more boilerplate code. I chose boost fusion and its adapted struct mechanism to enable easy access to members of such struct, but also know the member names. Lets dive deeper into this idea...
Maybe I should mention first, what I'm after...
As I've written earlier, I'd like to generate UI from a basic type, which has been adapted with fusion. So far I've shown that it generally works, at least the basic concepts. The proxy between the member type of the struct and the actual control displaying its value is a tag type. In the last post I've showed how this works for a simple EditDialog. Since then I've been busy with Meeting C++ 2018, which also includes working with my current code base. This gave me an insight, on how different the above approach is to the classic one. But lets first dive into the easy questions this approach offers.
Now that the basic concept works, its time to think about how the interfaces should look. As I'd like to keep it generic, its all about static polymorphism and what kind of member or free functions one needs to implement to support a certain feature. I don't want to introduce base classes, from which I derive my domain classes in order to use this (yet highly experimental) framework. Comboboxes are a good example of how different values in the domain model can result in the same control, and even same look. Do you store the index, text or data element of a QComboBox? Is the ComboBox editable? Also, how to provide a simple way to set the values of the combobox from the domain model?
Luckily, I might not have to answer all these questions. My experience with the "implement only what you actually use" approach is very good with my CMS. Yet annoying once you step of the usual path and suddenly have to add support for X where previously was no X. But the QComboBox situation is a good one to think about, to find solutions for the things I need to implement.
Then there is any other non trivial type you'd like to support to be displayed in the UI. Some options to handle this:
- If you can check that it is a fusion sequence, that type can be simply put into its own widget.
- If its not a fusion sequence and not a otherwise supported type (think POD / std::string), ignore or error?
- Pointers & References. Some wrapper is easy to write to handle both.
- Wrapper types like shared - and unique_ptr: could easily be forwarded to a pointer handler
And then containers, there probably needs to be support for at least adding and removing. And a set of classes just offering different UI choices for displaying a container. Sometimes its good to display the whole container in a table like view, sometimes its better to be able to select container entries to be then displayed in a control. For the value_type of a container more or less above list kicks in, but that can be handled by the different container UI templates.
And from the UI side of things, I need to decide on some infrastructure to easily supply the needed model types for a certain domain model. And how to give access to them. Qt handles all UI as single threaded, for both widgets and QML. So some FactorySingleton could be an evil but 'well' working option.
Interfacing with my own code base
If you're not custom to work with Qt every now and then, you might have no idea how alien a generic, template based approach is, that integrates with QObject. This new code is circles, while the old, Qt based code is squares and rectangles every where. Verdigris is header only, so its not that its hard to integrate or use in your Qt code base. Its just that my new approach based on verdigris is so much different from what I've written previously.
Qt offers its model/view architecture as a proxy between your domain model and UI. But this requires you to write one or more model classes for each domain type in your UI. So most people will just write the code to connect domain model and UI in the UI class, which you'd have to write anyways. And for my CMS, I currently use this approach to exchange data between domain model and ui. Qt offers a similar way with QDataWidgetMapper based on its model view API. Currently I favor the model/view API from Qt, as with verdigris, each model simply is a template. Qt already has the right wheel here, so I can drop my own code for this in the future.
But right now, I'm not able to simply drop in the current prototype into my CMS, as its just to different. Last week I wrote the widget for a class called ForwardingPage, which only consists of two strings and a bool. The Widget for this in the new model would be simply EditWidget<ForwardingPage>, which would take an instance of a model/QModelIndex for what needs editing. But the application context for both, widget and domain class, currently have no way to support this new approach. So it was easier to just quickly write a simple but boiler code rich ForwardingPageWidget class.
So far I consider this experiment a success, it shows a clear and good way into a future where I have to write much less UI code. Less boilerplate. Thats all good, so next steps is to explore both, the API details to be implemented, while also getting a feel for how to integrate this into my own, now kind of having a legacy feel Qt code.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!