Qt6 Proper way to use C++ in QML
-
I've just moved over to Qt6 with CMake with my work being done in Qt4 and 5 some time ago. In Qt 5 the setContextProperty in main.cpp was used to define a C++ class that had functions, etc. to be used in QML but that is not the preferred way in Qt6. However, after reading various documents such as https://doc.qt.io/qt-6/qtqml-writing-a-module.html and https://www.qt.io/blog/qml-modules-in-qt-6.2 as well as the Qt documents on QML and C++ interfacing I am totally confused!
This is fairly long but I appreciate your patience and any help. My frustration can be summed up by this quote from an article online. The author has nailed the issue with Qt documentation - which I feel is very good but could be much better. Moving to Qt6 means we need to do things differently and I want to do good practices but Qt has fallen down on some of the documentation with C++ interfacing.
"The Qt/QML documentation is comprehensive, but one flaw I find is that the framework has no one (recommended) way of doing stuff. You can find all method parameters and possible options, but if you want to know how to change the colour of the text on a Button{}, good luck searching on StackOverflow. Same goes for integrating C++ with QML. The Qt documentation provides an overview of different integration methods but does not tell you which one is best. It just tells you what is possible and leaves it up to you to decide. There is a flowcharts to help you which method to use, but almost all guides and examples online just use rootContext->setContextProperty()"
I have a project directory containing main.cpp and a main.qml file and a subdirectory with a qml file and a cpp file with it's header which define a class DataProcess containing a function, test.
After reading the blog post (https://www.qt.io/blog/qml-modules-in-qt-6.2) and the Qt document (https://doc.qt.io/qt-6/qtqml-writing-a-module.html) my understanding is that if I setup my CMakeLists.txt in the main project directory and the subdirectory holding additional qml and C++ code that building this would produce a plugin that can be used in QML. I do have add these to main.cpp.
#include <QtQml/QQmlExtensionPlugin.h> Q_IMPORT_QML_PLUGIN(DataprocessPlugin) Q_IMPORT_QML_PLUGIN(UtilitiesPlugin)
I then have to import Dataprocess (the URI defined for the qml module in it's CMakeLists.txt file in QML with
import Dataprocess
According to the documentation above and other documents for Qt6 that should be all I have to do and then I can use my C++ functions but it is not working. Unfortunately, there is a lot of Qt5 articles and not a lot of Qt6.
Today I found a post; in this forum, https://forum.qt.io/topic/142327/c-communication-with-qml/2, which indicates I need to use qmlRegisterType<MyClass>("BLAH", 1,0, "blah") in main.cpp.
Based on the articles cited above l thought that setting up the CMakeLists.txt files in the project and any subdirectories and including the plugin macros in main.cpp would be sufficient to include any C++ code also since it's defined in the SOURCES entry of the qt_add_qml_module() entries and a plugin is supposed to be generated that includes the C++ class.
Since that doesn't work does that mean that the macros I included in main.cpp are only meant to include QML modules? If so then I need to either use the qmlRegisterType or do a setContecProperty.
If I have it right qmlRegisterType simply makes the class and it's components available to a QML module if I do import MyClass in any QML class I need it to be used in as in MyClass.somefunction() in a QML module. Is it correct that this creates a instance of the class whenever it is imported into a module?
Using setContextProperty, as I understand it, creates an instance and that instance is used by all QML modules?l
Some authors feel setContextproperty is not a good idea because it does not separate things as they should be. and that qmlRegisterType creates separate instances for each QML module - if understand it correctly.
-
As if it weren't confusing enough I found this article https://web.archive.org/web/20211003125109/https://www.qt.io/blog/qml-type-registration-in-qt-5.15 which says we don't need qmlRegisterType but simplyh add QML_ELEMENT to my C++ class which I have done but still no luck when I do the import of Dataprocess, the URI defined in CMakeLIsts.txt.
Main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml/QQmlExtensionPlugin.h> Q_IMPORT_QML_PLUGIN(DataprocessPlugin) Q_IMPORT_QML_PLUGIN(UtilitiesPlugin) int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(u"qrc:/FlightSimDataAnalyzer/main.qml"_qs); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
Main projects CMakeLists.txt
cmake_minimum_required(VERSION 3.16) project(FlightSimDataAnalyzer VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 REQUIRED COMPONENTS Quick Qml Core ) qt_add_executable(appFlightSimDataAnalyzer main.cpp ) qt_add_qml_module(appFlightSimDataAnalyzer URI FlightSimDataAnalyzer VERSION 1.0 QML_FILES main.qml ) add_subdirectory(Dataprocess) add_subdirectory(Utilities) set_target_properties(appFlightSimDataAnalyzer PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE ) target_link_libraries(appFlightSimDataAnalyzer PRIVATE Qt6::Quick data_processplugin utilitiesplugin ) install(TARGETS appFlightSimDataAnalyzer BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
CMakeLists.txt for QML and C++ files in the subdirectory.
qt_add_library(data_process STATIC) qt_add_qml_module(data_process URI "Dataprocess" VERSION 1.0 QML_FILES GetRawData.qml SOURCES dataprocess.h dataprocess.cpp )
The header file.
#ifndef DATAPROCESS_H #define DATAPROCESS_H #include <QObject> #include <QDebug> #include "qqmlintegration.h" class DataProcess : public QObject { Q_OBJECT QML_ELEMENT public: explicit DataProcess(QObject *parent = nullptr); Q_INVOKABLE void rawdataprocess(QString sim, QString rawfile); signals: public slots: private: QString m_datafile; }; #endif // DATAPROCESS_H
The C++ file
#include "dataprocess.h" DataProcess::DataProcess(QObject *parent) : QObject{parent} { } void DataProcess::rawdataprocess(QString sim, QString rawfile) { qDebug() << "I am in processing."; }