How can I organize QML files into nested folders?

Viewed 1151

I have a sample project at https://github.com/jh3010-qt-questions/qml_location

If my hierarchy looks like:

$ tree qml_location/
qml_location/
├── MyDeepComponent.qml
├── MyDeepComponentForm.ui.qml
├── main.cpp
├── main.qml
├── qml.qrc
└── qml_location.pro

Then I can write main.qml like:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
  width: 640
  height: 480
  visible: true
  title: qsTr("Hello World")
  MyDeepComponent {}
}

and it will work.

However, I would like to organize some QML files into a folder hierarchy and not have them all at the same level.

For example, if I move to:

$ tree qml_location/
qml_location/
├── main.cpp
├── main.qml
├── qml
│   ├── MyDeepComponent.qml
│   ├── MyDeepComponentForm.ui.qml
│   └── more
│       ├── MyDeeperComponent.qml
│       └── MyDeeperComponentForm.ui.qml
├── qml.qrc
└── qml_location.pro

and have a main.qml that looks like:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

Window
{
  width: 640
  height: 480
  visible: true
  title: qsTr("Hello World")
  Column
  {
    MyDeepComponent {}
    MyDeeperComponent {}
  }
}

Qt Creator tells me that MyDeepComponent and MyDeeperComponent are Unknown.

When I try to run, I get the error: MyDeepComponent is not a type

What can I do so this will work?

One caveat, I do not want to place a special or additional import at the top of main.qml. Is this still possible?

qml.qrc

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
        <file>qml/MyDeepComponent.qml</file>
        <file>qml/MyDeepComponentForm.ui.qml</file>
        <file>qml/more/MyDeeperComponent.qml</file>
        <file>qml/more/MyDeeperComponentForm.ui.qml</file>
    </qresource>
</RCC>

qml_location.pro

QT += quick

CONFIG += c++11

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        main.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
5 Answers

This answer doesn't meet the requirement of no imports, but it is worth mentioning anyway that it can be solved with a couple of imports at the top of main.qml.

https://github.com/jh3010-qt-questions/qml_location/tree/import_solution

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

import "qml"
import "qml/more"

Window
{
  width: 640
  height: 480
  visible: true

  title: qsTr("Hello World")

  Column
  {
    MyDeepComponent
    {
    }

    MyDeeperComponent
    {
    }
  }
}

You can do something like this:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

Window
{
  width: 640
  height: 480
  visible: true
  title: qsTr("Hello World")
  Column
  {
    Loader {
        source: "qrc:/qml/MyDeepComponent.qml"
    }
    Loader {
        source: "qrc:/qml/more/MyDeeperComponent.qml"
    }
  }
}

Though I'm not quite sure why you would want to. Might help to describe the problem you are trying to solve?

https://github.com/jh3010-qt-questions/qml_location/tree/create_object_solution

createObject can be used to load the component directly into the Column.

main.qml looks like:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

Window
{
  width: 640
  height: 480
  visible: true

  title: qsTr("Hello World")

  property var componentNames: [ "qml/more/MyDeeperComponent.qml", "qml/MyDeepComponent.qml" ]

  function generateObjects()
  {
      function generateOneObject( name )
      {
          var component
          var componentObject

          function finishCreation()
          {
              componentObject = component.createObject( contentColumn );
          }

          component = Qt.createComponent( `qrc:/${name}` )

          if ( component.status === Component.Ready )
          {
              finishCreation()
          }
          else
          {
              component.statusChanged.connect( finishCreation );
          }
      }

      for ( var index in componentNames )
      {
          generateOneObject( componentNames[ index ] )
      }
  }

  Component.onCompleted: {
      generateObjects()
  }

  Column
  {
    id: contentColumn
  }
}

Another solution is to move main.qml into the qml folder. This allows main.qml to find MyDeepComponent because they are siblings. To find MyDeeperComponent, main.qml can import the "more" directory.

This solution in represented in the all_in_one_solution branch.

directory structure

$ tree qml_location/

qml_location/
├── main.cpp
├── qml
│   ├── MyDeepComponent.qml
│   ├── MyDeepComponentForm.ui.qml
│   ├── main.qml
│   └── more
│       ├── MyDeeperComponent.qml
│       └── MyDeeperComponentForm.ui.qml
├── qml.qrc
├── qml_location.pro
└── qml_location.pro.user

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

  QGuiApplication app(argc, argv);

  QQmlApplicationEngine engine;

  const QUrl url(QStringLiteral("qrc:/qml/main.qml"));

  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();
}

qml.qrc

<RCC>
    <qresource prefix="/">
        <file>qml/main.qml</file>
        <file>qml/MyDeepComponent.qml</file>
        <file>qml/MyDeepComponentForm.ui.qml</file>
        <file>qml/more/MyDeeperComponent.qml</file>
        <file>qml/more/MyDeeperComponentForm.ui.qml</file>
    </qresource>
</RCC>

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

import "more"

Window
{
  width: 640
  height: 480
  visible: true

  title: qsTr("Hello World")

  Column
  {
    MyDeepComponent
    {
    }

    MyDeeperComponent
    {
    }
  }
}

EDIT: I'm reading your part of the question where you state that you don't want an import at the top. I don't think this is possible, without throwing a bunch of addImportPath in your main (for each folder that you are to create) or heavy fiddling with qrc-aliases. Leaving the rest of the answer for a future visitor or for if you change your mind ;-)


You can use a qmldir file to make them available in a namespace of choice. But there is a bit of a learning curve. The qmldir file has to be situated in folder which resembles the namespace.

For example if you want to use MyApplication as namespace, you have to put the qmldir file in a folder "MyApplication" (within the qml import path). If you want MyOrg.application1 as namespace, you have to put qmldir in "MyOrg/application1".

For your situation, I choose the first option. I would make a separate qrc file for the importable qml files, which is also needed if you want to easily put all qml files in a "qml" folder:

qml/qml.qrc:

<!DOCTYPE RCC>
<RCC version="1.0">
    <qresource prefix="/qml/MyApplication">
        <file>MyDeepComponent.qml</file>
        <file>MyDeepComponentForm.ui.qml</file>
        <file>more/MyDeeperComponent.qml</file>
        <file>more/MyDeeperComponentForm.ui.qml</file>
        <file>qmldir</file>
    </qresource>
</RCC>

A simple qmldir would look something like this:

module MyApplication

MyDeepComponent         1.0 MyDeepComponent.qml
MyDeeperComponent       1.0 more/MyDeeperComponent.qml

You can see that I used a prefix, such that the files don't have to move. If you are to go to great heights with the app and want a MyApplication 2.0 namespace, you can use the prefix /qml/MyApplication.2

Related