Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-60344

No way to prevent bindings of scheduled-to-be-deleted Loader item from evaluating

    XMLWordPrintable

Details

    • 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:

      1. Click "Create TilesetProject" (simulates the user opening a TilesetProject)
      2. 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

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              srutledg Shawn Rutledge
              mitch_curtis Mitch Curtis
              Votes:
              3 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Gerrit Reviews

                  There are no open Gerrit changes