3

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
James Hudson
  • 844
  • 6
  • 19
  • 1
    add `import "qml"` and `import "qml/more"` – eyllanesc Mar 26 '21 at 19:02
  • @eyllanesc, The OP said he didn't want to use `import`. Not sure the reasoning though. @James Hudson, I haven't tried this, but it might work if you use an an alias in your .qrc file. – JarMan Mar 26 '21 at 19:06
  • It is possible with aliases, but then in QtCreator it all looks like they are in the same folder, so the only benefit is ordering on disk/git, not in your IDE. @JamesHudson why the requirement of no import's? – Amfasis Mar 26 '21 at 19:32
  • I could have been more specific in that I do not want an import that imports MyDeepComponent directly. The reason is related to https://stackoverflow.com/questions/66818329/how-do-you-dynamically-load-multiple-components-with-qml/66819256#66819256 ... I will need to dynamically load qml files and to reduce code maintenance, I don't want adding an import statement a requirement. however, as @eyllanesc points out, it does look like look like ```import "qml"```, etc. is a viable option. – James Hudson Mar 26 '21 at 19:39

5 Answers5

3

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
    {
    }
  }
}
James Hudson
  • 844
  • 6
  • 19
2

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?

David K. Hess
  • 16,632
  • 2
  • 49
  • 73
  • The reason is related to https://stackoverflow.com/questions/66818329/how-do-you-dynamically-load-multiple-components-with-qml/66819256#66819256 ... I will try out your solution. What I will be doing is dynamically generating those loader objects based on using either QDirIterator or FolderListModel to find the set of of QML files in Resources which define components that need to be inserted. The list of components is large and will grow. I am not sure if this is the appropriate forum to discuss my use case. We can move this to https://www.reddit.com/r/QtFramework/ or elsewhere. – James Hudson Mar 27 '21 at 03:06
  • Why don’t you just use that solution then? Dynamically load and create the components? – David K. Hess Mar 27 '21 at 12:16
  • 1
    That is a good question. In another context, I tried it and it was not working. Tried it in this context and it is working. I will need to figure out why. In any case, I tried your Loader solution and it works. https://github.com/jh3010-qt-questions/qml_location/tree/loader_solution – James Hudson Mar 29 '21 at 13:34
1

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
  }
}
James Hudson
  • 844
  • 6
  • 19
1

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
    {
    }
  }
}
James Hudson
  • 844
  • 6
  • 19
0

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

Amfasis
  • 3,932
  • 2
  • 18
  • 27
  • I wanted to try to get this solution working, but I am failing at the moment. I have a branch called 'qmldir_solution' at https://github.com/jh3010-qt-questions/qml_location/tree/qmldir_solution which tries to implement what you suggest. – James Hudson Mar 26 '21 at 20:41
  • Tried a variant of what you suggested at https://github.com/jh3010-qt-questions/qml_location/tree/qmldir_alt Unfortunately, it does not work either. – James Hudson Mar 26 '21 at 21:09
  • You still need the `addImportPath(":/qml:);`. I threw away my changes and cannot find them in the trash can, otherwise would have uploaded them for you – Amfasis Mar 27 '21 at 21:34
  • I think my problem is that I am having difficulty understanding and implementing your answer. If you can provide more details or explain where I have gone wrong, I would be interested. I have tried adding the import, but it did not help. – James Hudson Mar 29 '21 at 13:03
  • I recreated it and uploaded it to a gist: https://gist.github.com/amfasis/193625eff906d4648491c2e6144698e9 (I added comments in some of the file about location, because gist doesn't allow slash in filename) – Amfasis Mar 29 '21 at 14:06
  • Thank you for that. I can confirm that your solution does work. I did assume that you mean qml.qrc where you said main.qrc. ( https://github.com/jh3010-qt-questions/qml_location/tree/qmldir_solution )The bit I was missing was the ```import Components 1.0``` in main.qml. However, Qt Creator is complaining "QML module not found (Components)." For a clean solution, need to resolve that issue. I did try adding ```QML_IMPORT_PATH += $$PWD/qml``` and ```QML_DESIGNER_IMPORT_PATH += $$PWD/qml``` to the project file, but it did not help. – James Hudson Mar 29 '21 at 15:24
  • 1
    I did figure a solution to the problem. As you eluded to above, for this to work well, qmldir and the qml files should be in a folder named Components to follow how qmldir defines the component. Qt Creator is then happy and the code runs without error. I have updated the project at https://github.com/jh3010-qt-questions/qml_location/tree/qmldir_solution – James Hudson Mar 29 '21 at 15:43