Details
-
Bug
-
Resolution: Done
-
Not Evaluated
-
5.0.0
-
None
-
2eb2d6386da304cd1164264ae0bff685c796d89c
Description
Suppose there is an application that can open several different types of projects. The class hierarchy for the classes that represent these projects is as follows:
Project ImageProject TilesetProject
Project is the base class. All properties involving projects use this as their property type. ImageProject extends Project with some extra functionality. TilesetProject is very similar to ImageProject, with only some slight differences in functionality, so it derives from it and overrides some virtual functions.
The UI for this application displays two things: an item that is relevant for any ImageProject, and an item that is only ever relevant for TilesetProjects. The tileset-specific item should only ever be visible when the user has opened a TilesetProject.
To avoid having two separate QML files for ImageProject and TilesetProject that have duplicated content except for the tileset-specific item, the developer decides to use a Loader to simply instantiate the tileset-specific item when a TilesetProject is open, and destroy it otherwise.
import QtQuick 2.9 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import App 1.0 ApplicationWindow { id: window visible: true width: 640 height: 600 ProjectManager { id: projectManager } RowLayout { anchors.fill: parent RowLayout { spacing: 40 Layout.fillHeight: true Layout.fillWidth: true Button { text: "Create ImageProject" onClicked: projectManager.create("image") } Button { text: "Create TilesetProject" onClicked: projectManager.create("tileset") } } Item { Layout.fillWidth: true } Rectangle { color: "#ccc" Layout.preferredWidth: 200 Layout.fillHeight: true ColumnLayout { anchors.fill: parent Rectangle { id: veryUnspecificRectangle color: "darkgreen" Layout.fillWidth: true Layout.preferredHeight: window.height / 2 } Loader { id: veryTilesetSpecificLoader active: projectManager.project && projectManager.project.type === "tileset" Layout.fillWidth: true Layout.preferredHeight: window.height / 2 sourceComponent: Text { text: "Some tileset-specific text" enabled: { print("veryTilesetSpecificLoader is using project of type", projectManager.project.type) projectManager.project.someTilesetSpecificProperty } } } } } } }
The problem Loader has for this use case can be seen by following the steps below:
- Click "Create TilesetProject" (simulates the user opening a TilesetProject)
- Click "Create ImageProject" (simulates the implicit closing of the TilesetProject, and opening of an ImageProject)
The output is below:
qml: veryTilesetSpecificLoader is using project of type tileset qml: veryTilesetSpecificLoader is using project of type image qrc:/main.qml:66:34: Unable to assign [undefined] to bool
veryTilesetSpecificLoader is trying to access a property in an ImageProject that only exists in TilesetProject, even though the item's very existence relies on the fact that the current project is a TilesetProject.
The problem comes from this line. The comment there says:
We can't delete immediately because our item may have triggered the Loader to load a different item.
That's fine for that particular use case that the code is trying to account for, but it means that bindings that only make sense when a loader is active/has a non-null sourceComponent are run when they shouldn't be, and there doesn't appear to be any (non-hacky) way to stop them.
A hacky workaround is to replace d->object->deleteLater(); with delete d->object;.
A proper solution would be to expose a new property in Loader that gives the developer control over this; it would essentially mean that you take responsibility for what the loaded item (or any of its children) do, and that you're sure that they're not going to cause the Loader to load another item. One idea is:
enum DestructionPolicy { DestroyLater, DestroyInstantly }
with DestroyLater being the default for backwards compatibility.
I've experienced this in other projects, and always assumed it was a flaw in how I designed the application, but the more I think about it, the more it seems like a design flaw in Loader.
Attachments
Issue Links
- is duplicated by
-
QTBUG-61853 If Loader's sourceComponent Item references its parent, and I set “loader.active = false”, I get an error
-
- Closed
-
- relates to
-
QTBUG-51995 Loader doesn't immediately destroy the loaded item, and hence cannot be used to ensure a loaded item can freely reference properties that are only valid while the item is loaded
-
- Closed
-
-
QTBUG-47321 Removed Loader component gets a null parent
-
- Closed
-