1

I am trying to build a Qt project with qml. The qml metatype registration looks into the top level qml directories only. I would like to have it look into subdirectories as well.

The project is structured like this:

./CMakeLists.txt
./ui
./ui/MainWindow.qml
./src
./src/controller
./src/controller/Foo.cpp
./src/controller/FileController.cpp
./src/controller/FileController.h
./main.cpp

My CMakeLists.txt looks as follows:

cmake_minimum_required(VERSION 3.25)
project(testproject)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick Widgets)

qt_standard_project_setup()

qt_add_executable(testproject)
target_sources(testproject PRIVATE 
    main.cpp
)


target_link_libraries(testproject PRIVATE 
    Qt6::Widgets
    Qt6::Gui 
    Qt6::Quick 
)

set_target_properties(testproject PROPERTIES
        CXX_STANDARD 17
        CXX_STANDARD_REQUIRED ON
        CXX_EXTENSIONS OFF
        OUTPUT_NAME some_test_exec
)

target_include_directories(testproject PUBLIC ${PROJECT_SOURCE_DIR}/include)


qt_add_qml_module(testproject
        URI testproject
        VERSION 1.0
        SOURCES
            src/controller/FileController.h
            src/controller/FileController.cpp
        QML_FILES
            ui/MainWindow.qml
)

I have a main function which starts the application:

#include <iostream>
#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char* argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/testproject/ui/MainWindow.qml"_qs);
    engine.load(url);
    return app.exec();
}

I created a simple qml file in MainWindow.qml in here

ui/MainWindow.qml

which looks like this:

import QtQuick
import QtQuick.Controls

ApplicationWindow {
    id: mainwindow
    visible: true
    width: 640
    height: 480
    menuBar: MenuBar {
        Menu {
            title: "File"
            MenuItem { text: "open" }
        }
    }
}

in the

src/controller

subdirectory, I create the following header file FileController.h

#include <QObject>
#include <QtQml/qqmlregistration.h>

class FileController : public QObject {
    Q_OBJECT
    QML_ELEMENT // WITHOUT THIS IT COMPILES FINE
    Q_PROPERTY(QString file  READ name WRITE setName NOTIFY filenameChanged)
private:
    QString filename;
    void setName(QString name);
public:
    FileController(QObject *parent);
    QString name();
signals:
    void filenameChanged();
};

and some implementation details in FileController.cpp

#include "FileController.h"

FileController::FileController(QObject *parent) : QObject(parent) {}

void FileController::setName(QString name) {filename = name;}

QString FileController::name() {return filename;}

When compiling, I get the following error:

[13/19] Building CXX object CMakeFiles/test....dir/testproject_qmltyperegistrations.cpp.o
FAILED: CMakeFiles/testproject.dir/testproject_qmltyperegistrations.cpp.o 
/usr/bin/c++ -DQT_CORE_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_QMLINTEGRATION_LIB -DQT_QMLMODELS_LIB -DQT_QML_LIB -DQT_QUICK_LIB -DQT_WIDGETS_LIB -I/some/test/directory/build/testproject_autogen/include -I/ome/test/directory/include -I/some/test/directory -isystem /usr/include/qt6/QtQml/6.4.1 -isystem /usr/include/qt6/QtQml/6.4.1/QtQml -isystem /usr/include/qt6/QtCore/6.4.1 -isystem /usr/include/qt6/QtCore/6.4.1/QtCore -isystem /usr/include/qt6/QtCore -isystem /usr/include/qt6 -isystem /usr/lib/qt6/mkspecs/linux-g++ -isystem /usr/include/qt6/QtQml -isystem /usr/include/qt6/QtQmlIntegration -isystem /usr/include/qt6/QtNetwork -isystem /usr/include/qt6/QtWidgets -isystem /usr/include/qt6/QtGui -isystem /usr/include/qt6/QtQuick -isystem /usr/include/qt6/QtQmlModels -isystem /usr/include/qt6/QtOpenGL -fPIC -std=c++17 -MD -MT CMakeFiles/testproject.dir/testproject_qmltyperegistrations.cpp.o -MF CMakeFiles/testproject.dir/testproject_qmltyperegistrations.cpp.o.d -o CMakeFiles/testproject.dir/testproject_qmltyperegistrations.cpp.o -c /some/test/directory/build/testproject_qmltyperegistrations.cpp
/some/test/directory/qt/qml_in_other_dir/build/testproject_qmltyperegistrations.cpp:10:10: fatal error: FileController.h: No such file or directory
   10 | #include <FileController.h>

Apparently when registering qml metatype, Qt is not looking in the subdirectories for the corresponding headers. Or it should copy them, but doesn't. It doesn't have FileController.h in a directory in its include path.

  1. Why isn't the directory structure preserved with CMake and Qt?
  2. I want to have some logic in the qml files (specifically here open a file dialog and pass the result to the C++ class). In order to have qml and C++ interact, I need to register the class with the QML_ELEMENT macro. Am I doing something fundamentally wrong? What is the correct way to do this? (I could always fiddle with the include paths or put everything in a base directory, but that seems wrong).

Edit: the issue still occurs e.g. when putting Filecontroller.h into

./include/controller/

and adding

include

to the target_include_directories and adapting the include path in FileController.cpp to something like

#include <controller/FileController.h>
iam_peter
  • 3,205
  • 1
  • 19
  • 31
Mohammed Li
  • 823
  • 2
  • 7
  • 22

1 Answers1

2

You either need to add include_directories(src/controller) to your CMake file or set the include to #include <src/controller/FileController.h>.

Have a look at this SO question.

EDIT: I found these bugreports QTBUG-87221, QTBUG-101146 . The patch adds a note to the documentation:

All headers that declare QML types need to be accessible without any prefix from the project's include path.

iam_peter
  • 3,205
  • 1
  • 19
  • 31
  • That is what I meant by item 2. I do not want to add every single subdirectory of every single class that uses the QML_ELEMENT macro into the include directories, this feels wrong and could lead to hard to track bugs when there are multiple header files with the same name in distinct subdirectories. The include directories themselves are fine imho, as the code compiles whithout the QML_ELEMENT macro. Edit: some more googling: this seems to be the way the QML_ELEMENT macro works. The alternative seems to be something like qmlRegisterType in the main. Probably will go with that. – Mohammed Li Dec 19 '22 at 21:00
  • And what about adding a CMakeLists.txt to the subdirectory? – iam_peter Dec 19 '22 at 21:16
  • Have a look at my edit and https://bugreports.qt.io/browse/QTBUG-101146 – iam_peter Dec 19 '22 at 21:56