PDA

View Full Version : dynamically loaded components cannot reference types



doulos
14th August 2014, 12:44
Hello,

we have a component implemented in a plugin, which uses Loader to dynamicaly instantiate other components. Example:


import ourcomponent 1.0

Rectangle {
MyComponent {
loadthis: "someother.qml"
}
}

the ourcomponent namespace and the MyComponent type are defined in a plugin. MyComponent uses Loader to instantiate the component defined in "someother.qml".

Now it turns out that while this works well for simple cases where "someother.qml" is a self-contained component (only using stuff from the QtQuick namespace), we are facing problems when "someother.qml" references other local QML types from the same project. In that case we see messages like "AnotherItem is not a type", even though AnotherItem.qml is located right alongside the someother.qml file. It seems that components loaded from within the plugin cannot see the types defined in the calling project. What can we do?

Heres the example someother.qml file:


import QtQuick 2.0

Rectangle {
width: 100
height: 62

AnotherItem {} //visible at dev time in QtCreator, but not at runtime
}

anda_skoa
14th August 2014, 14:39
That should work, at least this small example does

main.qml


import QtQuick 2.0

Rectangle {
width: 200
height: 200
color: "lightsteelblue"

Loader {
source: "loaded.qml"
}
}

loaded.qml


import QtQuick 2.0

Rectangle {
width: 40
height: 40
color: "red"

LocalItem {
anchors.centerIn: parent
width: 20
height: 20
}
}

LocalItem.qml


import QtQuick 2.0

Rectangle {
color: "yellow"
}


You could try importing the directory


import "." as Local

Local.AnotherItem {
}


Cheers,
_

doulos
14th August 2014, 15:05
of course, what you show does work, as long as all components are located withing the same module (or application).

However, in our scenario, the code that uses the Loader is located in a plugin, and the loaded QML is from an application that uses the plugin. All QML resources (both in the plugin and in the application) are stored as resources.

BTW, heres what I see if I follow your import "." suggestion:


qrc:ProjectData.qml:74:5: Type Local.CustomerGrid2 unavailable
./CustomerGrid2.qml: Network error
interesting, eh?

anda_skoa
15th August 2014, 09:37
Is CustomerGrid2.qml in the same resource folder as the QML file being loaded?

Have you tried explicitly passing the qrc URL to the loader?

Cheers,
_

doulos
15th August 2014, 11:22
yes, CustomerGrid2.qml is located right alongside the QML being loaded. Changing the Loader.source to a "qrc:" URL doesn't change anything.

However, I have meanwhile made progress. After giving up on the attempt of creating a plugin, I copied all the QML files into a subdirectory in the project that uses the components. I then edited the QML files that use the components, changing the import moduleid 1.0 to a directory import import "subdirectory". Alas, this time even the initially loaded QML was not found, BUT the error message gave a clue:

qrc:///navigation/t1.qml: File not found
After adding a "/" to the QML reference, the file was found and worked normally even with nested components.

I then returned to the plugin code, and tried the "/" as well. And lo behold - even though the error messages had been different (and totally weird at times), the solution worked just the same

wysota
15th August 2014, 14:11
Hello,

we have a component implemented in a plugin, which uses Loader to dynamicaly instantiate other components. Example:


import ourcomponent 1.0

Rectangle {
MyComponent {
loadthis: "someother.qml"
}
}

the ourcomponent namespace and the MyComponent type are defined in a plugin. MyComponent uses Loader to instantiate the component defined in "someother.qml".

Now it turns out that while this works well for simple cases where "someother.qml" is a self-contained component (only using stuff from the QtQuick namespace), we are facing problems when "someother.qml" references other local QML types from the same project. In that case we see messages like "AnotherItem is not a type", even though AnotherItem.qml is located right alongside the someother.qml file. It seems that components loaded from within the plugin cannot see the types defined in the calling project. What can we do?

Heres the example someother.qml file:


import QtQuick 2.0

Rectangle {
width: 100
height: 62

AnotherItem {} //visible at dev time in QtCreator, but not at runtime
}

All paths are by default resolved relatively to the path of the calling component. Therefore if your plugin tries to load url called "someother.qml" then by default this will be resolved relative to where the plugin is located. If you want to resolve the path relative to the context where you pass "someother.qml" then use Qt.resolvedUrl:

import ourcomponent 1.0

Rectangle {
MyComponent { loadThis: Qt.resolvedUrl("someother.qml") }
}
If that doesn't work then it is always an option to pass not a URL but rather a component definition to your component:

import ourcomponent 1.0

Rectangle {
MyComponent { loadThis: Component { url: "someother.qml" } }
}

Of course you will then need to teach MyComponent to expect a Component instead (or aside to) a url.

doulos
15th August 2014, 17:15
All paths are by default resolved relatively to the path of the calling component. Therefore if your plugin tries to load url called "someother.qml" then by default this will be resolved relative to where the plugin is located. If you want to resolve the path relative to the context where you pass "someother.qml" then use Qt.resolvedUrl:

Please notice that I was not talking about "paths" not being resolved correctly. When I passed "someother.qml" to the plugin, the component was found correctly, although it resided in the calling application. However, components nested inside were not (except those from the global namespace). I tried the Qt.resolvedUrl recipe, and it did the same as prefixing with "/" - now the component was loaded and subcomponents also. It was a tough day, however, tracking this down, and I cannot say that I can make sense of it. I dint try the Component way, but assume that it would work, also.

Thanks for yout input, anyway.

wysota
15th August 2014, 22:09
Please notice that I was not talking about "paths" not being resolved correctly.

In my opinion you were, e.g. here:


we are facing problems when "someother.qml" references other local QML types from the same project.
I'm pretty sure "other local QML types" are defined in files bearing some kind of path relations to the calling file.


. I tried the Qt.resolvedUrl recipe, and it did the same as prefixing with "/" - now the component was loaded and subcomponents also.

Because that's basically what Qt.resolvedUrl() does -- it turns relative paths into absolute ones.


It was a tough day, however, tracking this down, and I cannot say that I can make sense of it.
It is easy once you understand why this happens. Essentially this provides locality and proper encapsulation as well as a kind of security of an application. Imagine there is a "MyComponent" type in your app defined in a file which is called by your application file called MyApplication.qml. Now you can take all your files, put them into a subdirectory of another project and instantiate by calling MyApplication {}. Now imagine this "external" application also defines a type called MyComponent. If there was no locality, you'd have a clash where the "internal" application could instantiate MyComponent defined in the external application instead of its own. This would inhibit security as well (injecting a component definition into the contained namespace). Resolving paths relative to the calling document prevents these problems.

doulos
16th August 2014, 10:22
Resolving paths relative to the calling document prevents these problems.
well, I still am not convinced. In my non-plugin scenario, I had the components in a subdirectory. If I directly referenced the root component via directory import, it was found and all nested types as well. If I load the root component through a Loader, suddenly nested components are NOT found. Again:


main.qml
navigation
Reusable.qml <-- uses Nested
Nested.qml

now, if in main.qml I simply do a directory import and use the Reusable type, everything works well:

import "navigation"
Rectangle {
Reusable {
}
}
However, if I load Reusable dynamically

Rectanngle {
Loader {
source: "navigation/Reusable.qml"
}
}
I see an error message that file "navigation/Nested.qml" was not found. In a plugin scenario, the message simply says "Nested is not a type". If this makes sense somehow, it is still almost impossible IMO to find a smooth path from that message to a solution, which may mean that the concepts are not that clear and plausible, or at least not well explained. As I said, it took a day of desperate poking for me.

wysota
16th August 2014, 14:33
With the first approach you are effectively importing all types defined in "navigation" into the scope of the current document (in other words "navigation" becomes a sort of plugin that contains documents present in that directory). In the second approach you get regular path resolution. Whether this works or not, depends on the scope of the containing document (i.e. the location of the file containing the last snippet).

If you want to load "Reusable" from within main.qml, you will refer to "navigation/Reusable.qml" or "Reusable.qml" having imported "navigation" module. If you want to load "Reusable" from Nested.qml, you will have to refer to Reusable.qml directly.