Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Delegate supplied as property => how to handle instantiation?
Forum Updated to NodeBB v4.3 + New Features

Delegate supplied as property => how to handle instantiation?

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
4 Posts 2 Posters 670 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    barbicels
    wrote on last edited by barbicels
    #1

    I created a QML type that receives its delegate as a (default) Component property, so it can handle various kinds of models; the model is a variant property.

    I want this type to be able to react to instantiations of this delegate with a handler that has access to the instantiated object (to wire up its signals). With a delegate declared locally, that would be a simple matter of sticking a Component.onCompleted handler on it, but in this scenario, the delegate is declared elsewhere and passed in as a property.

    I have tried two methods, neither one satisfactory:

    • Wrap the delegate in a locally declared Item and stick the completion handler on the Item. Problem: bound role properties (modelData, model, index) don’t pass through to the “inner” delegate without a lot of ugly wrapping code.

    • Add a Component.onCompleted handler to the object that instantiates the delegate, along these lines: Component.onCompleted: delegate.Component.completed.connect(function () { code here })
      This actually runs code here as expected, but I don’t know how to access the instantiated object.

    Anyone able to help?

    1 Reply Last reply
    0
    • dheerendraD Offline
      dheerendraD Offline
      dheerendra
      Qt Champions 2022
      wrote on last edited by
      #2

      Any sample code on this concept ? This will help us to help you.

      Dheerendra
      @Community Service
      Certified Qt Specialist
      http://www.pthinks.com

      B 1 Reply Last reply
      0
      • B Offline
        B Offline
        barbicels
        wrote on last edited by barbicels
        #3

        Sure!

        [Aside: My actual goal is to create a subclass of Menu whose internal ListView creates MenuItem delegates only as needed, so that the menu can be arbitrarily long without waiting for thousands of items to be created. ListView knows how to do this (using its cacheBuffer property), but unfortunately, Menu is written in such a way that it manages all child objects (Action or MenuItem) and feeds a complete set of instantiated items to its embedded ListView object in the form of a fully built object model.]

        Here is a non-Menu code example to illustrate my issue, which is, how can I connect signals from instantiated delegates to the view that instantiated them, when the delegate Component is supplied to the view as an opaque property, not declared in the same .qml file as the view?

        First, here's the delegate, a RowLayout containing a Timer that counts down 10 seconds and then signals its expiry. (Don't worry too much about this code; it's just a tool for building my view.)

        MyDelegate.qml

        import QtQuick 2.0
        import QtQuick.Layouts 1.12
        
        RowLayout {
            signal expired()
            property int serial
            property int limit: 10
            property int countdown: limit
            width: parent.width
            clip: true
            Text {
                id: t1
                Layout.alignment: Qt.AlignVCenter
                text: "Delegate #" + serial;
            }
            Rectangle {
                Layout.alignment: Qt.AlignCenter
                Layout.fillHeight: true
                border { color: "black" }
                color: "red"
                implicitWidth: (countdown / limit) * (parent.width - t1.width - t2.width)
            }
            Text {
                id: t2
                Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
                text: countdown + " seconds"
            }
            Timer {
                interval: 1000
                repeat: true
                running: true
                onTriggered: {
                    if (--countdown === 0) {
                        expired()
                        running = false
                    }
                }
            }
            Component.onCompleted: console.log("delegate #" + serial + " completed")
            Component.onDestruction: console.log("delegate #" + serial + " destroyed")
        }
        

        Next, I want a ListView to manage these delegates so that each row shrinks in height when its timer expires. The following code works nicely; the rows flatten upon expiry, and you can flick around and see how the ListView creates and destroys delegate objects as the rows enter and exit the view area. This code works perfectly, because the view's delegate Component is declared inline with its own onExpired signal handler. (I'm not concerned about the fact that, for some reason, ListView never destroys delegate #0, so it stays flattened and is not recreated.)

        main.qml

        import QtQuick 2.12
        import QtQuick.Controls 2.12
        import QtQuick.Window 2.12
        
        Window {
            visible: true
            width: 600
            height: 400
            title: qsTr("ListView with Delegates That Signal")
            ListView {
                anchors.fill: parent
                ScrollBar.vertical: ScrollBar { }
                model: 400
                cacheBuffer: 0
                delegate: MyDelegate {
                    serial: index
                    height: 50
                    onExpired: height = 1
                }
            }
        }
        

        All good so far. But now, say I want to make this delegate-flattening ListView reusable, so I'm putting it in its own .qml file that takes its model and delegate as opaque properties. First step is to subclass ListView:

        MyListView.qml

        import QtQuick 2.0
        import QtQuick.Controls 2.12
        
        ListView {
            ScrollBar.vertical: ScrollBar { }
            cacheBuffer: 0
            // I want to handle delegate.expired events here
        }
        

        main.qml

        import QtQuick 2.12
        import QtQuick.Window 2.12
        
        Window {
            visible: true
            width: 600
            height: 400
            title: qsTr("ListView with Delegates That Signal")
            MyListView {
                anchors { fill: parent }
                model: 400
                delegate: MyDelegate {
                    serial: index
                    height: 50
                    onExpired: height = 1 // move this to MyListView!
                }
            }
        }
        

        That works fine, but it hasn't accomplished my objective of making the delegate-flattening behavior (the onExpired signal handler) an integral part of MyListView, and removing it from the mainline. Problem is, MyListView's delegate is an opaque Component property. How can I attach a signal handler to every instance that is created from it, properly encapsulated inside the MyListView type?

        If the property were a simple Item, I could use a Connections object to attach a handler to its signal, but here it's a Component that gets instantiated by ListView in logic that is outside my control, so I can't target that property with a Connections object. One way to take control would be to wrap the delegate in a Loader. This gets the expired event handler out of the mainline (but note, I had to tweak it slightly, to remove the explicit delegate property label):

        MyListView.qml

        import QtQuick 2.0
        import QtQuick.Controls 2.12
        
        ListView {
            default property Component insideDelegate
            ScrollBar.vertical: ScrollBar { }
            cacheBuffer: 0
            delegate: Loader {
                height: insideDelegate.height
                width: parent.width
                sourceComponent: insideDelegate
                onLoaded: item.expired.connect(function () { item.height = 1 })
            }
        }
        

        main.qml

        import QtQuick 2.12
        import QtQuick.Window 2.12
        
        Window {
            visible: true
            width: 600
            height: 400
            title: qsTr("ListView with Delegates That Signal")
            MyListView {
                anchors { fill: parent }
                model: 400
                MyDelegate {
                    serial: index
                    height: 50
                }
            }
        }
        

        This almost works, but the log is full of warnings and all the rows say "Delegate #0", because the delegate no longer sees its bound index property. (If the model were a StringList or C++ model, it would also be missing its modelData, model, and other role properties that MyListView shouldn't have to care about.) Now I have to do some crazy nesting, just to make all necessary bound properties accessible to the delegate:

        MyListView.qml

        import QtQuick 2.0
        import QtQuick.Controls 2.12
        
        ListView {
            default property Component insideDelegate
            ScrollBar.vertical: ScrollBar { }
            cacheBuffer: 0
            delegate: Loader {
                height: insideDelegate.height
                width: parent.width
                property int _index: index
                sourceComponent: Loader {
                    property int index: _index
                    sourceComponent: insideDelegate
                    onLoaded: item.expired.connect(function () { item.height = 1 })
                }
            }
        }
        

        That's really messy, and any further logic I might add will have to dig through layers of child objects to examine the "inside" delegates. Let's go back a step, undo that nesting, and try watching for delegates as they are instantiated and added to the view:

        MyListView.qml

        import QtQuick 2.0
        import QtQuick.Controls 2.12
        
        ListView {
            ScrollBar.vertical: ScrollBar { }
            cacheBuffer: 0
            Connections {
                target: contentItem
                onChildrenChanged: {
                    var newItem = contentItem.children[contentItem.children.length - 1]
                    newItem.expired.connect(function () { newItem.height = 1 })
                }
            }
        }
        

        main.qml

        import QtQuick 2.12
        import QtQuick.Window 2.12
        
        Window {
            visible: true
            width: 600
            height: 400
            title: qsTr("ListView with Delegates That Signal")
            MyListView {
                anchors { fill: parent }
                model: 400
                delegate: MyDelegate {
                    serial: index
                    height: 50
                }
            }
        }
        

        That almost works, but for some reason child #1 gripes about its expired signal failing to connect, and the onChildrenChanged handler doesn't know the vertical row-index of the new child item (which could be relevant) because the children array is ordered from oldest to newest — at least, not without peeking at the item's serial property, which is an artifact of this sample MyDelegate implementation, which is none of MyListView's business.

        So, this is all very frustrating. What I wish I could do is to go back to the simplest version of MyListView.qml and add delegate.onExpired: height = 1 as a single line of code, but of course that's bad syntax.

        What do you suggest?

        1 Reply Last reply
        0
        • dheerendraD dheerendra

          Any sample code on this concept ? This will help us to help you.

          B Offline
          B Offline
          barbicels
          wrote on last edited by
          #4

          @dheerendra See my following post in this thread.

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved