Porting applications from QWidget to QML under Desktop
Recently, I often come across projects for desktop that need to be ported from QWidget to QML. Someone wants to write from scratch, someone to transfer old developments. In any scenario, this is the popularization of QML, which I am very happy about. I also decided to be useful and share my best practices and situations from personal practice. Perhaps some of them will seem simple and understandable to you, but if I missed something, I will be glad to read about it in the comments.
Briefly about QML and QWidget
QWidget is a preceding QML technology for creating a user interface, based on the use of pre-created desktop-style components and providing an API for interacting with them.
About a year ago, I found a great gitAhead tool. It is written in QWidget. In general, I am satisfied with most of the functionality and it often helps me. But this example clearly shows that widgets lack flexibility and functionality. Because of this, it is not possible to cover more user scenarios and make a friendlier interface. As a result, instead of an excellent commit and diff tree, we have a huge number of restrictions.
Application design and its impact on porting
Classically, Qt advises us to adhere to the MVC \ MVP patterns, where all interaction with the UI is placed in separate entities, which allows us to change them without affecting the rest of the applications and business logic. Abstraction of elements and the ability to embed a QML component in a standard QWidget allows you to flexibly approach the plan for further work. If you are at this point now, then I would like to advise you to think over the requirements and functionality of the future application, draw its layout, consult with designers, interview users or the customer. In general, decide how you will live in the near future, and what issues you will decide. Below are the options a project can take.
Plan A “From a Blank Slate”
We remove all the old UI, widgets, dependencies, legacy code and leave only the requirements and functionality that we will need to restore. A good opportunity to improve the architecture, prepare the way for new changes. When preparing, it is necessary to consider the following things that change during the transition from one library to another and it is better to think over them in advance:
- Library of elements. Create your initial database of elements, where you write down the basic settings for the elements, their appearance and default states. If possible, wrap them with tests. In one of the project configurations, make them copied to the project folder and not compiled into binary *.qrc, this will allow you to edit these assemblies in runtime without additional compilation and connect qmllive and its analogs.
- Lack of QAction. In QWidget applications, QAction is a frequent guest, expanding the ability to intercept user actions both by pressing a button on the screen and by intercepting hot keys. Unfortunately, in QML, QAction moved in a slightly different form and allows only QML wrappers to intercept predefined hot keys, which imposes restrictions on its use inside C++ or to create dynamically. Here you can write your implementation, use qobject's event-filter, or use third-party projects such as QHotkey .
Otherwise, everything here is typical for a C++ and QML application.
Plan B "We have Tipo agile"
Since for Agile one of the main values is a working product, we must make the move in stages, without regressing the functionality. To do this, we divide the entire UI into logical segments with a minimum number of interdependencies and implement them separately. Using gitAhead as an example, you can consider the following option (highlighted in green):
For each of the areas we will create a parent widget in which we put the root QML element. There are two ways to do this.
QQuickWidget *view=new QQuickWidget; view->setSource(QUrl::fromLocalFile("myqmlfile.qml")); view->show();
Or call the QWidget :: createWindowContainer (.) Method
QQuickView *view=new QQuickView();... QWidget *container=QWidget::createWindowContainer(view); container->setMinimumSize(...); container->setMaximumSize(...);... widgetLayout->addWidget(container);
The difference between the proposed methods is that QQuickWidget is a more flexible alternative than QWidget :: createWindowContainer (.), Behaving more like a regular QWidget. At the same time, more functionality is achieved due to minor losses in performance.
Further work is done as with the usual QQuickView, except for the following features:
- Limited context. Each parent QWidget will have its own QML context, which in turn imposes restrictions on the scope. Own property, functions, objects, etc. Each widget essentially becomes an independent sandbox
- Limit the size of the widget. When creating a widget, you can create a rule so that the size of the widget follows the size of the content inside and in fact this is correct. But this applies only to the root elements inside this QML, and all temporary objects with sizes larger than the widget will be trimmed with borders. A good example here is a tooltip for buttons, if the buttons are small, but there is a big description, then part of the tooltip will be hidden outside the widget. If the widget container sizes are not set to the size of the content, then this can also lead to problems when scaling the interface. This problem is partially solved by elements from the lab . Example of going out of bounds and incorrect layout
- 3D graphics. Everything is complicated here and full of restrictions, it is recommended to use QQuickWidget and there is not a small chance that your script will either require “crutches” or large investments, so read the restrictions in advance, or immediately implement them for use in QML
It is worth recalling that the above restrictions apply only to the period while you transfer your element base. The restrictions described in Plan A apply to the entire application. The last element will be the main application window, where it will be necessary to arrange everything in accordance with the design, focus, and other requirements. It will remain to remove the last dependencies of the application from the QWidget library, remove it from the delivery and that’s it, the work is finished. Well done!
I still have a lot of work to do with gitAhead, and not only on the UI, but about how it happens I described. If you liked the idea and would like to participate, I will be glad to cooperate. My repository will be here .